OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2017 The Chromium Authors. All rights reserved. | 3 # Copyright 2017 The Chromium Authors. All rights reserved. |
4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
6 | 6 |
7 import argparse | 7 import argparse |
8 import collections | 8 import collections |
9 import json | 9 import json |
10 import tempfile | 10 import tempfile |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 def feedback_url(result_details_link): | 268 def feedback_url(result_details_link): |
269 url_args = urllib.urlencode([ | 269 url_args = urllib.urlencode([ |
270 ('labels', 'Pri-2,Type-Bug,Restrict-View-Google'), | 270 ('labels', 'Pri-2,Type-Bug,Restrict-View-Google'), |
271 ('summary', 'Result Details Feedback:'), | 271 ('summary', 'Result Details Feedback:'), |
272 ('components', 'Test>Android'), | 272 ('components', 'Test>Android'), |
273 ('comment', 'Please check out: %s' % result_details_link)]) | 273 ('comment', 'Please check out: %s' % result_details_link)]) |
274 return 'https://bugs.chromium.org/p/chromium/issues/entry?%s' % url_args | 274 return 'https://bugs.chromium.org/p/chromium/issues/entry?%s' % url_args |
275 | 275 |
276 | 276 |
277 def results_to_html(results_dict, cs_base_url, bucket, test_name, | 277 def results_to_html(results_dict, cs_base_url, bucket, test_name, |
278 builder_name, build_number): | 278 builder_name, build_number, local_output): |
279 """Convert list of test results into html format.""" | 279 """Convert list of test results into html format. |
280 | 280 |
281 test_rows_header, test_rows = create_test_table(results_dict, cs_base_url, | 281 Args: |
282 test_name) | 282 local_output: Whether this results file is uploaded to Google Storage or |
| 283 just a local file. |
| 284 """ |
| 285 test_rows_header, test_rows = create_test_table( |
| 286 results_dict, cs_base_url, test_name) |
283 suite_rows_header, suite_rows, suite_row_footer = create_suite_table( | 287 suite_rows_header, suite_rows, suite_row_footer = create_suite_table( |
284 results_dict) | 288 results_dict) |
285 | 289 |
286 suite_table_values = { | 290 suite_table_values = { |
287 'table_id': 'suite-table', | 291 'table_id': 'suite-table', |
288 'table_headers': suite_rows_header, | 292 'table_headers': suite_rows_header, |
289 'table_row_blocks': suite_rows, | 293 'table_row_blocks': suite_rows, |
290 'table_footer': suite_row_footer, | 294 'table_footer': suite_row_footer, |
291 } | 295 } |
292 | 296 |
293 test_table_values = { | 297 test_table_values = { |
294 'table_id': 'test-table', | 298 'table_id': 'test-table', |
295 'table_headers': test_rows_header, | 299 'table_headers': test_rows_header, |
296 'table_row_blocks': test_rows, | 300 'table_row_blocks': test_rows, |
297 } | 301 } |
298 | 302 |
299 main_template = JINJA_ENVIRONMENT.get_template( | 303 main_template = JINJA_ENVIRONMENT.get_template( |
300 os.path.join('template', 'main.html')) | 304 os.path.join('template', 'main.html')) |
301 dest = google_storage_helper.unique_name( | |
302 '%s_%s_%s' % (test_name, builder_name, build_number)) | |
303 | 305 |
304 result_details_link = google_storage_helper.get_url_link( | 306 if local_output: |
305 dest, '%s/html' % bucket) | 307 html_render = main_template.render( # pylint: disable=no-member |
306 | 308 { |
307 return (main_template.render( # pylint: disable=no-member | 309 'tb_values': [suite_table_values, test_table_values] |
308 {'tb_values': [suite_table_values, test_table_values], | 310 }) |
309 'feedback_url': feedback_url(result_details_link) | 311 return (html_render, None, None) |
310 }), dest, result_details_link) | 312 else: |
| 313 dest = google_storage_helper.unique_name( |
| 314 '%s_%s_%s' % (test_name, builder_name, build_number)) |
| 315 result_details_link = google_storage_helper.get_url_link( |
| 316 dest, '%s/html' % bucket) |
| 317 html_render = main_template.render( # pylint: disable=no-member |
| 318 { |
| 319 'tb_values': [suite_table_values, test_table_values], |
| 320 'feedback_url': feedback_url(result_details_link), |
| 321 }) |
| 322 return (html_render, dest, result_details_link) |
311 | 323 |
312 | 324 |
313 def result_details(json_path, cs_base_url, bucket, test_name, | 325 def result_details(json_path, test_name, cs_base_url, bucket=None, |
314 builder_name, build_number): | 326 builder_name=None, build_number=None, local_output=False): |
315 """Get result details from json path and then convert results to html.""" | 327 """Get result details from json path and then convert results to html. |
| 328 |
| 329 Args: |
| 330 local_output: Whether this results file is uploaded to Google Storage or |
| 331 just a local file. |
| 332 """ |
316 | 333 |
317 with open(json_path) as json_file: | 334 with open(json_path) as json_file: |
318 json_object = json.loads(json_file.read()) | 335 json_object = json.loads(json_file.read()) |
319 | 336 |
320 if not 'per_iteration_data' in json_object: | 337 if not 'per_iteration_data' in json_object: |
321 return 'Error: json file missing per_iteration_data.' | 338 return 'Error: json file missing per_iteration_data.' |
322 | 339 |
323 results_dict = collections.defaultdict(list) | 340 results_dict = collections.defaultdict(list) |
324 for testsuite_run in json_object['per_iteration_data']: | 341 for testsuite_run in json_object['per_iteration_data']: |
325 for test, test_runs in testsuite_run.iteritems(): | 342 for test, test_runs in testsuite_run.iteritems(): |
326 results_dict[test].extend(test_runs) | 343 results_dict[test].extend(test_runs) |
327 return results_to_html(results_dict, cs_base_url, bucket, | 344 return results_to_html(results_dict, cs_base_url, bucket, test_name, |
328 test_name, builder_name, build_number) | 345 builder_name, build_number, local_output) |
329 | 346 |
330 | 347 |
331 def upload_to_google_bucket(html, bucket, dest): | 348 def upload_to_google_bucket(html, bucket, dest): |
332 with tempfile.NamedTemporaryFile(suffix='.html') as temp_file: | 349 with tempfile.NamedTemporaryFile(suffix='.html') as temp_file: |
333 temp_file.write(html) | 350 temp_file.write(html) |
334 temp_file.flush() | 351 temp_file.flush() |
335 return google_storage_helper.upload( | 352 return google_storage_helper.upload( |
336 name=dest, | 353 name=dest, |
337 filepath=temp_file.name, | 354 filepath=temp_file.name, |
338 bucket='%s/html' % bucket, | 355 bucket='%s/html' % bucket, |
339 content_type='text/html', | 356 content_type='text/html', |
340 authenticated_link=True) | 357 authenticated_link=True) |
341 | 358 |
342 | 359 |
343 def main(): | 360 def main(): |
344 parser = argparse.ArgumentParser() | 361 parser = argparse.ArgumentParser() |
345 parser.add_argument('--json-file', help='Path of json file.') | 362 parser.add_argument('--json-file', help='Path of json file.') |
346 parser.add_argument('--cs-base-url', help='Base url for code search.', | 363 parser.add_argument('--cs-base-url', help='Base url for code search.', |
347 default='http://cs.chromium.org') | 364 default='http://cs.chromium.org') |
348 parser.add_argument('--bucket', help='Google storage bucket.', required=True) | 365 parser.add_argument('--bucket', help='Google storage bucket.', required=True) |
349 parser.add_argument('--builder-name', help='Builder name.') | 366 parser.add_argument('--builder-name', help='Builder name.') |
350 parser.add_argument('--build-number', help='Build number.') | 367 parser.add_argument('--build-number', help='Build number.') |
351 parser.add_argument('--test-name', help='The name of the test.', | 368 parser.add_argument('--test-name', help='The name of the test.', |
352 required=True) | 369 required=True) |
353 parser.add_argument( | 370 parser.add_argument( |
354 '-o', '--output-json', | 371 '-o', '--output-json', |
355 help='(Swarming Merge Script API)' | 372 help='(Swarming Merge Script API) ' |
356 ' Output JSON file to create.') | 373 'Output JSON file to create.') |
357 parser.add_argument( | 374 parser.add_argument( |
358 '--build-properties', | 375 '--build-properties', |
359 help='(Swarming Merge Script API) ' | 376 help='(Swarming Merge Script API) ' |
360 'Build property JSON file provided by recipes.') | 377 'Build property JSON file provided by recipes.') |
361 parser.add_argument( | 378 parser.add_argument( |
362 '--summary-json', | 379 '--summary-json', |
363 help='(Swarming Merge Script API)' | 380 help='(Swarming Merge Script API) ' |
364 ' Summary of shard state running on swarming.' | 381 'Summary of shard state running on swarming. ' |
365 ' (Output of the swarming.py collect' | 382 '(Output of the swarming.py collect ' |
366 ' --task-summary-json=XXX command.)') | 383 '--task-summary-json=XXX command.)') |
367 parser.add_argument( | 384 parser.add_argument( |
368 'positional', nargs='*', | 385 'positional', nargs='*', |
369 help='output.json from shards.') | 386 help='output.json from shards.') |
370 | 387 |
371 args = parser.parse_args() | 388 args = parser.parse_args() |
372 | 389 |
373 if ((args.build_properties is None) == | 390 if ((args.build_properties is None) == |
374 (args.build_number is None or args.builder_name is None)): | 391 (args.build_number is None or args.builder_name is None)): |
375 raise parser.error('Exactly one of build_perperties or ' | 392 raise parser.error('Exactly one of build_perperties or ' |
376 '(build_number or builder_name) should be given.') | 393 '(build_number or builder_name) should be given.') |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
413 else: | 430 else: |
414 raise Exception('summary_json required by merge API is missing.') | 431 raise Exception('summary_json required by merge API is missing.') |
415 elif args.json_file: | 432 elif args.json_file: |
416 json_file = args.json_file | 433 json_file = args.json_file |
417 | 434 |
418 if not os.path.exists(json_file): | 435 if not os.path.exists(json_file): |
419 raise IOError('--json-file %s not found.' % json_file) | 436 raise IOError('--json-file %s not found.' % json_file) |
420 | 437 |
421 # Link to result details presentation page is a part of the page. | 438 # Link to result details presentation page is a part of the page. |
422 result_html_string, dest, result_details_link = result_details( | 439 result_html_string, dest, result_details_link = result_details( |
423 json_file, args.cs_base_url, args.bucket, | 440 json_file, args.test_name, args.cs_base_url, args.bucket, |
424 args.test_name, builder_name, build_number) | 441 builder_name, build_number) |
425 | 442 |
426 result_details_link_2 = upload_to_google_bucket( | 443 result_details_link_2 = upload_to_google_bucket( |
427 result_html_string.encode('UTF-8'), | 444 result_html_string.encode('UTF-8'), |
428 args.bucket, dest) | 445 args.bucket, dest) |
429 | |
430 assert result_details_link == result_details_link_2, ( | 446 assert result_details_link == result_details_link_2, ( |
431 'Result details link do not match. The link returned by get_url_link' | 447 'Result details link do not match. The link returned by get_url_link' |
432 ' should be the same as that returned by upload.') | 448 ' should be the same as that returned by upload.') |
433 | 449 |
434 if args.output_json: | 450 if args.output_json: |
435 with open(json_file) as original_json_file: | 451 with open(json_file) as original_json_file: |
436 json_object = json.load(original_json_file) | 452 json_object = json.load(original_json_file) |
437 json_object['links'] = {'result_details': result_details_link} | 453 json_object['links'] = {'result_details': result_details_link} |
438 with open(args.output_json, 'w') as f: | 454 with open(args.output_json, 'w') as f: |
439 json.dump(json_object, f) | 455 json.dump(json_object, f) |
440 else: | 456 else: |
441 print result_details_link | 457 print result_details_link |
442 | 458 |
443 if __name__ == '__main__': | 459 if __name__ == '__main__': |
444 sys.exit(main()) | 460 sys.exit(main()) |
OLD | NEW |