Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(249)

Side by Side Diff: build/android/pylib/results/presentation/test_results_presentation.py

Issue 2933993002: Add local results details pages.
Patch Set: Fix some of Johns comments. Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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())
OLDNEW
« no previous file with comments | « build/android/pylib/results/presentation/template/main.html ('k') | build/android/pylib/utils/google_storage_helper.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698