#!/usr/bin/python2.4 # # # Copyright 2009, The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """TestSuite for running native Android tests.""" # python imports import os import re # local imports import android_build import logger import run_command import test_suite class NativeTestSuite(test_suite.AbstractTestSuite): """A test suite for running native aka C/C++ tests on device.""" def Run(self, options, adb): """Run the provided *native* test suite. The test_suite must contain a build path where the native test files are. Subdirectories are automatically scanned as well. Each test's name must have a .cc or .cpp extension and match one of the following patterns: - test_* - *_test.[cc|cpp] - *_unittest.[cc|cpp] A successful test must return 0. Any other value will be considered as an error. Args: options: command line options adb: adb interface """ # find all test files, convert unicode names to ascii, take the basename # and drop the .cc/.cpp extension. source_list = [] build_path = os.path.join(android_build.GetTop(), self.GetBuildPath()) os.path.walk(build_path, self._CollectTestSources, source_list) logger.SilentLog("Tests source %s" % source_list) # Host tests are under out/host/-/bin. host_list = self._FilterOutMissing(android_build.GetHostBin(), source_list) logger.SilentLog("Host tests %s" % host_list) # Target tests are under $ANDROID_PRODUCT_OUT/data/nativetest. target_list = self._FilterOutMissing(android_build.GetTargetNativeTestPath(), source_list) logger.SilentLog("Target tests %s" % target_list) # Run on the host logger.Log("\nRunning on host") for f in host_list: if run_command.RunHostCommand(f) != 0: logger.Log("%s... failed" % f) else: if run_command.HasValgrind(): if run_command.RunHostCommand(f, valgrind=True) == 0: logger.Log("%s... ok\t\t[valgrind: ok]" % f) else: logger.Log("%s... ok\t\t[valgrind: failed]" % f) else: logger.Log("%s... ok\t\t[valgrind: missing]" % f) # Run on the device logger.Log("\nRunning on target") for f in target_list: full_path = os.path.join(os.sep, "data", "nativetest", f) # Single quotes are needed to prevent the shell splitting it. output = adb.SendShellCommand("'%s 2>&1;echo -n exit code:$?'" % "(cd /sdcard;%s)" % full_path, int(options.timeout)) success = output.endswith("exit code:0") logger.Log("%s... %s" % (f, success and "ok" or "failed")) # Print the captured output when the test failed. if not success or options.verbose: pos = output.rfind("exit code") output = output[0:pos] logger.Log(output) # Cleanup adb.SendShellCommand("rm %s" % full_path) def _CollectTestSources(self, test_list, dirname, files): """For each directory, find tests source file and add them to the list. Test files must match one of the following pattern: - test_*.[cc|cpp] - *_test.[cc|cpp] - *_unittest.[cc|cpp] This method is a callback for os.path.walk. Args: test_list: Where new tests should be inserted. dirname: Current directory. files: List of files in the current directory. """ for f in files: (name, ext) = os.path.splitext(f) if ext == ".cc" or ext == ".cpp" or ext == ".c": if re.search("_test$|_test_$|_unittest$|_unittest_$|^test_", name): logger.SilentLog("Found %s" % f) test_list.append(str(os.path.join(dirname, f))) def _FilterOutMissing(self, path, sources): """Filter out from the sources list missing tests. Sometimes some test source are not built for the target, i.e there is no binary corresponding to the source file. We need to filter these out. Args: path: Where the binaries should be. sources: List of tests source path. Returns: A list of relative paths to the test binaries built from the sources. """ binaries = [] for f in sources: binary = os.path.basename(f) binary = os.path.splitext(binary)[0] found = self._FindFileRecursively(path, binary) if found: binary = os.path.relpath(os.path.abspath(found), os.path.abspath(path)) binaries.append(binary) return binaries def _FindFileRecursively(self, path, match): """Finds the first executable binary in a given path that matches the name. Args: path: Where to search for binaries. Can be nested directories. binary: Which binary to search for. Returns: first matched file in the path or None if none is found. """ for root, dirs, files in os.walk(path): for f in files: if f == match: return os.path.join(root, f) for d in dirs: found = self._FindFileRecursively(os.path.join(root, d), match) if found: return found return None def _RunHostCommand(self, binary, valgrind=False): """Run a command on the host (opt using valgrind). Runs the host binary and returns the exit code. If successfull, the output (stdout and stderr) are discarded, but printed in case of error. The command can be run under valgrind in which case all the output are always discarded. Args: binary: basename of the file to be run. It is expected to be under $ANDROID_HOST_OUT/bin. valgrind: If True the command will be run under valgrind. Returns: The command exit code (int) """ full_path = os.path.join(android_build.GetHostBin(), binary) return run_command.RunHostCommand(full_path, valgrind=valgrind)