# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import os import re import tempfile from pylib import pexpect from pylib import ports from pylib.base import base_test_result from pylib.base import base_test_runner from pylib.device import device_errors from pylib.gtest import gtest_test_instance from pylib.local import local_test_server_spawner from pylib.perf import perf_control # Test case statuses. RE_RUN = re.compile('\\[ RUN \\] ?(.*)\r\n') RE_FAIL = re.compile('\\[ FAILED \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n') RE_OK = re.compile('\\[ OK \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n') # Test run statuses. RE_PASSED = re.compile('\\[ PASSED \\] ?(.*)\r\n') RE_RUNNER_FAIL = re.compile('\\[ RUNNER_FAILED \\] ?(.*)\r\n') # Signal handlers are installed before starting tests # to output the CRASHED marker when a crash happens. RE_CRASH = re.compile('\\[ CRASHED \\](.*)\r\n') # Bots that don't output anything for 20 minutes get timed out, so that's our # hard cap. _INFRA_STDOUT_TIMEOUT = 20 * 60 def _TestSuiteRequiresMockTestServer(suite_name): """Returns True if the test suite requires mock test server.""" tests_require_net_test_server = ['unit_tests', 'net_unittests', 'components_browsertests', 'content_unittests', 'content_browsertests'] return (suite_name in tests_require_net_test_server) def _TestSuiteRequiresHighPerfMode(suite_name): """Returns True if the test suite requires high performance mode.""" return 'perftests' in suite_name class TestRunner(base_test_runner.BaseTestRunner): def __init__(self, test_options, device, test_package): """Single test suite attached to a single device. Args: test_options: A GTestOptions object. device: Device to run the tests. test_package: An instance of TestPackage class. """ super(TestRunner, self).__init__(device, test_options.tool) self.test_package = test_package self.test_package.tool = self.tool self._test_arguments = test_options.test_arguments timeout = test_options.timeout if timeout == 0: timeout = 60 # On a VM (e.g. chromium buildbots), this timeout is way too small. if os.environ.get('BUILDBOT_SLAVENAME'): timeout = timeout * 2 self._timeout = min(timeout * self.tool.GetTimeoutScale(), _INFRA_STDOUT_TIMEOUT) if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name): self._perf_controller = perf_control.PerfControl(self.device) if _TestSuiteRequiresMockTestServer(self.test_package.suite_name): self._servers = [ local_test_server_spawner.LocalTestServerSpawner( ports.AllocateTestServerPort(), self.device, self.tool)] else: self._servers = [] if test_options.app_data_files: self._app_data_files = test_options.app_data_files if test_options.app_data_file_dir: self._app_data_file_dir = test_options.app_data_file_dir else: self._app_data_file_dir = tempfile.mkdtemp() logging.critical('Saving app files to %s', self._app_data_file_dir) else: self._app_data_files = None self._app_data_file_dir = None #override def InstallTestPackage(self): self.test_package.Install(self.device) def _ParseTestOutput(self, p): """Process the test output. Args: p: An instance of pexpect spawn class. Returns: A TestRunResults object. """ results = base_test_result.TestRunResults() log = '' try: while True: full_test_name = None found = p.expect([RE_RUN, RE_PASSED, RE_RUNNER_FAIL], timeout=self._timeout) if found == 1: # RE_PASSED break elif found == 2: # RE_RUNNER_FAIL break else: # RE_RUN full_test_name = p.match.group(1).replace('\r', '') found = p.expect([RE_OK, RE_FAIL, RE_CRASH], timeout=self._timeout) log = p.before.replace('\r', '') if found == 0: # RE_OK if full_test_name == p.match.group(1).replace('\r', ''): duration_ms = int(p.match.group(3)) if p.match.group(3) else 0 results.AddResult(base_test_result.BaseTestResult( full_test_name, base_test_result.ResultType.PASS, duration=duration_ms, log=log)) elif found == 2: # RE_CRASH results.AddResult(base_test_result.BaseTestResult( full_test_name, base_test_result.ResultType.CRASH, log=log)) break else: # RE_FAIL duration_ms = int(p.match.group(3)) if p.match.group(3) else 0 results.AddResult(base_test_result.BaseTestResult( full_test_name, base_test_result.ResultType.FAIL, duration=duration_ms, log=log)) except pexpect.EOF: logging.error('Test terminated - EOF') # We're here because either the device went offline, or the test harness # crashed without outputting the CRASHED marker (crbug.com/175538). if not self.device.IsOnline(): raise device_errors.DeviceUnreachableError( 'Device %s went offline.' % str(self.device)) if full_test_name: results.AddResult(base_test_result.BaseTestResult( full_test_name, base_test_result.ResultType.CRASH, log=p.before.replace('\r', ''))) except pexpect.TIMEOUT: logging.error('Test terminated after %d second timeout.', self._timeout) if full_test_name: results.AddResult(base_test_result.BaseTestResult( full_test_name, base_test_result.ResultType.TIMEOUT, log=p.before.replace('\r', ''))) finally: p.close() ret_code = self.test_package.GetGTestReturnCode(self.device) if ret_code: logging.critical( 'gtest exit code: %d\npexpect.before: %s\npexpect.after: %s', ret_code, p.before, p.after) return results #override def RunTest(self, test): test_results = base_test_result.TestRunResults() if not test: return test_results, None try: self.test_package.ClearApplicationState(self.device) self.test_package.CreateCommandLineFileOnDevice( self.device, test, self._test_arguments) test_results = self._ParseTestOutput( self.test_package.SpawnTestProcess(self.device)) if self._app_data_files: self.test_package.PullAppFiles(self.device, self._app_data_files, self._app_data_file_dir) finally: for s in self._servers: s.Reset() # Calculate unknown test results. all_tests = set(test.split(':')) all_tests_ran = set([t.GetName() for t in test_results.GetAll()]) unknown_tests = all_tests - all_tests_ran test_results.AddResults( [base_test_result.BaseTestResult(t, base_test_result.ResultType.UNKNOWN) for t in unknown_tests]) retry = ':'.join([t.GetName() for t in test_results.GetNotPass()]) return test_results, retry #override def SetUp(self): """Sets up necessary test enviroment for the test suite.""" super(TestRunner, self).SetUp() for s in self._servers: s.SetUp() if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name): self._perf_controller.SetHighPerfMode() self.tool.SetupEnvironment() #override def TearDown(self): """Cleans up the test enviroment for the test suite.""" for s in self._servers: s.TearDown() if _TestSuiteRequiresHighPerfMode(self.test_package.suite_name): self._perf_controller.SetDefaultPerfMode() self.test_package.ClearApplicationState(self.device) self.tool.CleanUpEnvironment() super(TestRunner, self).TearDown()