Chromium Code Reviews| Index: build/android/pylib/local/device/local_device_instrumentation_test_run.py |
| diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py |
| index c95fe6d6e9bf81d10465469fd425d253e45a56a7..45a5ae1ed98d4ad48842e7c1c19bb316fb707b3f 100644 |
| --- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py |
| +++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py |
| @@ -6,6 +6,8 @@ import logging |
| import os |
| import posixpath |
| import re |
| +import sys |
| +import tempfile |
| import time |
| from devil.android import device_errors |
| @@ -15,6 +17,7 @@ from devil.utils import reraiser_thread |
| from pylib import valgrind_tools |
| from pylib.android import logdog_logcat_monitor |
| from pylib.base import base_test_result |
| +from pylib.constants import host_paths |
| from pylib.instrumentation import instrumentation_test_instance |
| from pylib.local.device import local_device_environment |
| from pylib.local.device import local_device_test_run |
| @@ -25,6 +28,14 @@ from py_utils import contextlib_ext |
| from py_utils import tempfile_ext |
| import tombstones |
| +sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party')) |
| +import jinja2 # pylint: disable=import-error |
| + |
| + |
| +_JINJA_TEMPLATE_DIR = os.path.join( |
| + host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'pylib', 'instrumentation') |
| +_JINJA_TEMPLATE_FILENAME = 'render_test.html.jinja' |
| + |
| _TAG = 'test_runner_py' |
| TIMEOUT_ANNOTATIONS = [ |
| @@ -39,6 +50,15 @@ TIMEOUT_ANNOTATIONS = [ |
| LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v'] |
| +FEATURE_ANNOTATION = 'Feature' |
| +RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest' |
| + |
| +# This needs to be kept in sync with formatting in |RenderUtils.imageName| |
| +RE_RENDER_IMAGE_NAME = re.compile( |
| + r'(?P<test_class>\w+)\.' |
| + r'(?P<description>\w+)\.' |
| + r'(?P<device_model>\w+)\.' |
| + r'(?P<orientation>port|land)\.png') |
| # TODO(jbudorick): Make this private once the instrumentation test_runner is |
| # deprecated. |
| @@ -246,7 +266,8 @@ class LocalDeviceInstrumentationTestRun( |
| def _RunTest(self, device, test): |
| extras = {} |
| - flags = None |
| + flags_to_add = [] |
| + flags_to_remove = [] |
| test_timeout_scale = None |
| if self._test_instance.coverage_directory: |
| coverage_basename = '%s.ec' % ('%s_group' % test[0]['method'] |
| @@ -293,7 +314,8 @@ class LocalDeviceInstrumentationTestRun( |
| self._test_instance.test_package, self._test_instance.test_runner) |
| extras['class'] = test_name |
| if 'flags' in test: |
| - flags = test['flags'] |
| + flags_to_add.extend(test['flags'].add) |
| + flags_to_remove.extend(test['flags'].remove) |
| timeout = self._GetTimeoutFromAnnotations( |
| test['annotations'], test_display_name) |
| @@ -305,10 +327,19 @@ class LocalDeviceInstrumentationTestRun( |
| logging.info('preparing to run %s: %s', test_display_name, test) |
| - if flags: |
| + render_tests_device_output_dir = None |
| + if _IsRenderTest(test): |
| + # 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
|
| + render_tests_device_output_dir = posixpath.join( |
| + device.GetExternalStoragePath(), |
| + 'render_test_output_dir') |
| + flags_to_add.append('--render-test-output-dir=%s' % |
| + render_tests_device_output_dir) |
| + |
| + if flags_to_add or flags_to_remove: |
| self._CreateFlagChangerIfNeeded(device) |
| self._flag_changers[str(device)].PushFlags( |
| - add=flags.add, remove=flags.remove) |
| + add=flags_to_add, remove=flags_to_remove) |
| try: |
| device.RunShellCommand( |
| @@ -337,7 +368,7 @@ class LocalDeviceInstrumentationTestRun( |
| ['log', '-p', 'i', '-t', _TAG, 'END %s' % test_name], |
| check_return=True) |
| duration_ms = time_ms() - start_ms |
| - if flags: |
| + if flags_to_add or flags_to_remove: |
| self._flag_changers[str(device)].Restore() |
| if test_timeout_scale: |
| valgrind_tools.SetChromeTimeoutScale( |
| @@ -353,8 +384,19 @@ class LocalDeviceInstrumentationTestRun( |
| if logcat_url: |
| result.SetLink('logcat', logcat_url) |
| + if _IsRenderTest(test): |
| + # Render tests do not cause test failure by default. So we have to check |
| + # to see if any failure images were generated even if the test does not |
| + # fail. |
| + try: |
| + self._ProcessRenderTestResults( |
| + device, render_tests_device_output_dir, results) |
| + finally: |
| + device.RemovePath(render_tests_device_output_dir, |
| + recursive=True, force=True) |
| + |
| # Update the result name if the test used flags. |
| - if flags: |
| + if flags_to_add or flags_to_remove: |
| for r in results: |
| if r.GetName() == test_name: |
| r.SetName(test_display_name) |
| @@ -442,6 +484,97 @@ class LocalDeviceInstrumentationTestRun( |
| result.SetLink('tombstones', tombstones_url) |
| return results, None |
| + def _ProcessRenderTestResults( |
| + self, device, render_tests_device_output_dir, results): |
| + # Will archive test images if we are given a GS bucket to store the results |
| + # in and are given a results file to output the links to. |
| + if not bool(self._test_instance.gs_results_bucket): |
| + return |
| + |
| + failure_images_device_dir = posixpath.join( |
| + render_tests_device_output_dir, 'failures') |
| + |
| + if not device.FileExists(failure_images_device_dir): |
| + return |
| + |
| + render_tests_bucket = ( |
| + self._test_instance.gs_results_bucket + '/render_tests') |
| + |
| + diff_images_device_dir = posixpath.join( |
| + render_tests_device_output_dir, 'diffs') |
| + |
| + golden_images_device_dir = posixpath.join( |
| + render_tests_device_output_dir, 'goldens') |
| + |
| + with tempfile_ext.NamedTemporaryDirectory() as temp_dir: |
| + device.PullFile(failure_images_device_dir, temp_dir) |
| + |
| + if device.FileExists(diff_images_device_dir): |
| + device.PullFile(diff_images_device_dir, temp_dir) |
| + else: |
| + logging.error('Diff images not found on device.') |
| + |
| + if device.FileExists(golden_images_device_dir): |
| + device.PullFile(golden_images_device_dir, temp_dir) |
| + else: |
| + logging.error('Golden images not found on device.') |
| + |
| + for failure_filename in os.listdir(os.path.join(temp_dir, 'failures')): |
| + |
| + m = RE_RENDER_IMAGE_NAME.match(failure_filename) |
| + if not m: |
| + logging.warning('Unexpected file in render test failures: %s', |
| + failure_filename) |
| + continue |
| + |
| + failure_filepath = os.path.join(temp_dir, 'failures', failure_filename) |
| + failure_link = google_storage_helper.upload( |
| + google_storage_helper.unique_name( |
| + 'failure_%s' % failure_filename, device=device), |
| + failure_filepath, |
| + bucket=render_tests_bucket) |
| + |
| + golden_filepath = os.path.join(temp_dir, 'goldens', failure_filename) |
| + if os.path.exists(golden_filepath): |
| + golden_link = google_storage_helper.upload( |
| + google_storage_helper.unique_name( |
| + 'golden_%s' % failure_filename, device=device), |
| + golden_filepath, |
| + bucket=render_tests_bucket) |
| + else: |
| + golden_link = '' |
| + |
| + diff_filepath = os.path.join(temp_dir, 'diffs', failure_filename) |
| + if os.path.exists(diff_filepath): |
| + diff_link = google_storage_helper.upload( |
| + google_storage_helper.unique_name( |
| + 'diff_%s' % failure_filename, device=device), |
| + diff_filepath, |
| + bucket=render_tests_bucket) |
| + else: |
| + diff_link = '' |
| + |
| + with tempfile.NamedTemporaryFile(suffix='.html') as temp_html: |
| + jinja2_env = jinja2.Environment( |
| + loader=jinja2.FileSystemLoader(_JINJA_TEMPLATE_DIR), |
| + trim_blocks=True) |
| + template = jinja2_env.get_template(_JINJA_TEMPLATE_FILENAME) |
| + # pylint: disable=no-member |
| + processed_template_output = template.render( |
| + failure_link=failure_link, |
| + golden_link=golden_link, |
| + diff_link=diff_link) |
| + |
| + temp_html.write(processed_template_output) |
| + temp_html.flush() |
| + html_results_link = google_storage_helper.upload( |
| + google_storage_helper.unique_name('render_html', device=device), |
| + temp_html.name, |
| + bucket=render_tests_bucket, |
| + content_type='text/html') |
| + for result in results: |
| + result.SetLink(failure_filename, html_results_link) |
| + |
| #override |
| def _ShouldRetry(self, test): |
| if 'RetryOnFailure' in test.get('annotations', {}): |
| @@ -477,3 +610,10 @@ class LocalDeviceInstrumentationTestRun( |
| timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) |
| return timeout |
| + |
| +def _IsRenderTest(test): |
| + """Determines if a test or list of tests has a RenderTest amongst them.""" |
| + if not isinstance(test, list): |
| + test = [test] |
| + return any([RENDER_TEST_FEATURE_ANNOTATION in t['annotations'].get( |
| + FEATURE_ANNOTATION, ())] for t in test) |