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

Side by Side Diff: build/android/pylib/instrumentation/test_runner.py

Issue 558883003: [Android] Allow instrumentation test skipping. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: add unit tests Created 6 years, 3 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 (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 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 """Class for running instrumentation tests on a single device.""" 5 """Class for running instrumentation tests on a single device."""
6 6
7 import logging 7 import logging
8 import os 8 import os
9 import re 9 import re
10 import sys 10 import sys
11 import time 11 import time
12 12
13 from pylib import android_commands
14 from pylib import constants 13 from pylib import constants
15 from pylib import flag_changer 14 from pylib import flag_changer
16 from pylib import valgrind_tools 15 from pylib import valgrind_tools
17 from pylib.base import base_test_result 16 from pylib.base import base_test_result
18 from pylib.base import base_test_runner 17 from pylib.base import base_test_runner
19 from pylib.device import device_errors 18 from pylib.device import device_errors
20 from pylib.instrumentation import json_perf_parser 19 from pylib.instrumentation import json_perf_parser
21 from pylib.instrumentation import test_result 20 from pylib.instrumentation import test_result
22 21
23 sys.path.append(os.path.join(sys.path[0], 22 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
24 os.pardir, os.pardir, 'build', 'util', 'lib',
25 'common')) 23 'common'))
26 import perf_tests_results_helper # pylint: disable=F0401 24 import perf_tests_results_helper # pylint: disable=F0401
27 25
28 26
29 _PERF_TEST_ANNOTATION = 'PerfTest' 27 _PERF_TEST_ANNOTATION = 'PerfTest'
30 28
31 29
32 def _GetDataFilesForTestSuite(suite_basename): 30 def _GetDataFilesForTestSuite(suite_basename):
33 """Returns a list of data files/dirs needed by the test suite. 31 """Returns a list of data files/dirs needed by the test suite.
34 32
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
212 210
213 Args: 211 Args:
214 test: The name of the test to be run. 212 test: The name of the test to be run.
215 """ 213 """
216 if not self._IsPerfTest(test): 214 if not self._IsPerfTest(test):
217 return 215 return
218 self.device.old_interface.Adb().SendCommand( 216 self.device.old_interface.Adb().SendCommand(
219 'shell rm ' + TestRunner._DEVICE_PERF_OUTPUT_SEARCH_PREFIX) 217 'shell rm ' + TestRunner._DEVICE_PERF_OUTPUT_SEARCH_PREFIX)
220 self.device.old_interface.StartMonitoringLogcat() 218 self.device.old_interface.StartMonitoringLogcat()
221 219
222 def TestTeardown(self, test, raw_result): 220 def TestTeardown(self, test, result):
223 """Cleans up the test harness after running a particular test. 221 """Cleans up the test harness after running a particular test.
224 222
225 Depending on the options of this TestRunner this might handle performance 223 Depending on the options of this TestRunner this might handle performance
226 tracking. This method will only be called if the test passed. 224 tracking. This method will only be called if the test passed.
227 225
228 Args: 226 Args:
229 test: The name of the test that was just run. 227 test: The name of the test that was just run.
230 raw_result: result for this test. 228 result: result for this test.
231 """ 229 """
232 230
233 self.tool.CleanUpEnvironment() 231 self.tool.CleanUpEnvironment()
234 232
235 # The logic below relies on the test passing. 233 # The logic below relies on the test passing.
236 if not raw_result or raw_result.GetStatusCode(): 234 if not result or not result.DidRunPass():
237 return 235 return
238 236
239 self.TearDownPerfMonitoring(test) 237 self.TearDownPerfMonitoring(test)
240 238
241 if self.coverage_dir: 239 if self.coverage_dir:
242 self.device.PullFile( 240 self.device.PullFile(
243 self.coverage_device_file, self.coverage_host_file) 241 self.coverage_device_file, self.coverage_host_file)
244 self.device.RunShellCommand( 242 self.device.RunShellCommand(
245 'rm -f %s' % self.coverage_device_file) 243 'rm -f %s' % self.coverage_device_file)
246 244
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
344 def RunInstrumentationTest(self, test, test_package, instr_args, timeout): 342 def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
345 """Runs a single instrumentation test. 343 """Runs a single instrumentation test.
346 344
347 Args: 345 Args:
348 test: Test class/method. 346 test: Test class/method.
349 test_package: Package name of test apk. 347 test_package: Package name of test apk.
350 instr_args: Extra key/value to pass to am instrument. 348 instr_args: Extra key/value to pass to am instrument.
351 timeout: Timeout time in seconds. 349 timeout: Timeout time in seconds.
352 350
353 Returns: 351 Returns:
354 An instance of am_instrument_parser.TestResult object. 352 An instance of InstrumentationTestResult
355 """ 353 """
354 # Build the 'am instrument' command
356 instrumentation_path = ( 355 instrumentation_path = (
357 '%s/%s' % (test_package, self.options.test_runner)) 356 '%s/%s' % (test_package, self.options.test_runner))
358 args_with_filter = dict(instr_args)
359 args_with_filter['class'] = test
360 logging.info(args_with_filter)
361 (raw_results, _) = self.device.old_interface.Adb().StartInstrumentation(
362 instrumentation_path=instrumentation_path,
363 instrumentation_args=args_with_filter,
364 timeout_time=timeout)
365 assert len(raw_results) == 1
366 return raw_results[0]
367 357
358 cmd = ['am', 'instrument', '-r']
359 for k, v in instr_args.iteritems():
360 cmd.extend(['-e', k, "'%s'" % v])
361 cmd.extend(['-e', 'class', "'%s'" % test])
362 cmd.extend(['-w', instrumentation_path])
368 363
369 def _RunTest(self, test, timeout): 364 time_ms = lambda: int(time.time() * 1000)
365
366 # Run the test.
367 start_ms = time_ms()
370 try: 368 try:
371 return self.RunInstrumentationTest( 369 instr_output = self.device.RunShellCommand(
372 test, self.test_pkg.GetPackageName(), 370 cmd, timeout=timeout, retries=0)
373 self._GetInstrumentationArgs(), timeout) 371 except device_errors.CommandTimeoutError:
374 except (device_errors.CommandTimeoutError, 372 return test_result.InstrumentationTestResult(
375 # TODO(jbudorick) Remove this once the underlying implementations 373 test, base_test_result.ResultType.TIMEOUT, start_ms,
376 # for the above are switched or wrapped. 374 time_ms() - start_ms)
377 android_commands.errors.WaitForResponseTimedOutError): 375 duration_ms = time_ms() - start_ms
378 logging.info('Ran the test with timeout of %ds.' % timeout)
379 raise
380 376
381 #override 377 # Parse the test output
382 def RunTest(self, test): 378 _, _, statuses = self._ParseAmInstrumentRawOutput(instr_output)
383 raw_result = None 379 return self._GenerateTestResult(test, statuses, start_ms, duration_ms)
384 start_date_ms = None 380
385 results = base_test_result.TestRunResults() 381 @staticmethod
386 timeout = (self._GetIndividualTestTimeoutSecs(test) * 382 def _ParseAmInstrumentRawOutput(raw_output):
387 self._GetIndividualTestTimeoutScale(test) * 383 """Parses the output of an |am instrument -r| call.
388 self.tool.GetTimeoutScale()) 384
389 try: 385 Args:
390 self.TestSetup(test) 386 raw_output: the output of an |am instrument -r| call as a list of lines
391 start_date_ms = int(time.time()) * 1000 387 Returns:
392 raw_result = self._RunTest(test, timeout) 388 A 3-tuple containing:
393 duration_ms = int(time.time()) * 1000 - start_date_ms 389 - the instrumentation code as an integer
394 status_code = raw_result.GetStatusCode() 390 - the instrumentation result as a list of lines
395 if status_code: 391 - the instrumentation statuses received as a list of 2-tuples
396 if self.options.screenshot_failures: 392 containing:
397 self._TakeScreenshot(test) 393 - the status code as an integer
398 log = raw_result.GetFailureReason() 394 - the bundle dump as a dict mapping string keys to string values
399 if not log: 395 """
400 log = 'No information.' 396 INSTR_STATUS = 'INSTRUMENTATION_STATUS: '
397 INSTR_STATUS_CODE = 'INSTRUMENTATION_STATUS_CODE: '
398 INSTR_RESULT = 'INSTRUMENTATION_RESULT: '
399 INSTR_CODE = 'INSTRUMENTATION_CODE: '
400
401 last = None
402 instr_code = None
403 instr_result = []
404 instr_statuses = []
405 bundle = {}
406 for line in raw_output:
407 if line.startswith(INSTR_STATUS):
408 instr_var = line[len(INSTR_STATUS):]
409 if '=' in instr_var:
410 k, v = instr_var.split('=', 1)
411 bundle[k] = [v]
412 last = INSTR_STATUS
413 last_key = k
414 else:
415 logging.debug('Unknown "%s" line: %s' % (INSTR_STATUS, line))
416
417 elif line.startswith(INSTR_STATUS_CODE):
418 instr_status = line[len(INSTR_STATUS_CODE):]
419 for k, v in bundle.iteritems():
420 bundle[k] = '\n'.join(v)
klundberg 2014/09/18 15:35:05 Minor thing but it seems a bit weird to me to add
jbudorick 2014/09/18 16:13:01 Done.
421 instr_statuses.append((int(instr_status), bundle))
422 bundle = {}
423 last = INSTR_STATUS_CODE
424
425 elif line.startswith(INSTR_RESULT):
426 instr_result.append(line[len(INSTR_RESULT):])
427 last = INSTR_RESULT
428
429 elif line.startswith(INSTR_CODE):
430 instr_code = int(line[len(INSTR_CODE):])
431 last = INSTR_CODE
432
433 elif last == INSTR_STATUS:
434 bundle[last_key].append(line)
435
436 elif last == INSTR_RESULT:
437 instr_result.append(line)
438
439 return (instr_code, instr_result, instr_statuses)
440
441 def _GenerateTestResult(self, test, instr_statuses, start_ms, duration_ms):
442 """Generate the result of |test| from |instr_statuses|.
443
444 Args:
445 instr_statuses: A list of 2-tuples containing:
446 - the status code as an integer
447 - the bundle dump as a dict mapping string keys to string values
448 Note that this is the same as the third item in the 3-tuple returned by
449 |_ParseAmInstrumentRawOutput|.
450 start_ms: The start time of the test in milliseconds.
451 duration_ms: The duration of the test in milliseconds.
452 Returns:
453 An InstrumentationTestResult object.
454 """
455 INSTR_STATUS_CODE_START = 1
456 INSTR_STATUS_CODE_OK = 0
457 INSTR_STATUS_CODE_ERROR = -1
458 INSTR_STATUS_CODE_FAIL = -2
459
460 log = ''
461 result_type = base_test_result.ResultType.UNKNOWN
462
463 for status_code, bundle in instr_statuses:
464 if status_code == INSTR_STATUS_CODE_START:
465 pass
466 elif status_code == INSTR_STATUS_CODE_OK:
467 if ((test == '%s#%s' % (bundle.get('class', ''),
468 bundle.get('test', ''))) and
469 result_type == base_test_result.ResultType.UNKNOWN):
470 result_type = base_test_result.ResultType.PASS
471 elif bundle.get('test_skipped', '').lower() in ('true', '1', 'yes'):
472 result_type = base_test_result.ResultType.SKIP
473 logging.info('Skipped ' + test)
474 else:
475 if status_code not in (INSTR_STATUS_CODE_ERROR,
476 INSTR_STATUS_CODE_FAIL):
477 logging.info('Unrecognized status code %d. Handling as an error.',
478 status_code)
401 result_type = base_test_result.ResultType.FAIL 479 result_type = base_test_result.ResultType.FAIL
480 if 'stack' in bundle:
481 log = bundle['stack']
402 # Dismiss any error dialogs. Limit the number in case we have an error 482 # Dismiss any error dialogs. Limit the number in case we have an error
403 # loop or we are failing to dismiss. 483 # loop or we are failing to dismiss.
404 for _ in xrange(10): 484 for _ in xrange(10):
405 package = self.device.old_interface.DismissCrashDialogIfNeeded() 485 package = self.device.old_interface.DismissCrashDialogIfNeeded()
406 if not package: 486 if not package:
407 break 487 break
408 # Assume test package convention of ".test" suffix 488 # Assume test package convention of ".test" suffix
409 if package in self.test_pkg.GetPackageName(): 489 if package in self.test_pkg.GetPackageName():
410 result_type = base_test_result.ResultType.CRASH 490 result_type = base_test_result.ResultType.CRASH
411 break 491 break
412 result = test_result.InstrumentationTestResult( 492
413 test, result_type, start_date_ms, duration_ms, log=log) 493 return test_result.InstrumentationTestResult(
414 else: 494 test, result_type, start_ms, duration_ms, log=log)
415 result = test_result.InstrumentationTestResult( 495
416 test, base_test_result.ResultType.PASS, start_date_ms, duration_ms) 496 #override
497 def RunTest(self, test):
498 results = base_test_result.TestRunResults()
499 timeout = (self._GetIndividualTestTimeoutSecs(test) *
500 self._GetIndividualTestTimeoutScale(test) *
501 self.tool.GetTimeoutScale())
502 try:
503 self.TestSetup(test)
504 result = self.RunInstrumentationTest(
505 test, self.test_pkg.GetPackageName(), self._GetInstrumentationArgs(),
506 timeout)
417 results.AddResult(result) 507 results.AddResult(result)
418 # Catch exceptions thrown by StartInstrumentation().
419 # See ../../third_party/android/testrunner/adb_interface.py
420 except (device_errors.CommandTimeoutError, 508 except (device_errors.CommandTimeoutError,
421 device_errors.DeviceUnreachableError, 509 device_errors.DeviceUnreachableError) as e:
422 # TODO(jbudorick) Remove these once the underlying implementations
423 # for the above are switched or wrapped.
424 android_commands.errors.WaitForResponseTimedOutError,
425 android_commands.errors.DeviceUnresponsiveError,
426 android_commands.errors.InstrumentationError), e:
427 if start_date_ms:
428 duration_ms = int(time.time()) * 1000 - start_date_ms
429 else:
430 start_date_ms = int(time.time()) * 1000
431 duration_ms = 0
432 message = str(e) 510 message = str(e)
433 if not message: 511 if not message:
434 message = 'No information.' 512 message = 'No information.'
435 results.AddResult(test_result.InstrumentationTestResult( 513 results.AddResult(test_result.InstrumentationTestResult(
436 test, base_test_result.ResultType.CRASH, start_date_ms, duration_ms, 514 test, base_test_result.ResultType.CRASH, int(time.time() * 1000), 0,
437 log=message)) 515 log=message))
438 raw_result = None 516 self.TestTeardown(test, results)
439 self.TestTeardown(test, raw_result)
440 return (results, None if results.DidRunPass() else test) 517 return (results, None if results.DidRunPass() else test)
OLDNEW
« no previous file with comments | « build/android/pylib/base/base_test_result.py ('k') | build/android/pylib/instrumentation/test_runner_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698