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