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 |