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

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

Issue 2701473003: Add failure screenshots and render test images to results detail. (Closed)
Patch Set: Add failure screenshots and render test images to results detail. Created 3 years, 10 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 shutil
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.utils import reraiser_thread 15 from devil.utils import reraiser_thread
14 from pylib import valgrind_tools 16 from pylib import valgrind_tools
15 from pylib.android import logdog_logcat_monitor 17 from pylib.android import logdog_logcat_monitor
18 from pylib.constants import host_paths
16 from pylib.base import base_test_result 19 from pylib.base import base_test_result
17 from pylib.instrumentation import instrumentation_test_instance 20 from pylib.instrumentation import instrumentation_test_instance
18 from pylib.local.device import local_device_environment 21 from pylib.local.device import local_device_environment
19 from pylib.local.device import local_device_test_run 22 from pylib.local.device import local_device_test_run
20 from pylib.utils import logdog_helper 23 from pylib.utils import logdog_helper
21 from py_trace_event import trace_event 24 from py_trace_event import trace_event
22 from py_utils import contextlib_ext 25 from py_utils import contextlib_ext
23 import tombstones 26 import tombstones
24 27
25 _TAG = 'test_runner_py' 28 _TAG = 'test_runner_py'
26 29
27 TIMEOUT_ANNOTATIONS = [ 30 TIMEOUT_ANNOTATIONS = [
28 ('Manual', 10 * 60 * 60), 31 ('Manual', 10 * 60 * 60),
29 ('IntegrationTest', 30 * 60), 32 ('IntegrationTest', 30 * 60),
30 ('External', 10 * 60), 33 ('External', 10 * 60),
31 ('EnormousTest', 10 * 60), 34 ('EnormousTest', 10 * 60),
32 ('LargeTest', 5 * 60), 35 ('LargeTest', 5 * 60),
33 ('MediumTest', 3 * 60), 36 ('MediumTest', 3 * 60),
34 ('SmallTest', 1 * 60), 37 ('SmallTest', 1 * 60),
35 ] 38 ]
36 39
40 _RE_RENDER_IMAGE_NAME = re.compile(
41 r'(?P<test_class>\w+)\.'
42 r'(?P<description>\w+)\.'
43 r'(?P<device_model>\w+)\.'
44 r'(?P<orientation>port|land)\.png')
45
46 RENDER_TESTS_RESULTS_DIR = {
47 'chrome_public_test_apk': 'chrome/test/data/android/render_tests'
48 }
37 49
38 # TODO(jbudorick): Make this private once the instrumentation test_runner is 50 # TODO(jbudorick): Make this private once the instrumentation test_runner is
39 # deprecated. 51 # deprecated.
40 def DidPackageCrashOnDevice(package_name, device): 52 def DidPackageCrashOnDevice(package_name, device):
41 # Dismiss any error dialogs. Limit the number in case we have an error 53 # Dismiss any error dialogs. Limit the number in case we have an error
42 # loop or we are failing to dismiss. 54 # loop or we are failing to dismiss.
43 try: 55 try:
44 for _ in xrange(10): 56 for _ in xrange(10):
45 package = device.DismissCrashDialogIfNeeded() 57 package = device.DismissCrashDialogIfNeeded()
46 if not package: 58 if not package:
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after
253 self._flag_changers[str(device)].PushFlags( 265 self._flag_changers[str(device)].PushFlags(
254 add=flags.add, remove=flags.remove) 266 add=flags.add, remove=flags.remove)
255 267
256 try: 268 try:
257 device.RunShellCommand( 269 device.RunShellCommand(
258 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name], 270 ['log', '-p', 'i', '-t', _TAG, 'START %s' % test_name],
259 check_return=True) 271 check_return=True)
260 time_ms = lambda: int(time.time() * 1e3) 272 time_ms = lambda: int(time.time() * 1e3)
261 start_ms = time_ms() 273 start_ms = time_ms()
262 274
263 stream_name = 'logcat_%s_%s_%s' % ( 275 stream_name = 'logcat_%s_%s_%s' % (
jbudorick 2017/02/21 18:32:39 Can you use unique_name here?
264 test_name.replace('#', '.'), 276 test_name.replace('#', '.'),
265 time.strftime('%Y%m%dT%H%M%S', time.localtime()), 277 time.strftime('%Y%m%dT%H%M%S', time.localtime()),
266 device.serial) 278 device.serial)
267 logmon = logdog_logcat_monitor.LogdogLogcatMonitor( 279 logmon = logdog_logcat_monitor.LogdogLogcatMonitor(
268 device.adb, stream_name) 280 device.adb, stream_name)
269 with contextlib_ext.Optional( 281 with contextlib_ext.Optional(
270 logmon, self._test_instance.should_save_logcat): 282 logmon, self._test_instance.should_save_logcat):
271 with contextlib_ext.Optional( 283 with contextlib_ext.Optional(
272 trace_event.trace(test_name), 284 trace_event.trace(test_name),
273 self._env.trace_output): 285 self._env.trace_output):
(...skipping 14 matching lines...) Expand all
288 # TODO(jbudorick): Make instrumentation tests output a JSON so this 300 # TODO(jbudorick): Make instrumentation tests output a JSON so this
289 # doesn't have to parse the output. 301 # doesn't have to parse the output.
290 result_code, result_bundle, statuses = ( 302 result_code, result_bundle, statuses = (
291 self._test_instance.ParseAmInstrumentRawOutput(output)) 303 self._test_instance.ParseAmInstrumentRawOutput(output))
292 results = self._test_instance.GenerateTestResults( 304 results = self._test_instance.GenerateTestResults(
293 result_code, result_bundle, statuses, start_ms, duration_ms) 305 result_code, result_bundle, statuses, start_ms, duration_ms)
294 for result in results: 306 for result in results:
295 if logcat_url: 307 if logcat_url:
296 result.SetLink('logcat', logcat_url) 308 result.SetLink('logcat', logcat_url)
297 309
310 # Save render test results.
311 if self._test_instance.should_save_images:
jbudorick 2017/02/21 18:37:12 Also, would it make sense to extract this into its
mikecase (-- gone --) 2017/02/23 00:21:26 Done
312 if self._test_instance.suite in RENDER_TESTS_RESULTS_DIR:
313 render_results_dir = RENDER_TESTS_RESULTS_DIR[self._test_instance.suite]
314
315 temp_dir = None
jbudorick 2017/02/21 18:32:40 https://chromium.googlesource.com/external/github.
mikecase (-- gone --) 2017/02/23 00:21:26 Done
316 try:
317 temp_dir = tempfile.mkdtemp()
318
319 failure_images_device_dir = posixpath.join(
320 device.GetExternalStoragePath(),
321 'chromium_tests_root', render_results_dir, 'failures')
322 device.PullFile(failure_images_device_dir, temp_dir)
323 device.RemovePath(failure_images_device_dir)
324
325 for failure_filename in os.listdir(
jbudorick 2017/02/21 18:37:12 Of note, though: process_render_test_results attem
mikecase (-- gone --) 2017/02/23 00:21:26 Added diffing back. Makes this a bit more complex.
326 os.path.join(temp_dir, 'failures')):
327
328 m = _RE_RENDER_IMAGE_NAME.match(failure_filename)
jbudorick 2017/02/21 18:32:39 Do you do anything with m other than check it here
mikecase (-- gone --) 2017/02/23 00:21:26 no
329 if not m:
330 logging.warning('Unexpected file in render test failures, %s',
jbudorick 2017/02/21 18:32:39 nit: "... failures: %s" ^
mikecase (-- gone --) 2017/02/23 00:21:26 Done
331 failure_filename)
332 continue
333
334 failure_filepath = os.path.join(
335 temp_dir, 'failures', failure_filename)
336
337 link = logdog_helper.image(
338 logdog_helper.unique_name(failure_filename, device=device),
339 failure_filepath)
340 for result in results:
341 result.SetLink('%s_FAIL' % failure_filename, link)
jbudorick 2017/02/21 18:32:39 nit: name the link something else. Not immediately
mikecase (-- gone --) 2017/02/23 00:21:26 I changed this since I didnt want three separate l
342
343 golden_filepath = os.path.join(
344 host_paths.DIR_SOURCE_ROOT, render_results_dir,
345 failure_filename)
346 if not os.path.exists(golden_filepath):
347 logging.error('Cannot find golden image for %s', failure_filename)
348 continue
349
350 link = logdog_helper.image(
351 logdog_helper.unique_name(failure_filename, device=device),
352 golden_filepath)
353 for result in results:
354 result.SetLink('%s_GOLDEN' % failure_filename, link)
jbudorick 2017/02/21 18:32:39 nit: same
mikecase (-- gone --) 2017/02/23 00:21:26 See above.
355
356 finally:
357 if temp_dir:
358 shutil.rmtree(temp_dir)
359
298 # Update the result name if the test used flags. 360 # Update the result name if the test used flags.
299 if flags: 361 if flags:
300 for r in results: 362 for r in results:
301 if r.GetName() == test_name: 363 if r.GetName() == test_name:
302 r.SetName(test_display_name) 364 r.SetName(test_display_name)
303 365
304 # Add UNKNOWN results for any missing tests. 366 # Add UNKNOWN results for any missing tests.
305 iterable_test = test if isinstance(test, list) else [test] 367 iterable_test = test if isinstance(test, list) else [test]
306 test_names = set(self._GetUniqueTestName(t) for t in iterable_test) 368 test_names = set(self._GetUniqueTestName(t) for t in iterable_test)
307 results_names = set(r.GetName() for r in results) 369 results_names = set(r.GetName() for r in results)
(...skipping 11 matching lines...) Expand all
319 # - optionally taking a screenshot 381 # - optionally taking a screenshot
320 # - logging the raw output at INFO level 382 # - logging the raw output at INFO level
321 # - clearing the application state while persisting permissions 383 # - clearing the application state while persisting permissions
322 if any(r.GetType() not in (base_test_result.ResultType.PASS, 384 if any(r.GetType() not in (base_test_result.ResultType.PASS,
323 base_test_result.ResultType.SKIP) 385 base_test_result.ResultType.SKIP)
324 for r in results): 386 for r in results):
325 if self._test_instance.screenshot_dir: 387 if self._test_instance.screenshot_dir:
326 file_name = '%s-%s.png' % ( 388 file_name = '%s-%s.png' % (
327 test_display_name, 389 test_display_name,
328 time.strftime('%Y%m%dT%H%M%S', time.localtime())) 390 time.strftime('%Y%m%dT%H%M%S', time.localtime()))
329 saved_dir = device.TakeScreenshot( 391 screenshot_file = device.TakeScreenshot(
330 os.path.join(self._test_instance.screenshot_dir, file_name)) 392 os.path.join(self._test_instance.screenshot_dir, file_name))
331 logging.info( 393 logging.info(
332 'Saved screenshot for %s to %s.', 394 'Saved screenshot for %s to %s.',
333 test_display_name, saved_dir) 395 test_display_name, screenshot_file)
396 if self._test_instance.should_save_images:
397 link = logdog_helper.image(
398 logdog_helper.unique_name('screenshot', device=device),
399 screenshot_file)
400 for result in results:
401 result.SetLink('failure_screenshot', link)
402
334 logging.info('detected failure in %s. raw output:', test_display_name) 403 logging.info('detected failure in %s. raw output:', test_display_name)
335 for l in output: 404 for l in output:
336 logging.info(' %s', l) 405 logging.info(' %s', l)
337 if (not self._env.skip_clear_data 406 if (not self._env.skip_clear_data
338 and self._test_instance.package_info): 407 and self._test_instance.package_info):
339 permissions = ( 408 permissions = (
340 self._test_instance.apk_under_test.GetPermissions() 409 self._test_instance.apk_under_test.GetPermissions()
341 if self._test_instance.apk_under_test 410 if self._test_instance.apk_under_test
342 else None) 411 else None)
343 device.ClearApplicationState(self._test_instance.package_info.package, 412 device.ClearApplicationState(self._test_instance.package_info.package,
344 permissions=permissions) 413 permissions=permissions)
345
346 else: 414 else:
347 logging.debug('raw output from %s:', test_display_name) 415 logging.debug('raw output from %s:', test_display_name)
348 for l in output: 416 for l in output:
349 logging.debug(' %s', l) 417 logging.debug(' %s', l)
350 if self._test_instance.coverage_directory: 418 if self._test_instance.coverage_directory:
351 device.PullFile(coverage_directory, 419 device.PullFile(coverage_directory,
352 self._test_instance.coverage_directory) 420 self._test_instance.coverage_directory)
353 device.RunShellCommand('rm -f %s' % os.path.join(coverage_directory, 421 device.RunShellCommand('rm -f %s' % os.path.join(coverage_directory,
354 '*')) 422 '*'))
355 if self._test_instance.store_tombstones: 423 if self._test_instance.store_tombstones:
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
399 timeout = v 467 timeout = v
400 break 468 break
401 else: 469 else:
402 logging.warning('Using default 1 minute timeout for %s', test_name) 470 logging.warning('Using default 1 minute timeout for %s', test_name)
403 timeout = 60 471 timeout = 60
404 472
405 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations) 473 timeout *= cls._GetTimeoutScaleFromAnnotations(annotations)
406 474
407 return timeout 475 return timeout
408 476
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698