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..42e4ec9815786d68b3423a129c132516ab342aad 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,7 @@ import logging |
| import os |
| import posixpath |
| import re |
| +import tempfile |
| import time |
| from devil.android import device_errors |
| @@ -39,6 +40,31 @@ TIMEOUT_ANNOTATIONS = [ |
| LOGCAT_FILTERS = ['*:e', 'chromium:v', 'cr_*:v'] |
| +FEATURE_ANNOTATION = 'Feature' |
| +RENDER_TEST_FEATURE_ANNOTATION = 'RenderTest' |
| + |
| +RE_RENDER_IMAGE_NAME = re.compile( |
|
PEConn
2017/05/09 08:52:39
Could you please put a comment here pointing to Re
jbudorick
2017/05/09 14:33:26
Might be good to add a few tests on both sides as
mikecase (-- gone --)
2017/05/10 01:05:00
Done
|
| + r'(?P<test_class>\w+)\.' |
| + r'(?P<description>\w+)\.' |
| + r'(?P<device_model>\w+)\.' |
| + r'(?P<orientation>port|land)\.png') |
| + |
| +RENDER_TESTS_HTML_TEMPLATE = ''' |
|
PEConn
2017/05/09 08:52:39
Could you move this into a separate file (or Jinja
mikecase (-- gone --)
2017/05/10 01:05:00
Done. Moved into own file so it will be easier to
|
| + <html> |
| + <table> |
| + <tr> |
| + <th>Failure</th> |
| + <th>Golden</th> |
| + <th>Diff</th> |
| + </tr> |
| + <tr> |
| + <td><img src="%s"/></td> |
|
jbudorick
2017/05/09 14:33:26
Are these ok with an empty URL? (It's been a while
mikecase (-- gone --)
2017/05/10 01:05:00
Yeah, I doubled checked, it just doesnt load anyth
|
| + <td><img src="%s"/></td> |
| + <td><img src="%s"/></td> |
| + </tr> |
| + </table> |
| + </html> |
| + ''' |
| # TODO(jbudorick): Make this private once the instrumentation test_runner is |
| # deprecated. |
| @@ -246,7 +272,8 @@ class LocalDeviceInstrumentationTestRun( |
| def _RunTest(self, device, test): |
| extras = {} |
| - flags = None |
| + flags_to_add = [] |
|
jbudorick
2017/05/09 14:33:26
This change was in your other CL?
mikecase (-- gone --)
2017/05/10 01:05:00
Yeah, have to make the same change here as well. W
|
| + flags_to_remove = [] |
| test_timeout_scale = None |
| if self._test_instance.coverage_directory: |
| coverage_basename = '%s.ec' % ('%s_group' % test[0]['method'] |
| @@ -293,7 +320,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 +333,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. |
| + 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 +374,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 +390,15 @@ 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. |
| + self._ProcessRenderTestResults( |
| + device, render_tests_device_output_dir, results) |
| + |
| # 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 +486,90 @@ 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: |
| + try: |
| + 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.') |
| + finally: |
| + device.RemovePath(render_tests_device_output_dir, recursive=True) |
|
jbudorick
2017/05/09 14:33:26
I think this should be the responsibility of _RunT
mikecase (-- gone --)
2017/05/10 01:05:00
Done
|
| + |
| + 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_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( |
| + 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( |
| + failure_filename, device=device), |
| + diff_filepath, |
| + bucket=render_tests_bucket) |
| + else: |
| + diff_link = '' |
| + |
| + with tempfile.NamedTemporaryFile(suffix='.html') as temp_html: |
| + temp_html.write(RENDER_TESTS_HTML_TEMPLATE % |
| + (failure_link, golden_link, diff_link)) |
| + 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 +605,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) |