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

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

Powered by Google App Engine
This is Rietveld 408576698