michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: michael@0: import logging michael@0: import os michael@0: import shutil michael@0: import sys michael@0: import tempfile michael@0: michael@0: import cmd_helper michael@0: import constants michael@0: from test_package import TestPackage michael@0: from pylib import pexpect michael@0: michael@0: michael@0: class TestPackageExecutable(TestPackage): michael@0: """A helper class for running stand-alone executables.""" michael@0: michael@0: _TEST_RUNNER_RET_VAL_FILE = 'gtest_retval' michael@0: michael@0: def __init__(self, adb, device, test_suite, timeout, rebaseline, michael@0: performance_test, cleanup_test_files, tool, dump_debug_info, michael@0: symbols_dir=None): michael@0: """ michael@0: Args: michael@0: adb: ADB interface the tests are using. michael@0: device: Device to run the tests. michael@0: test_suite: A specific test suite to run, empty to run all. michael@0: timeout: Timeout for each test. michael@0: rebaseline: Whether or not to run tests in isolation and update the michael@0: filter. michael@0: performance_test: Whether or not performance test(s). michael@0: cleanup_test_files: Whether or not to cleanup test files on device. michael@0: tool: Name of the Valgrind tool. michael@0: dump_debug_info: A debug_info object. michael@0: symbols_dir: Directory to put the stripped binaries. michael@0: """ michael@0: TestPackage.__init__(self, adb, device, test_suite, timeout, michael@0: rebaseline, performance_test, cleanup_test_files, michael@0: tool, dump_debug_info) michael@0: self.symbols_dir = symbols_dir michael@0: michael@0: def _GetGTestReturnCode(self): michael@0: ret = None michael@0: ret_code = 1 # Assume failure if we can't find it michael@0: ret_code_file = tempfile.NamedTemporaryFile() michael@0: try: michael@0: if not self.adb.Adb().Pull( michael@0: self.adb.GetExternalStorage() + '/' + michael@0: TestPackageExecutable._TEST_RUNNER_RET_VAL_FILE, michael@0: ret_code_file.name): michael@0: logging.critical('Unable to pull gtest ret val file %s', michael@0: ret_code_file.name) michael@0: raise ValueError michael@0: ret_code = file(ret_code_file.name).read() michael@0: ret = int(ret_code) michael@0: except ValueError: michael@0: logging.critical('Error reading gtest ret val file %s [%s]', michael@0: ret_code_file.name, ret_code) michael@0: ret = 1 michael@0: return ret michael@0: michael@0: def _AddNativeCoverageExports(self): michael@0: # export GCOV_PREFIX set the path for native coverage results michael@0: # export GCOV_PREFIX_STRIP indicates how many initial directory michael@0: # names to strip off the hardwired absolute paths. michael@0: # This value is calculated in buildbot.sh and michael@0: # depends on where the tree is built. michael@0: # Ex: /usr/local/google/code/chrome will become michael@0: # /code/chrome if GCOV_PREFIX_STRIP=3 michael@0: try: michael@0: depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP'] michael@0: except KeyError: michael@0: logging.info('NATIVE_COVERAGE_DEPTH_STRIP is not defined: ' michael@0: 'No native coverage.') michael@0: return '' michael@0: export_string = ('export GCOV_PREFIX="%s/gcov"\n' % michael@0: self.adb.GetExternalStorage()) michael@0: export_string += 'export GCOV_PREFIX_STRIP=%s\n' % depth michael@0: return export_string michael@0: michael@0: def GetAllTests(self): michael@0: """Returns a list of all tests available in the test suite.""" michael@0: all_tests = self.adb.RunShellCommand( michael@0: '%s %s/%s --gtest_list_tests' % michael@0: (self.tool.GetTestWrapper(), michael@0: constants.TEST_EXECUTABLE_DIR, michael@0: self.test_suite_basename)) michael@0: return self._ParseGTestListTests(all_tests) michael@0: michael@0: def CreateTestRunnerScript(self, gtest_filter, test_arguments): michael@0: """Creates a test runner script and pushes to the device. michael@0: michael@0: Args: michael@0: gtest_filter: A gtest_filter flag. michael@0: test_arguments: Additional arguments to pass to the test binary. michael@0: """ michael@0: tool_wrapper = self.tool.GetTestWrapper() michael@0: sh_script_file = tempfile.NamedTemporaryFile() michael@0: # We need to capture the exit status from the script since adb shell won't michael@0: # propagate to us. michael@0: sh_script_file.write('cd %s\n' michael@0: '%s' michael@0: '%s %s/%s --gtest_filter=%s %s\n' michael@0: 'echo $? > %s' % michael@0: (constants.TEST_EXECUTABLE_DIR, michael@0: self._AddNativeCoverageExports(), michael@0: tool_wrapper, constants.TEST_EXECUTABLE_DIR, michael@0: self.test_suite_basename, michael@0: gtest_filter, test_arguments, michael@0: TestPackageExecutable._TEST_RUNNER_RET_VAL_FILE)) michael@0: sh_script_file.flush() michael@0: cmd_helper.RunCmd(['chmod', '+x', sh_script_file.name]) michael@0: self.adb.PushIfNeeded( michael@0: sh_script_file.name, michael@0: constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh') michael@0: logging.info('Conents of the test runner script: ') michael@0: for line in open(sh_script_file.name).readlines(): michael@0: logging.info(' ' + line.rstrip()) michael@0: michael@0: def RunTestsAndListResults(self): michael@0: """Runs all the tests and checks for failures. michael@0: michael@0: Returns: michael@0: A TestResults object. michael@0: """ michael@0: args = ['adb', '-s', self.device, 'shell', 'sh', michael@0: constants.TEST_EXECUTABLE_DIR + '/chrome_test_runner.sh'] michael@0: logging.info(args) michael@0: p = pexpect.spawn(args[0], args[1:], logfile=sys.stdout) michael@0: return self._WatchTestOutput(p) michael@0: michael@0: def StripAndCopyExecutable(self): michael@0: """Strips and copies the executable to the device.""" michael@0: if self.tool.NeedsDebugInfo(): michael@0: target_name = self.test_suite michael@0: else: michael@0: target_name = self.test_suite + '_' + self.device + '_stripped' michael@0: should_strip = True michael@0: if os.path.isfile(target_name): michael@0: logging.info('Found target file %s' % target_name) michael@0: target_mtime = os.stat(target_name).st_mtime michael@0: source_mtime = os.stat(self.test_suite).st_mtime michael@0: if target_mtime > source_mtime: michael@0: logging.info('Target mtime (%d) is newer than source (%d), assuming ' michael@0: 'no change.' % (target_mtime, source_mtime)) michael@0: should_strip = False michael@0: michael@0: if should_strip: michael@0: logging.info('Did not find up-to-date stripped binary. Generating a ' michael@0: 'new one (%s).' % target_name) michael@0: # Whenever we generate a stripped binary, copy to the symbols dir. If we michael@0: # aren't stripping a new binary, assume it's there. michael@0: if self.symbols_dir: michael@0: if not os.path.exists(self.symbols_dir): michael@0: os.makedirs(self.symbols_dir) michael@0: shutil.copy(self.test_suite, self.symbols_dir) michael@0: strip = os.environ['STRIP'] michael@0: cmd_helper.RunCmd([strip, self.test_suite, '-o', target_name]) michael@0: test_binary = constants.TEST_EXECUTABLE_DIR + '/' + self.test_suite_basename michael@0: self.adb.PushIfNeeded(target_name, test_binary) michael@0: michael@0: def _GetTestSuiteBaseName(self): michael@0: """Returns the base name of the test suite.""" michael@0: return os.path.basename(self.test_suite)