Merge lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting into lp:ubuntu-community-testing
- improved-reporting
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Christopher Lee | ||||
Approved revision: | 50 | ||||
Merged at revision: | 35 | ||||
Proposed branch: | lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting | ||||
Merge into: | lp:ubuntu-community-testing | ||||
Diff against target: |
704 lines (+466/-124) 9 files modified
ubuntu_pt_community/pages/pages.py (+40/-9) ubuntu_pt_community/pages/reports.py (+235/-91) ubuntu_pt_community/templates/base.html (+1/-0) ubuntu_pt_community/templates/results/all_testsuite_overview.html (+45/-0) ubuntu_pt_community/templates/results/all_testsuites.html (+0/-23) ubuntu_pt_community/templates/results/index.html (+1/-1) ubuntu_pt_community/templates/results/overview_base.html (+33/-0) ubuntu_pt_community/templates/results/test_drilldown_details.html (+69/-0) ubuntu_pt_community/templates/results/testsuite_drilldown.html (+42/-0) |
||||
To merge this branch: | bzr merge lp:~canonical-platform-qa/ubuntu-community-testing/improved-reporting | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brendan Donegan (community) | Needs Fixing | ||
Review via email: mp+274093@code.launchpad.net |
Commit message
Improved reporting for uploaded test results.
Description of the change
Better reporting for the uploaded results.
Reporting types included (incl. reference to the image lines in the follow up branch.)
- [1] All application testsuite review
-> Shows breakdown of all application and their tests (pass/skip/fail
- [2] Singular application testsuite (same as above but shows single application (i.e. 2015.com.
- [3] Testsuite overview (click on an apps testsuite name to get to.)
-> Drilldown of the testsuite incl P/S/F
- [4] Test Drilldown (click on test name to get to this report)
-> Lowest level to go, shows P/S/F, comments. W/ plans to linkify comments referencing bugs.
Christopher Lee (veebers) wrote : | # |
I Ahmad (iahmad) wrote : | # |
I think there should be someway to see the latest test results at both testsuite and test case level.
- 50. By Christopher Lee
-
Added latest outcome to report.
Christopher Lee (veebers) wrote : | # |
> I think there should be someway to see the latest test results at both
> testsuite and test case level.
Added this at revno 50. Images showing this:
- testsuite details: http://
- test drilldown: http://
I Ahmad (iahmad) wrote : | # |
ok reporting looks good for now but it would be nice to see the date for last run, device it ran on, related bug links etc
I Ahmad (iahmad) wrote : | # |
and of course the information about the image/build and a graph/chart too.
Christopher Lee (veebers) wrote : | # |
@iahmad, sounds good all do-able. I would suggest getting this landed as is (well, within it's current scope) and iterate to add those additional things (with a defined scope, i.e. what does the graph/chart need to show.)
Brendan Donegan (brendan-donegan) wrote : | # |
Technically speaking the part of a provider name before the : is just an FQDN, so it doesn't really make much sense on its own. Therefore I'd have the top level of reporting be the full name of the provider, breaking down into individual testplans/
2014.com.
-> clock-app-alarm
-> clock-app-setting
-> clock-app-stopwatch
-> clock-app-worldcity
Also the testsuite overview seems to be mixing job plainbox execution unit (pxu) file names with test plan or whitelist file names. I'd have to look at the raw data to see what the reason for this is though.
Christopher Lee (veebers) wrote : | # |
> Technically speaking the part of a provider name before the : is just an FQDN,
> so it doesn't really make much sense on its own. Therefore I'd have the top
> level of reporting be the full name of the provider, breaking down into
> individual testplans/
>
> 2014.com.
> -> clock-app-alarm
> -> clock-app-setting
> -> clock-app-stopwatch
> -> clock-app-worldcity
I understand that the FQDN doesn't really make much sense in itself, but it's used here to collect the tests under a related header. For instance in your example there is no definition of 'clock-tests' (as the results come in named '2015.com.
Otherwise for the main overview there would be a _lot_ of tables (e.g. one for 2015.com.
As balloons has acked the existing reports and is really eager to see something I'm going to do the potentially rude thing and deploy this as is so we have something now and we can iterate on the specifics and improve it from there.
> Also the testsuite overview seems to be mixing job plainbox execution unit
> (pxu) file names with test plan or whitelist file names. I'd have to look at
> the raw data to see what the reason for this is though.
This is more than likely due to the example results that I've uploaded during my testing. I have a task to clear this out of the database and it should resolve the issue you're seeing. (You mention this due to the appearance of '2015.com.
Preview Diff
1 | === modified file 'ubuntu_pt_community/pages/pages.py' | |||
2 | --- ubuntu_pt_community/pages/pages.py 2015-09-17 05:31:40 +0000 | |||
3 | +++ ubuntu_pt_community/pages/pages.py 2015-10-13 04:38:18 +0000 | |||
4 | @@ -27,11 +27,20 @@ | |||
5 | 27 | 27 | ||
6 | 28 | class PageDefinition: | 28 | class PageDefinition: |
7 | 29 | 29 | ||
10 | 30 | def __init__(self, name, route_name, route_func): | 30 | def __init__(self, name, route_name, route_func, display=True): |
11 | 31 | """Encapsulate the report view details.""" | 31 | """Encapsulate the report view details. |
12 | 32 | |||
13 | 33 | :param name: Title name of the Page | ||
14 | 34 | :param route_name: Route string to use when defining the route | ||
15 | 35 | :param route_func: Function to bind to the route | ||
16 | 36 | :param display: Is this link displayable as is (i.e. not a composite | ||
17 | 37 | url) | ||
18 | 38 | |||
19 | 39 | """ | ||
20 | 32 | self.name = name | 40 | self.name = name |
21 | 33 | self.route_name = route_name | 41 | self.route_name = route_name |
22 | 34 | self.route_func = route_func | 42 | self.route_func = route_func |
23 | 43 | self.display = display | ||
24 | 35 | 44 | ||
25 | 36 | @property | 45 | @property |
26 | 37 | def route_function_name(self): | 46 | def route_function_name(self): |
27 | @@ -43,16 +52,37 @@ | |||
28 | 43 | # them and adding routes. | 52 | # them and adding routes. |
29 | 44 | Report_Pages = [ | 53 | Report_Pages = [ |
30 | 45 | PageDefinition( | 54 | PageDefinition( |
31 | 46 | 'All Results', | ||
32 | 47 | '/reports/all_results', | ||
33 | 48 | reports.view_all_results | ||
34 | 49 | ), | ||
35 | 50 | |||
36 | 51 | PageDefinition( | ||
37 | 52 | 'Latest Uploads', | 55 | 'Latest Uploads', |
38 | 53 | '/reports/latest', | 56 | '/reports/latest', |
39 | 54 | reports.view_latest_uploads | 57 | reports.view_latest_uploads |
40 | 55 | ), | 58 | ), |
41 | 59 | |||
42 | 60 | PageDefinition( | ||
43 | 61 | 'All Tests Overview', | ||
44 | 62 | '/reports/overview', | ||
45 | 63 | reports.view_testsuite_overview | ||
46 | 64 | ), | ||
47 | 65 | |||
48 | 66 | PageDefinition( | ||
49 | 67 | 'Testsuite Overview', | ||
50 | 68 | '/reports/overview/<app_under_test>', | ||
51 | 69 | reports.view_testsuite_overview, | ||
52 | 70 | display=False | ||
53 | 71 | ), | ||
54 | 72 | |||
55 | 73 | PageDefinition( | ||
56 | 74 | 'Testsuite Overview', | ||
57 | 75 | '/reports/overview/<app_under_test>/<testsuite>', | ||
58 | 76 | reports.view_testsuite_drilldown, | ||
59 | 77 | display=False | ||
60 | 78 | ), | ||
61 | 79 | |||
62 | 80 | PageDefinition( | ||
63 | 81 | 'Test Details', | ||
64 | 82 | '/reports/overview/<app_under_test>/<testsuite>/<test>', | ||
65 | 83 | reports.view_test_details, | ||
66 | 84 | display=False | ||
67 | 85 | ), | ||
68 | 56 | ] | 86 | ] |
69 | 57 | 87 | ||
70 | 58 | 88 | ||
71 | @@ -66,4 +96,5 @@ | |||
72 | 66 | 96 | ||
73 | 67 | def view_reports(): | 97 | def view_reports(): |
74 | 68 | """Render a simple list of links to available reports.""" | 98 | """Render a simple list of links to available reports.""" |
76 | 69 | return render_template('results/index.html', reports=Report_Pages) | 99 | index_reports = [report for report in Report_Pages if report.display] |
77 | 100 | return render_template('results/index.html', reports=index_reports) | ||
78 | 70 | 101 | ||
79 | === modified file 'ubuntu_pt_community/pages/reports.py' | |||
80 | --- ubuntu_pt_community/pages/reports.py 2015-09-29 03:40:29 +0000 | |||
81 | +++ ubuntu_pt_community/pages/reports.py 2015-10-13 04:38:18 +0000 | |||
82 | @@ -19,7 +19,9 @@ | |||
83 | 19 | 19 | ||
84 | 20 | import json | 20 | import json |
85 | 21 | import logging | 21 | import logging |
86 | 22 | import re | ||
87 | 22 | from collections import ( | 23 | from collections import ( |
88 | 24 | Counter, | ||
89 | 23 | defaultdict, | 25 | defaultdict, |
90 | 24 | namedtuple, | 26 | namedtuple, |
91 | 25 | ) | 27 | ) |
92 | @@ -30,78 +32,77 @@ | |||
93 | 30 | logger = logging.getLogger(__name__) | 32 | logger = logging.getLogger(__name__) |
94 | 31 | 33 | ||
95 | 32 | 34 | ||
168 | 33 | def view_all_results(): | 35 | CommentDetail = namedtuple('CommentDetail', ['date', 'comment']) |
169 | 34 | """Render report displaying all uploaded testsuites with stats. | 36 | |
170 | 35 | 37 | ||
171 | 36 | Stats displayed are number of runs, number of passes and fails and the % of | 38 | class RunAggregator: |
172 | 37 | successful runs. | 39 | """Build up aggregate details for a testsuite/test.""" |
173 | 38 | """ | 40 | |
174 | 39 | results_collection = db.get_results_collection() | 41 | def __init__(self): |
175 | 40 | all_uploads = results_collection.find() | 42 | self.outcome_count = Counter() |
176 | 41 | simple_report_data = _get_simple_report_data(all_uploads) | 43 | self.comments = [] |
177 | 42 | return render_template( | 44 | self._latest_outcome_date = None |
178 | 43 | 'results/all_testsuites.html', | 45 | self._latest_outcome = None |
179 | 44 | results=simple_report_data | 46 | |
180 | 45 | ) | 47 | def update(self, outcome, testdate, comment=None): |
181 | 46 | 48 | """Update stored details for the test/testsuite. | |
182 | 47 | 49 | ||
183 | 48 | def _get_simple_report_data(all_uploads): | 50 | :param outcome: String representing a test-run outcome. (Only report on |
184 | 49 | """Produce a list of suite results. | 51 | 'pass', 'fail', 'skip'.) |
185 | 50 | 52 | :param testdate: Datetime for the testrun. | |
186 | 51 | :param all_uploads: list of dicts containing uploaded data details. | 53 | :param comment: Comment string for the test (if present). Will not be |
187 | 52 | :returns: a list of TestsuiteResult objects. | 54 | stored if it is None (defaults to none). |
188 | 53 | """ | 55 | |
189 | 54 | TestsuiteResult = namedtuple( | 56 | """ |
190 | 55 | 'TestsuiteResult', | 57 | # Not all tests will have comments (or have comments we care about). |
191 | 56 | ['name', 'runs', 'passes', 'fails', 'success_rate'] | 58 | self.outcome_count[outcome] += 1 |
192 | 57 | ) | 59 | if comment is not None and comment != "": |
193 | 58 | 60 | self.comments.append(CommentDetail(testdate, comment)) | |
194 | 59 | # all_uploads will be a list of dicts | 61 | |
195 | 60 | # dict will have keys: | 62 | self._update_latest_outcome(testdate, outcome) |
196 | 61 | # - results: a json string with the result details. | 63 | |
197 | 62 | # - user_email: email of the user who uploaded the results | 64 | def _update_latest_outcome(self, testdate, outcome): |
198 | 63 | # - uploaded: a date of uploading | 65 | try: |
199 | 64 | testsuites = defaultdict(list) | 66 | if testdate > self._latest_outcome_date: |
200 | 65 | for upload in all_uploads: | 67 | self._latest_outcome_date = testdate |
201 | 66 | upload_id = str(upload['_id']) | 68 | self._latest_outcome = outcome |
202 | 67 | 69 | except TypeError: | |
203 | 68 | try: | 70 | # first testdate comparison |
204 | 69 | results = _result_dict_from_document(upload) | 71 | self._latest_outcome_date = testdate |
205 | 70 | except KeyError as e: | 72 | self._latest_outcome = outcome |
206 | 71 | logger.warning(e) | 73 | |
207 | 72 | continue | 74 | @property |
208 | 73 | 75 | def latest_outcome(self): | |
209 | 74 | try: | 76 | """Return the most recent outcome for this testcase.""" |
210 | 75 | only_tests = _get_only_testcases(results) | 77 | return self._latest_outcome |
211 | 76 | except KeyError: | 78 | |
212 | 77 | logger.warning( | 79 | @property |
213 | 78 | 'Skipping {}: does not contain expected details'.format( | 80 | def runs(self): |
214 | 79 | upload_id | 81 | return sum(self.outcome_count.values()) |
215 | 80 | ) | 82 | |
216 | 81 | ) | 83 | @property |
217 | 82 | continue | 84 | def passes(self): |
218 | 83 | 85 | return self.outcome_count['pass'] | |
219 | 84 | for testname in only_tests: | 86 | |
220 | 85 | # outcome will be 'pass' or 'fail' | 87 | @property |
221 | 86 | try: | 88 | def fails(self): |
222 | 87 | testsuites[testname].append(only_tests[testname]['outcome']) | 89 | return self.outcome_count['fail'] |
223 | 88 | except KeyError: | 90 | |
224 | 89 | logger.error( | 91 | @property |
225 | 90 | 'Testsuite has not outcome. Document ID: ', | 92 | def skips(self): |
226 | 91 | upload_id | 93 | return self.outcome_count['skip'] |
227 | 92 | ) | 94 | |
228 | 93 | 95 | @property | |
229 | 94 | results = [] | 96 | def success_rate(self): |
230 | 95 | for suite in testsuites: | 97 | return format(self.passes / self.runs * 100, '.2f') |
231 | 96 | runs = len(testsuites[suite]) | 98 | |
232 | 97 | passes = len([t for t in testsuites[suite] if t == 'pass']) | 99 | @property |
233 | 98 | fails = runs - passes | 100 | def fail_rate(self): |
234 | 99 | success_rate = format(passes / runs * 100, '.2f') | 101 | return format(self.fails / self.runs * 100, '.2f') |
235 | 100 | 102 | ||
236 | 101 | results.append( | 103 | @property |
237 | 102 | TestsuiteResult(suite, runs, passes, fails, success_rate) | 104 | def skip_rate(self): |
238 | 103 | ) | 105 | return format(self.skips / self.runs * 100, '.2f') |
167 | 104 | return results | ||
239 | 105 | 106 | ||
240 | 106 | 107 | ||
241 | 107 | def view_latest_uploads(): | 108 | def view_latest_uploads(): |
242 | @@ -113,27 +114,15 @@ | |||
243 | 113 | # slight shim over the returned data to prepare it for presention. | 114 | # slight shim over the returned data to prepare it for presention. |
244 | 114 | sanitised_uploads = [] | 115 | sanitised_uploads = [] |
245 | 115 | for upload in all_uploads: | 116 | for upload in all_uploads: |
246 | 117 | try: | ||
247 | 118 | testsuite_details = _get_only_testcases_from_upload(upload) | ||
248 | 119 | except KeyError: | ||
249 | 120 | logger.info('Skipping upload; issue encountered') | ||
250 | 121 | continue | ||
251 | 122 | |||
252 | 123 | uploaded_testsuite_names = testsuite_details.keys() | ||
253 | 116 | upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S') | 124 | upload_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S') |
254 | 117 | uploader_email = upload.get('user_email') or 'Anonymous' | 125 | uploader_email = upload.get('user_email') or 'Anonymous' |
255 | 118 | |||
256 | 119 | try: | ||
257 | 120 | results = _result_dict_from_document(upload) | ||
258 | 121 | except KeyError as e: | ||
259 | 122 | logger.warning(e) | ||
260 | 123 | continue | ||
261 | 124 | |||
262 | 125 | try: | ||
263 | 126 | testsuite_details = _get_only_testcases(results) | ||
264 | 127 | except KeyError: | ||
265 | 128 | upload_id = str(upload.get('_id', 'Unknown')) | ||
266 | 129 | logger.warning( | ||
267 | 130 | 'Skipping {}: does not contain expected details'.format( | ||
268 | 131 | upload_id | ||
269 | 132 | ) | ||
270 | 133 | ) | ||
271 | 134 | continue | ||
272 | 135 | uploaded_testsuite_names = testsuite_details.keys() | ||
273 | 136 | |||
274 | 137 | sanitised_uploads.append( | 126 | sanitised_uploads.append( |
275 | 138 | dict( | 127 | dict( |
276 | 139 | upload_date=upload_date, | 128 | upload_date=upload_date, |
277 | @@ -148,6 +137,51 @@ | |||
278 | 148 | ) | 137 | ) |
279 | 149 | 138 | ||
280 | 150 | 139 | ||
281 | 140 | def view_testsuite_overview(app_under_test=None): | ||
282 | 141 | results_collection = db.get_results_collection() | ||
283 | 142 | all_uploads = results_collection.find() | ||
284 | 143 | overview_details = _get_testsuite_overview(all_uploads, app_under_test) | ||
285 | 144 | |||
286 | 145 | return render_template( | ||
287 | 146 | 'results/all_testsuite_overview.html', | ||
288 | 147 | results=overview_details, | ||
289 | 148 | app_under_test=app_under_test, | ||
290 | 149 | ) | ||
291 | 150 | |||
292 | 151 | |||
293 | 152 | def view_testsuite_drilldown(app_under_test, testsuite): | ||
294 | 153 | results_collection = db.get_results_collection() | ||
295 | 154 | all_uploads = results_collection.find() | ||
296 | 155 | overview_details = _get_testsuite_drilldown( | ||
297 | 156 | all_uploads, | ||
298 | 157 | app_under_test, | ||
299 | 158 | testsuite | ||
300 | 159 | ) | ||
301 | 160 | |||
302 | 161 | return render_template( | ||
303 | 162 | 'results/testsuite_drilldown.html', | ||
304 | 163 | results=overview_details, | ||
305 | 164 | app_under_test=app_under_test, | ||
306 | 165 | testsuite=testsuite, | ||
307 | 166 | ) | ||
308 | 167 | |||
309 | 168 | |||
310 | 169 | def view_test_details(app_under_test, testsuite, test): | ||
311 | 170 | results_collection = db.get_results_collection() | ||
312 | 171 | all_uploads = results_collection.find() | ||
313 | 172 | test_details = _get_test_drilldown_details( | ||
314 | 173 | all_uploads, app_under_test, testsuite, test | ||
315 | 174 | ) | ||
316 | 175 | |||
317 | 176 | return render_template( | ||
318 | 177 | 'results/test_drilldown_details.html', | ||
319 | 178 | test_details=test_details, | ||
320 | 179 | app_under_test=app_under_test, | ||
321 | 180 | testsuite=testsuite, | ||
322 | 181 | testname=test, | ||
323 | 182 | ) | ||
324 | 183 | |||
325 | 184 | |||
326 | 151 | def _result_dict_from_document(upload): | 185 | def _result_dict_from_document(upload): |
327 | 152 | """Return a dict of result details. | 186 | """Return a dict of result details. |
328 | 153 | 187 | ||
329 | @@ -190,3 +224,113 @@ | |||
330 | 190 | except KeyError: | 224 | except KeyError: |
331 | 191 | logger.warning('Upload details does not contain a resource_map') | 225 | logger.warning('Upload details does not contain a resource_map') |
332 | 192 | raise | 226 | raise |
333 | 227 | |||
334 | 228 | |||
335 | 229 | def _get_testsuite_overview(all_uploads, app_under_test=None): | ||
336 | 230 | test_result_data = defaultdict(lambda: defaultdict(RunAggregator)) | ||
337 | 231 | |||
338 | 232 | for upload in all_uploads: | ||
339 | 233 | try: | ||
340 | 234 | test_cases = _get_only_testcases_from_upload(upload) | ||
341 | 235 | except KeyError: | ||
342 | 236 | logger.info('Skipping upload; issue encountered') | ||
343 | 237 | continue | ||
344 | 238 | |||
345 | 239 | for full_testsuitename, results in test_cases.items(): | ||
346 | 240 | app, testsuite, testcase = _split_testsuite_id(full_testsuitename) | ||
347 | 241 | if app_under_test and app_under_test != app: | ||
348 | 242 | continue | ||
349 | 243 | outcome = results['outcome'] | ||
350 | 244 | testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S') | ||
351 | 245 | test_result_data[app][testsuite].update(outcome, testrun_date) | ||
352 | 246 | |||
353 | 247 | return test_result_data | ||
354 | 248 | |||
355 | 249 | |||
356 | 250 | def _get_testsuite_drilldown(all_uploads, app_under_test, testsuite): | ||
357 | 251 | testsuite_result_data = defaultdict(RunAggregator) | ||
358 | 252 | |||
359 | 253 | for upload in all_uploads: | ||
360 | 254 | try: | ||
361 | 255 | test_cases = _get_only_testcases_from_upload(upload) | ||
362 | 256 | except KeyError: | ||
363 | 257 | logger.info('Skipping upload; issue encountered') | ||
364 | 258 | continue | ||
365 | 259 | |||
366 | 260 | testsuite_qualifier = '{app}::{testsuite}'.format( | ||
367 | 261 | app=app_under_test, testsuite=testsuite | ||
368 | 262 | ) | ||
369 | 263 | |||
370 | 264 | for full_testsuitename, results in test_cases.items(): | ||
371 | 265 | if not full_testsuitename.startswith(testsuite_qualifier): | ||
372 | 266 | continue | ||
373 | 267 | app, testsuite, testcase = _split_testsuite_id(full_testsuitename) | ||
374 | 268 | |||
375 | 269 | testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S') | ||
376 | 270 | outcome = results['outcome'] | ||
377 | 271 | testsuite_result_data[testcase].update(outcome, testrun_date) | ||
378 | 272 | |||
379 | 273 | return testsuite_result_data | ||
380 | 274 | |||
381 | 275 | |||
382 | 276 | def _get_test_drilldown_details(all_uploads, app_under_test, testsuite, test): | ||
383 | 277 | test_details_data = RunAggregator() | ||
384 | 278 | |||
385 | 279 | for upload in all_uploads: | ||
386 | 280 | try: | ||
387 | 281 | test_cases = _get_only_testcases_from_upload(upload) | ||
388 | 282 | except KeyError: | ||
389 | 283 | logger.info('Skipping upload; issue encountered') | ||
390 | 284 | continue | ||
391 | 285 | |||
392 | 286 | test_qualifier_str = '{app}::{testsuite}/{test}' | ||
393 | 287 | required_test = test_qualifier_str.format( | ||
394 | 288 | app=app_under_test, testsuite=testsuite, test=test | ||
395 | 289 | ) | ||
396 | 290 | |||
397 | 291 | for full_testsuitename, results in test_cases.items(): | ||
398 | 292 | app, this_testsuite, testcase = _split_testsuite_id( | ||
399 | 293 | full_testsuitename | ||
400 | 294 | ) | ||
401 | 295 | |||
402 | 296 | this_test = test_qualifier_str.format( | ||
403 | 297 | app=app, testsuite=this_testsuite, test=testcase | ||
404 | 298 | ) | ||
405 | 299 | |||
406 | 300 | if this_test != required_test: | ||
407 | 301 | continue | ||
408 | 302 | |||
409 | 303 | outcome = results['outcome'] | ||
410 | 304 | testrun_date = upload['uploaded'].strftime('%Y-%b-%d %H:%M:%S') | ||
411 | 305 | comment = results['comments'] | ||
412 | 306 | |||
413 | 307 | test_details_data.update(outcome, testrun_date, comment) | ||
414 | 308 | |||
415 | 309 | return test_details_data | ||
416 | 310 | |||
417 | 311 | |||
418 | 312 | def _split_testsuite_id(testsuite_string): | ||
419 | 313 | # Some tests will have a pt-id at the end | ||
420 | 314 | # app-under-test::testsuite/test/pt-id (pt-id optional) | ||
421 | 315 | elements = re.split('::|\/', testsuite_string) | ||
422 | 316 | # Ensure we only ever return 3 elements. | ||
423 | 317 | app, testsuite, test = elements[0:3] | ||
424 | 318 | return [app, testsuite, test] | ||
425 | 319 | |||
426 | 320 | |||
427 | 321 | def _get_only_testcases_from_upload(upload): | ||
428 | 322 | upload_id = str(upload['_id']) | ||
429 | 323 | try: | ||
430 | 324 | results = _result_dict_from_document(upload) | ||
431 | 325 | except KeyError as e: | ||
432 | 326 | logger.warning(e) | ||
433 | 327 | raise | ||
434 | 328 | try: | ||
435 | 329 | return _get_only_testcases(results) | ||
436 | 330 | except KeyError: | ||
437 | 331 | logger.warning( | ||
438 | 332 | 'Skipping {}: does not contain expected details'.format( | ||
439 | 333 | upload_id | ||
440 | 334 | ) | ||
441 | 335 | ) | ||
442 | 336 | raise | ||
443 | 193 | 337 | ||
444 | === modified file 'ubuntu_pt_community/templates/base.html' | |||
445 | --- ubuntu_pt_community/templates/base.html 2015-09-09 05:31:37 +0000 | |||
446 | +++ ubuntu_pt_community/templates/base.html 2015-10-13 04:38:18 +0000 | |||
447 | @@ -14,6 +14,7 @@ | |||
448 | 14 | <body> | 14 | <body> |
449 | 15 | 15 | ||
450 | 16 | <div id="content" class="container"> | 16 | <div id="content" class="container"> |
451 | 17 | {% block nav %}{% endblock %} | ||
452 | 17 | {% block content %}{% endblock %} | 18 | {% block content %}{% endblock %} |
453 | 18 | </div> | 19 | </div> |
454 | 19 | 20 | ||
455 | 20 | 21 | ||
456 | === added file 'ubuntu_pt_community/templates/results/all_testsuite_overview.html' | |||
457 | --- ubuntu_pt_community/templates/results/all_testsuite_overview.html 1970-01-01 00:00:00 +0000 | |||
458 | +++ ubuntu_pt_community/templates/results/all_testsuite_overview.html 2015-10-13 04:38:18 +0000 | |||
459 | @@ -0,0 +1,45 @@ | |||
460 | 1 | {% extends "results/overview_base.html" %} | ||
461 | 2 | {% block title %}Testsuite Overview{% endblock %} | ||
462 | 3 | |||
463 | 4 | {% block content %} | ||
464 | 5 | <h1 class="page-header">Application Testsuite Overview</h1> | ||
465 | 6 | |||
466 | 7 | <table class="table table-condensed table-bordered table-hover"> | ||
467 | 8 | {% for app_under_test in results | sort %} | ||
468 | 9 | <tr> | ||
469 | 10 | <th colspan=6 class="info"> | ||
470 | 11 | {% if results|length > 1 %} | ||
471 | 12 | <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}">{{ app_under_test }}</a> | ||
472 | 13 | {% else %} | ||
473 | 14 | {{ app_under_test }} | ||
474 | 15 | {% endif %} | ||
475 | 16 | |||
476 | 17 | </th> | ||
477 | 18 | </tr> | ||
478 | 19 | <tr> | ||
479 | 20 | <th>Testsuite</th> | ||
480 | 21 | <th class="text-center">Runs</th> | ||
481 | 22 | <th class="text-center success">Pass</th> | ||
482 | 23 | <th class="text-center warning">Skip</th> | ||
483 | 24 | <th class="text-center danger">Fail</th> | ||
484 | 25 | <th class="text-center">Success Rate</th> | ||
485 | 26 | </tr> | ||
486 | 27 | {% for testsuite in results[app_under_test] | sort %} | ||
487 | 28 | <tr> | ||
488 | 29 | <td> | ||
489 | 30 | <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}">{{testsuite}}</a> | ||
490 | 31 | </td> | ||
491 | 32 | <td class="text-center">{{results[app_under_test][testsuite].runs}}</td> | ||
492 | 33 | <td class="text-center success">{{results[app_under_test][testsuite].passes}}</td> | ||
493 | 34 | <td class="text-center warning">{{results[app_under_test][testsuite].skips}}</td> | ||
494 | 35 | <td class="text-center danger">{{results[app_under_test][testsuite].fails}}</td> | ||
495 | 36 | <td class="text-center">{{results[app_under_test][testsuite].success_rate}}%</td> | ||
496 | 37 | </tr> | ||
497 | 38 | {% endfor %} | ||
498 | 39 | <tr> | ||
499 | 40 | <td colspan=6> </td> | ||
500 | 41 | </tr> | ||
501 | 42 | {% endfor %} | ||
502 | 43 | </table> | ||
503 | 44 | |||
504 | 45 | {% endblock %} | ||
505 | 0 | 46 | ||
506 | === removed file 'ubuntu_pt_community/templates/results/all_testsuites.html' | |||
507 | --- ubuntu_pt_community/templates/results/all_testsuites.html 2015-09-09 05:44:07 +0000 | |||
508 | +++ ubuntu_pt_community/templates/results/all_testsuites.html 1970-01-01 00:00:00 +0000 | |||
509 | @@ -1,23 +0,0 @@ | |||
510 | 1 | {% extends "base.html" %} | ||
511 | 2 | {% block title %}All Result Details{% endblock %} | ||
512 | 3 | |||
513 | 4 | {% block content %} | ||
514 | 5 | <h1 class="page-header">All testsuite statistics</h1> | ||
515 | 6 | |||
516 | 7 | <!-- Could probably also add a list of users/email address that have done this report. --> | ||
517 | 8 | <table class="table table-striped table-bordered"> | ||
518 | 9 | <tr> | ||
519 | 10 | <th>Testsuite</th> | ||
520 | 11 | <th>Total Runs</th> | ||
521 | 12 | <th>Success Rate</th> | ||
522 | 13 | </tr> | ||
523 | 14 | {% for report in results %} | ||
524 | 15 | <tr> | ||
525 | 16 | <td>{{report.name}}</td> | ||
526 | 17 | <td>{{report.runs}}</td> | ||
527 | 18 | <td>{{report.success_rate}}%</td> | ||
528 | 19 | </tr> | ||
529 | 20 | {% endfor %} | ||
530 | 21 | </table> | ||
531 | 22 | |||
532 | 23 | {% endblock %} | ||
533 | 24 | 0 | ||
534 | === modified file 'ubuntu_pt_community/templates/results/index.html' | |||
535 | --- ubuntu_pt_community/templates/results/index.html 2015-09-09 05:44:07 +0000 | |||
536 | +++ ubuntu_pt_community/templates/results/index.html 2015-10-13 04:38:18 +0000 | |||
537 | @@ -1,7 +1,7 @@ | |||
538 | 1 | {% extends "base.html" %} | 1 | {% extends "base.html" %} |
539 | 2 | {% block title %}Community Testing Reports{% endblock %} | 2 | {% block title %}Community Testing Reports{% endblock %} |
540 | 3 | {% block content %} | 3 | {% block content %} |
542 | 4 | <h1 class="page-header">List of available reports</h1> | 4 | <h1 class="page-header">Available reports</h1> |
543 | 5 | <ul> | 5 | <ul> |
544 | 6 | {% for report in reports %} | 6 | {% for report in reports %} |
545 | 7 | <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li> | 7 | <li><a href="{{ url_for(report.route_function_name) }}">{{ report.name }}</a></li> |
546 | 8 | 8 | ||
547 | === added file 'ubuntu_pt_community/templates/results/overview_base.html' | |||
548 | --- ubuntu_pt_community/templates/results/overview_base.html 1970-01-01 00:00:00 +0000 | |||
549 | +++ ubuntu_pt_community/templates/results/overview_base.html 2015-10-13 04:38:18 +0000 | |||
550 | @@ -0,0 +1,33 @@ | |||
551 | 1 | {% extends "base.html" %} | ||
552 | 2 | |||
553 | 3 | {% block nav %} | ||
554 | 4 | <ol class="breadcrumb"> | ||
555 | 5 | {% set active = 'overview' %} | ||
556 | 6 | <li> | ||
557 | 7 | <a href="{{url_for('view_reports')}}">Reports</a> | ||
558 | 8 | </li> | ||
559 | 9 | {% if app_under_test is defined and not None and app_under_test is not none %} | ||
560 | 10 | {% set active = app_under_test %} | ||
561 | 11 | <li> | ||
562 | 12 | <a href="{{url_for('view_testsuite_overview')}}">overview</a> | ||
563 | 13 | </li> | ||
564 | 14 | {% if testsuite is defined and app_under_test is not none %} | ||
565 | 15 | {% set active = testsuite %} | ||
566 | 16 | <li> | ||
567 | 17 | <a href="{{url_for('view_testsuite_overview', app_under_test=app_under_test)}}"> | ||
568 | 18 | {{app_under_test}} | ||
569 | 19 | </a> | ||
570 | 20 | </li> | ||
571 | 21 | {% if testname is defined %} | ||
572 | 22 | {% set active = testname %} | ||
573 | 23 | <li> | ||
574 | 24 | <a href="{{url_for('view_testsuite_drilldown', app_under_test=app_under_test, testsuite=testsuite)}}"> | ||
575 | 25 | {{testsuite}} | ||
576 | 26 | </a> | ||
577 | 27 | </li> | ||
578 | 28 | {% endif %} | ||
579 | 29 | {% endif %} | ||
580 | 30 | {% endif %} | ||
581 | 31 | <li class="active">{{active}}</li> | ||
582 | 32 | </ol> | ||
583 | 33 | {% endblock %} | ||
584 | 0 | 34 | ||
585 | === added file 'ubuntu_pt_community/templates/results/test_drilldown_details.html' | |||
586 | --- ubuntu_pt_community/templates/results/test_drilldown_details.html 1970-01-01 00:00:00 +0000 | |||
587 | +++ ubuntu_pt_community/templates/results/test_drilldown_details.html 2015-10-13 04:38:18 +0000 | |||
588 | @@ -0,0 +1,69 @@ | |||
589 | 1 | {% extends "results/overview_base.html" %} | ||
590 | 2 | {% block title %}Test Details{% endblock %} | ||
591 | 3 | |||
592 | 4 | {% block content %} | ||
593 | 5 | <h1 class="page-header">Test Details</h1> | ||
594 | 6 | |||
595 | 7 | {% set col_count = 6 %} | ||
596 | 8 | {% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %} | ||
597 | 9 | <table class="table table-condensed table-bordered table-hover"> | ||
598 | 10 | <tr> | ||
599 | 11 | <th colspan={{col_count}} class="info">{{ app_under_test }}::{{ testsuite }}::{{ testname }}</th> | ||
600 | 12 | </tr> | ||
601 | 13 | <tr> | ||
602 | 14 | <th class="text-center">Runs</th> | ||
603 | 15 | <th class="text-center success">Pass</th> | ||
604 | 16 | <th class="text-center warning">Skips</th> | ||
605 | 17 | <th class="text-center danger">Fail</th> | ||
606 | 18 | <th class="text-center">Success Rate</th> | ||
607 | 19 | <th class="text-center">Latest Outcome</th> | ||
608 | 20 | </tr> | ||
609 | 21 | <tr> | ||
610 | 22 | <td class="text-center">{{test_details.runs}}</td> | ||
611 | 23 | <td class="text-center success">{{test_details.passes}}</td> | ||
612 | 24 | <td class="text-center warning">{{test_details.skips}}</td> | ||
613 | 25 | <td class="text-center danger">{{test_details.fails}}</td> | ||
614 | 26 | <td class="text-center">{{test_details.success_rate}}%</td> | ||
615 | 27 | <td class="text-center {{outcome_highlight[test_details.latest_outcome]}}"> | ||
616 | 28 | {{test_details.latest_outcome|capitalize}} | ||
617 | 29 | </td> | ||
618 | 30 | </tr> | ||
619 | 31 | |||
620 | 32 | <tr> | ||
621 | 33 | <th colspan={{col_count}}>Overview</th> | ||
622 | 34 | </tr> | ||
623 | 35 | <tr class="active"> | ||
624 | 36 | <td colspan={{col_count}}> | ||
625 | 37 | <div class="progress" style="margin-bottom: 0px;"> | ||
626 | 38 | <div class="progress-bar progress-bar-success" style="width: {{test_details.success_rate}}%"> | ||
627 | 39 | <span class="sr-only">{{test_details.success_rate}}% success rate</span> | ||
628 | 40 | </div> | ||
629 | 41 | <div class="progress-bar progress-bar-warning" style="width: {{test_details.skip_rate}}%"> | ||
630 | 42 | <span class="sr-only">{{test_details.skip_rate}}% skip rate</span> | ||
631 | 43 | </div> | ||
632 | 44 | <div class="progress-bar progress-bar-danger" style="width: {{test_details.fail_rate}}%"> | ||
633 | 45 | <span class="sr-only">{{test_details.fail_rate}}% fail rate</span> | ||
634 | 46 | </div> | ||
635 | 47 | </div> | ||
636 | 48 | </td> | ||
637 | 49 | </tr> | ||
638 | 50 | |||
639 | 51 | {% if test_details.comments| length > 0 %} | ||
640 | 52 | <tr> | ||
641 | 53 | <th colspan={{col_count}}>Comments</th> | ||
642 | 54 | </tr> | ||
643 | 55 | <tr class="active"> | ||
644 | 56 | <td colspan={{col_count}}> | ||
645 | 57 | {% for comment in test_details.comments %} | ||
646 | 58 | <blockquote> | ||
647 | 59 | <p>{{comment.comment}}</p> | ||
648 | 60 | <footer>{{comment.date}}</footer> | ||
649 | 61 | </blockquote> | ||
650 | 62 | {% endfor %} | ||
651 | 63 | </td> | ||
652 | 64 | </tr> | ||
653 | 65 | {% endif %} | ||
654 | 66 | |||
655 | 67 | </table> | ||
656 | 68 | |||
657 | 69 | {% endblock %} | ||
658 | 0 | 70 | ||
659 | === added file 'ubuntu_pt_community/templates/results/testsuite_drilldown.html' | |||
660 | --- ubuntu_pt_community/templates/results/testsuite_drilldown.html 1970-01-01 00:00:00 +0000 | |||
661 | +++ ubuntu_pt_community/templates/results/testsuite_drilldown.html 2015-10-13 04:38:18 +0000 | |||
662 | @@ -0,0 +1,42 @@ | |||
663 | 1 | {% extends "results/overview_base.html" %} | ||
664 | 2 | {% block title %}Testsuite Drilldown{% endblock %} | ||
665 | 3 | |||
666 | 4 | {% block content %} | ||
667 | 5 | <h1 class="page-header">Testsuite Drilldown</h1> | ||
668 | 6 | |||
669 | 7 | {% set col_count = 7 %} | ||
670 | 8 | {% set outcome_highlight = {'pass': 'success', 'skip': 'warning', 'fail': 'danger'} %} | ||
671 | 9 | <table class="table table-condensed table-bordered table-hover"> | ||
672 | 10 | <tr> | ||
673 | 11 | <th colspan={{ col_count }} class="info">{{ app_under_test }}::{{ testsuite }}</th> | ||
674 | 12 | </tr> | ||
675 | 13 | <tr> | ||
676 | 14 | <th>Testsuite</th> | ||
677 | 15 | <th class="text-center">Runs</th> | ||
678 | 16 | <th class="text-center success">Pass</th> | ||
679 | 17 | <th class="text-center warning">Skip</th> | ||
680 | 18 | <th class="text-center danger">Fail</th> | ||
681 | 19 | <th class="text-center">Success Rate</th> | ||
682 | 20 | <th class="text-center">Latest Outcome</th> | ||
683 | 21 | </tr> | ||
684 | 22 | {% for test in results | sort %} | ||
685 | 23 | {% set current_test = results[test] %} | ||
686 | 24 | <tr> | ||
687 | 25 | <td> | ||
688 | 26 | <a href="{{url_for('view_test_details', app_under_test=app_under_test, testsuite=testsuite, test=test)}}"> | ||
689 | 27 | {{test}} | ||
690 | 28 | </a> | ||
691 | 29 | </td> | ||
692 | 30 | <td class="text-center">{{current_test.runs}}</td> | ||
693 | 31 | <td class="text-center success">{{current_test.passes}}</td> | ||
694 | 32 | <td class="text-center warning">{{current_test.skips}}</td> | ||
695 | 33 | <td class="text-center danger">{{current_test.fails}}</td> | ||
696 | 34 | <td class="text-center">{{current_test.success_rate}}%</td> | ||
697 | 35 | <td class="text-center {{outcome_highlight[current_test.latest_outcome]}}"> | ||
698 | 36 | {{current_test.latest_outcome|capitalize}} | ||
699 | 37 | </td> | ||
700 | 38 | </tr> | ||
701 | 39 | |||
702 | 40 | {% endfor %} | ||
703 | 41 | </table> | ||
704 | 42 | {% endblock %} |
These are images of the reports mentioned in the desc. of change.
[1] http:// people. canonical. com/~leecj2/ reporting/ reporting- overview. png people. canonical. com/~leecj2/ reporting/ reporting- suite-overview. png people. canonical. com/~leecj2/ reporting/ reporting- suite-drilldown .png people. canonical. com/~leecj2/ reporting/ reporting- test-details. png
[2] http://
[3] http://
[4] http://