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

Side by Side Diff: build/android/pylib/local/device/local_device_instrumentation_test_run.py

Issue 2866103002: Add render test results to the results_details webpage. (Closed)
Patch Set: Addressed Comments Created 3 years, 7 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 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import logging 5 import logging
6 import os 6 import os
7 import posixpath 7 import posixpath
8 import re 8 import re
9 import sys
10 import tempfile
9 import time 11 import time
10 12
11 from devil.android import device_errors 13 from devil.android import device_errors
12 from devil.android import flag_changer 14 from devil.android import flag_changer
13 from devil.android.sdk import shared_prefs 15 from devil.android.sdk import shared_prefs
14 from devil.utils import reraiser_thread 16 from devil.utils import reraiser_thread
15 from pylib import valgrind_tools 17 from pylib import valgrind_tools
16 from pylib.android import logdog_logcat_monitor 18 from pylib.android import logdog_logcat_monitor
17 from pylib.base import base_test_result 19 from pylib.base import base_test_result
20 from pylib.constants import host_paths
18 from pylib.instrumentation import instrumentation_test_instance 21 from pylib.instrumentation import instrumentation_test_instance
19 from pylib.local.device import local_device_environment 22 from pylib.local.device import local_device_environment
20 from pylib.local.device import local_device_test_run 23 from pylib.local.device import local_device_test_run
21 from pylib.utils import google_storage_helper 24 from pylib.utils import google_storage_helper
22 from pylib.utils import logdog_helper 25 from pylib.utils import logdog_helper
23 from py_trace_event import trace_event 26 from py_trace_event import trace_event
24 from py_utils import contextlib_ext 27 from py_utils import contextlib_ext
25 from py_utils import tempfile_ext 28 from py_utils import tempfile_ext
26 import tombstones 29 import tombstones
27 30
31 sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party'))
32 import jinja2 # pylint: disable=import-error
33
34
35 _JINJA_TEMPLATE_DIR = os.path.join(
36 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'pylib', 'instrumentation')
37 _JINJA_TEMPLATE_FILENAME = 'render_test.html.jinja'
38
28 _TAG = 'test_runner_py' 39 _TAG = 'test_runner_py'
29 40
30 TIMEOUT_ANNOTATIONS = [ 41 TIMEOUT_ANNOTATIONS = [
31 ('Manual', 10 * 60 * 60), 42 ('Manual', 10 * 60 * 60),
32 ('IntegrationTest', 30 * 60), 43 ('IntegrationTest', 30 * 60),
33 ('External', 10 * 60), 44 ('External', 10 * 60),
34 ('EnormousTest', 10 * 60), 45 ('EnormousTest', 10 * 60),
35 ('LargeTest', 5 * 60), 46 ('LargeTest', 5 * 60),
36 ('MediumTest', 3 * 60), 47 ('MediumTest', 3 * 60),
37 ('SmallTest', 1 * 60), 48 ('SmallTest', 1 * 60),
38 ] 49 ]
39 50
40 LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v'] 51 LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v']
41 52
53 FEATURE_ANNOTATION = 'Feature'
54 RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest'
55
56 # This needs to be kept in sync with formatting in |RenderUtils.imageName|
57 RE_RENDER_IMAGE_NAME = re.compile(
58 r'(?P<test_class>\w+)\.'
59 r'(?P<description>\w+)\.'
60 r'(?P<device_model>\w+)\.'
61 r'(?P<orientation>port|land)\.png')
42 62
43 # TODO(jbudorick): Make this private once the instrumentation test_runner is 63 # TODO(jbudorick): Make this private once the instrumentation test_runner is
44 # deprecated. 64 # deprecated.
45 def DidPackageCrashOnDevice(package_name, device): 65 def DidPackageCrashOnDevice(package_name, device):
46 # Dismiss any error dialogs. Limit the number in case we have an error 66 # Dismiss any error dialogs. Limit the number in case we have an error
47 # loop or we are failing to dismiss. 67 # loop or we are failing to dismiss.
48 try: 68 try:
49 for _ in xrange(10): 69 for _ in xrange(10):
50 package = device.DismissCrashDialogIfNeeded() 70 package = device.DismissCrashDialogIfNeeded()
51 if not package: 71 if not package:
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 return tests 259 return tests
240 260
241 #override 261 #override
242 def _GetUniqueTestName(self, test): 262 def _GetUniqueTestName(self, test):
243 return instrumentation_test_instance.GetUniqueTestName(test) 263 return instrumentation_test_instance.GetUniqueTestName(test)
244 264
245 #override 265 #override
246 def _RunTest(self, device, test): 266 def _RunTest(self, device, test):
247 extras = {} 267 extras = {}
248 268
249 flags = None 269 flags_to_add = []
270 flags_to_remove = []
250 test_timeout_scale = None 271 test_timeout_scale = None
251 if self._test_instance.coverage_directory: 272 if self._test_instance.coverage_directory:
252 coverage_basename = '%s.ec' % ('%s_group' % test[0]['method'] 273 coverage_basename = '%s.ec' % ('%s_group' % test[0]['method']
253 if isinstance(test, list) else test['method']) 274 if isinstance(test, list) else test['method'])
254 extras['coverage'] = 'true' 275 extras['coverage'] = 'true'
255 coverage_directory = os.path.join( 276 coverage_directory = os.path.join(
256 device.GetExternalStoragePath(), 'chrome', 'test', 'coverage') 277 device.GetExternalStoragePath(), 'chrome', 'test', 'coverage')
257 coverage_device_file = os.path.join( 278 coverage_device_file = os.path.join(
258 coverage_directory, coverage_basename) 279 coverage_directory, coverage_basename)
259 extras['coverageFile'] = coverage_device_file 280 extras['coverageFile'] = coverage_device_file
(...skipping 26 matching lines...) Expand all
286 test_display_name = self._GetUniqueTestName(test) 307 test_display_name = self._GetUniqueTestName(test)
287 if test['is_junit4']: 308 if test['is_junit4']:
288 target = '%s/%s' % ( 309 target = '%s/%s' % (
289 self._test_instance.test_package, 310 self._test_instance.test_package,
290 self._test_instance.test_runner_junit4) 311 self._test_instance.test_runner_junit4)
291 else: 312 else:
292 target = '%s/%s' % ( 313 target = '%s/%s' % (
293 self._test_instance.test_package, self._test_instance.test_runner) 314 self._test_instance.test_package, self._test_instance.test_runner)
294 extras['class'] = test_name 315 extras['class'] = test_name
295 if 'flags' in test: 316 if 'flags' in test:
296 flags = test['flags'] 317 flags_to_add.extend(test['flags'].add)
318 flags_to_remove.extend(test['flags'].remove)
297 timeout = self._GetTimeoutFromAnnotations( 319 timeout = self._GetTimeoutFromAnnotations(
298 test['annotations'], test_display_name) 320 test['annotations'], test_display_name)
299 321
300 test_timeout_scale = self._GetTimeoutScaleFromAnnotations( 322 test_timeout_scale = self._GetTimeoutScaleFromAnnotations(
301 test['annotations']) 323 test['annotations'])
302 if test_timeout_scale and test_timeout_scale != 1: 324 if test_timeout_scale and test_timeout_scale != 1:
303 valgrind_tools.SetChromeTimeoutScale( 325 valgrind_tools.SetChromeTimeoutScale(
304 device, test_timeout_scale * self._test_instance.timeout_scale) 326 device, test_timeout_scale * self._test_instance.timeout_scale)
305 327
306 logging.info('preparing to run %s: %s', test_display_name, test) 328 logging.info('preparing to run %s: %s', test_display_name, test)
307 329
308 if flags: 330 render_tests_device_output_dir = None
331 if _IsRenderTest(test):
332 # TODO(mikecase): Add DeviceTempDirectory class and use that instead.
jbudorick 2017/05/10 15:10:05 This would be a pretty nice addition to device_tem
333 render_tests_device_output_dir = posixpath.join(
334 device.GetExternalStoragePath(),
335 'render_test_output_dir')
336 flags_to_add.append('--render-test-output-dir=%s' %
337 render_tests_device_output_dir)
338
339 if flags_to_add or flags_to_remove:
309 self._CreateFlagChangerIfNeeded(device) 340 self._CreateFlagChangerIfNeeded(device)
310 self._flag_changers[str(device)].PushFlags( 341 self._flag_changers[str(device)].PushFlags(
311 add=flags.add, remove=flags.remove) 342 add=flags_to_add, remove=flags_to_remove)
312 343
313 try: 344 try:
314 device.RunShellCommand( 345 device.RunShellCommand(
315 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name], 346 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name],
316 check_return=True) 347 check_return=True)
317 time_ms = lambda: int(time.time() * 1e3) 348 time_ms = lambda: int(time.time() * 1e3)
318 start_ms = time_ms() 349 start_ms = time_ms()
319 350
320 stream_name = 'logcat_%s_%s_%s' % ( 351 stream_name = 'logcat_%s_%s_%s' % (
321 test_name.replace('#', '.'), 352 test_name.replace('#', '.'),
322 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), 353 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
323 device.serial) 354 device.serial)
324 logmon = logdog_logcat_monitor.LogdogLogcatMonitor( 355 logmon = logdog_logcat_monitor.LogdogLogcatMonitor(
325 device.adb, stream_name, filter_specs=LOGCAT_FILTERS) 356 device.adb, stream_name, filter_specs=LOGCAT_FILTERS)
326 357
327 with contextlib_ext.Optional( 358 with contextlib_ext.Optional(
328 logmon, self._test_instance.should_save_logcat): 359 logmon, self._test_instance.should_save_logcat):
329 with contextlib_ext.Optional( 360 with contextlib_ext.Optional(
330 trace_event.trace(test_name), 361 trace_event.trace(test_name),
331 self._env.trace_output): 362 self._env.trace_output):
332 output = device.StartInstrumentation( 363 output = device.StartInstrumentation(
333 target, raw=True, extras=extras, timeout=timeout, retries=0) 364 target, raw=True, extras=extras, timeout=timeout, retries=0)
334 logcat_url = logmon.GetLogcatURL() 365 logcat_url = logmon.GetLogcatURL()
335 finally: 366 finally:
336 device.RunShellCommand( 367 device.RunShellCommand(
337 ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name], 368 ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name],
338 check_return=True) 369 check_return=True)
339 duration_ms = time_ms() - start_ms 370 duration_ms = time_ms() - start_ms
340 if flags: 371 if flags_to_add or flags_to_remove:
341 self._flag_changers[str(device)].Restore() 372 self._flag_changers[str(device)].Restore()
342 if test_timeout_scale: 373 if test_timeout_scale:
343 valgrind_tools.SetChromeTimeoutScale( 374 valgrind_tools.SetChromeTimeoutScale(
344 device, self._test_instance.timeout_scale) 375 device, self._test_instance.timeout_scale)
345 376
346 # TODO(jbudorick): Make instrumentation tests output a JSON so this 377 # TODO(jbudorick): Make instrumentation tests output a JSON so this
347 # doesn't have to parse the output. 378 # doesn't have to parse the output.
348 result_code, result_bundle, statuses = ( 379 result_code, result_bundle, statuses = (
349 self._test_instance.ParseAmInstrumentRawOutput(output)) 380 self._test_instance.ParseAmInstrumentRawOutput(output))
350 results = self._test_instance.GenerateTestResults( 381 results = self._test_instance.GenerateTestResults(
351 result_code, result_bundle, statuses, start_ms, duration_ms) 382 result_code, result_bundle, statuses, start_ms, duration_ms)
352 for result in results: 383 for result in results:
353 if logcat_url: 384 if logcat_url:
354 result.SetLink('logcat', logcat_url) 385 result.SetLink('logcat', logcat_url)
355 386
387 if _IsRenderTest(test):
388 # Render tests do not cause test failure by default. So we have to check
389 # to see if any failure images were generated even if the test does not
390 # fail.
391 try:
392 self._ProcessRenderTestResults(
393 device, render_tests_device_output_dir, results)
394 finally:
395 device.RemovePath(render_tests_device_output_dir,
396 recursive=True, force=True)
397
356 # Update the result name if the test used flags. 398 # Update the result name if the test used flags.
357 if flags: 399 if flags_to_add or flags_to_remove:
358 for r in results: 400 for r in results:
359 if r.GetName() == test_name: 401 if r.GetName() == test_name:
360 r.SetName(test_display_name) 402 r.SetName(test_display_name)
361 403
362 # Add UNKNOWN results for any missing tests. 404 # Add UNKNOWN results for any missing tests.
363 iterable_test = test if isinstance(test, list) else [test] 405 iterable_test = test if isinstance(test, list) else [test]
364 test_names = set(self._GetUniqueTestName(t) for t in iterable_test) 406 test_names = set(self._GetUniqueTestName(t) for t in iterable_test)
365 results_names = set(r.GetName() for r in results) 407 results_names = set(r.GetName() for r in results)
366 results.extend( 408 results.extend(
367 base_test_result.BaseTestResult(u, base_test_result.ResultType.UNKNOWN) 409 base_test_result.BaseTestResult(u, base_test_result.ResultType.UNKNOWN)
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
435 include_stack_symbols=False, 477 include_stack_symbols=False,
436 wipe_tombstones=True) 478 wipe_tombstones=True)
437 stream_name = 'tombstones_%s_%s' % ( 479 stream_name = 'tombstones_%s_%s' % (
438 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), 480 time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
439 device.serial) 481 device.serial)
440 tombstones_url = logdog_helper.text( 482 tombstones_url = logdog_helper.text(
441 stream_name, '\n'.join(resolved_tombstones)) 483 stream_name, '\n'.join(resolved_tombstones))
442 result.SetLink('tombstones', tombstones_url) 484 result.SetLink('tombstones', tombstones_url)
443 return results, None 485 return results, None
444 486
487 def _ProcessRenderTestResults(
488 self, device, render_tests_device_output_dir, results):
489 # Will archive test images if we are given a GS bucket to store the results
490 # in and are given a results file to output the links to.
491 if not bool(self._test_instance.gs_results_bucket):
492 return
493
494 failure_images_device_dir = posixpath.join(
495 render_tests_device_output_dir, 'failures')
496
497 if not device.FileExists(failure_images_device_dir):
498 return
499
500 render_tests_bucket = (
501 self._test_instance.gs_results_bucket + '/render_tests')
502
503 diff_images_device_dir = posixpath.join(
504 render_tests_device_output_dir, 'diffs')
505
506 golden_images_device_dir = posixpath.join(
507 render_tests_device_output_dir, 'goldens')
508
509 with tempfile_ext.NamedTemporaryDirectory() as temp_dir:
510 device.PullFile(failure_images_device_dir, temp_dir)
511
512 if device.FileExists(diff_images_device_dir):
513 device.PullFile(diff_images_device_dir, temp_dir)
514 else:
515 logging.error('Diff images not found on device.')
516
517 if device.FileExists(golden_images_device_dir):
518 device.PullFile(golden_images_device_dir, temp_dir)
519 else:
520 logging.error('Golden images not found on device.')
521
522 for failure_filename in os.listdir(os.path.join(temp_dir, 'failures')):
523
524 m = RE_RENDER_IMAGE_NAME.match(failure_filename)
525 if not m:
526 logging.warning('Unexpected file in render test failures: %s',
527 failure_filename)
528 continue
529
530 failure_filepath = os.path.join(temp_dir, 'failures', failure_filename)
531 failure_link = google_storage_helper.upload(
532 google_storage_helper.unique_name(
533 'failure_%s' % failure_filename, device=device),
534 failure_filepath,
535 bucket=render_tests_bucket)
536
537 golden_filepath = os.path.join(temp_dir, 'goldens', failure_filename)
538 if os.path.exists(golden_filepath):
539 golden_link = google_storage_helper.upload(
540 google_storage_helper.unique_name(
541 'golden_%s' % failure_filename, device=device),
542 golden_filepath,
543 bucket=render_tests_bucket)
544 else:
545 golden_link = ''
546
547 diff_filepath = os.path.join(temp_dir, 'diffs', failure_filename)
548 if os.path.exists(diff_filepath):
549 diff_link = google_storage_helper.upload(
550 google_storage_helper.unique_name(
551 'diff_%s' % failure_filename, device=device),
552 diff_filepath,
553 bucket=render_tests_bucket)
554 else:
555 diff_link = ''
556
557 with tempfile.NamedTemporaryFile(suffix='.html') as temp_html:
558 jinja2_env = jinja2.Environment(
559 loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR),
560 trim_blocks=True)
561 template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME)
562 # pylint: disable=no-member
563 processed_template_output = template.render(
564 failure_link=failure_link,
565 golden_link=golden_link,
566 diff_link=diff_link)
567
568 temp_html.write(processed_template_output)
569 temp_html.flush()
570 html_results_link = google_storage_helper.upload(
571 google_storage_helper.unique_name('render_html', device=device),
572 temp_html.name,
573 bucket=render_tests_bucket,
574 content_type='text/html')
575 for result in results:
576 result.SetLink(failure_filename, html_results_link)
577
445 #override 578 #override
446 def _ShouldRetry(self, test): 579 def _ShouldRetry(self, test):
447 if 'RetryOnFailure' in test.get('annotations', {}): 580 if 'RetryOnFailure' in test.get('annotations', {}):
448 return True 581 return True
449 582
450 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been 583 # TODO(jbudorick): Remove this log message once @RetryOnFailure has been
451 # enabled for a while. See crbug.com/619055 for more details. 584 # enabled for a while. See crbug.com/619055 for more details.
452 logging.error('Default retries are being phased out. crbug.com/619055') 585 logging.error('Default retries are being phased out. crbug.com/619055')
453 return False 586 return False
454 587
(...skipping 15 matching lines...) Expand all
470 if k in annotations: 603 if k in annotations:
471 timeout = v 604 timeout = v
472 break 605 break
473 else: 606 else:
474 logging.warning('Using default 1 minute timeout for %s', test_name) 607 logging.warning('Using default 1 minute timeout for %s', test_name)
475 timeout = 60 608 timeout = 60
476 609
477 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) 610 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations)
478 611
479 return timeout 612 return timeout
613
614 def _IsRenderTest(test):
615 """Determines if a test or list of tests has a RenderTest amongst them."""
616 if not isinstance(test, list):
617 test = [test]
618 return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get(
619 FEATURE_ANNOTATION, ())] for t in test)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698