1#!/usr/bin/python2.4 2# 3# 4# Copyright 2009, The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17 18"""TestSuite for running native Android tests.""" 19 20# python imports 21import os 22import re 23 24# local imports 25import android_build 26import logger 27import run_command 28import test_suite 29 30 31class NativeTestSuite(test_suite.AbstractTestSuite): 32 """A test suite for running native aka C/C++ tests on device.""" 33 34 def Run(self, options, adb): 35 """Run the provided *native* test suite. 36 37 The test_suite must contain a build path where the native test 38 files are. Subdirectories are automatically scanned as well. 39 40 Each test's name must have a .cc or .cpp extension and match one 41 of the following patterns: 42 - test_* 43 - *_test.[cc|cpp] 44 - *_unittest.[cc|cpp] 45 A successful test must return 0. Any other value will be considered 46 as an error. 47 48 Args: 49 options: command line options 50 adb: adb interface 51 """ 52 # find all test files, convert unicode names to ascii, take the basename 53 # and drop the .cc/.cpp extension. 54 source_list = [] 55 build_path = os.path.join(android_build.GetTop(), self.GetBuildPath()) 56 os.path.walk(build_path, self._CollectTestSources, source_list) 57 logger.SilentLog("Tests source %s" % source_list) 58 59 # Host tests are under out/host/<os>-<arch>/bin. 60 host_list = self._FilterOutMissing(android_build.GetHostBin(), source_list) 61 logger.SilentLog("Host tests %s" % host_list) 62 63 # Target tests are under $ANDROID_PRODUCT_OUT/data/nativetest. 64 target_list = self._FilterOutMissing(android_build.GetTargetNativeTestPath(), 65 source_list) 66 logger.SilentLog("Target tests %s" % target_list) 67 68 # Run on the host 69 logger.Log("\nRunning on host") 70 for f in host_list: 71 if run_command.RunHostCommand(f) != 0: 72 logger.Log("%s... failed" % f) 73 else: 74 if run_command.HasValgrind(): 75 if run_command.RunHostCommand(f, valgrind=True) == 0: 76 logger.Log("%s... ok\t\t[valgrind: ok]" % f) 77 else: 78 logger.Log("%s... ok\t\t[valgrind: failed]" % f) 79 else: 80 logger.Log("%s... ok\t\t[valgrind: missing]" % f) 81 82 # Run on the device 83 logger.Log("\nRunning on target") 84 for f in target_list: 85 full_path = os.path.join(os.sep, "data", "nativetest", f) 86 87 # Single quotes are needed to prevent the shell splitting it. 88 output = adb.SendShellCommand("'%s 2>&1;echo -n exit code:$?'" % 89 "(cd /sdcard;%s)" % full_path, 90 int(options.timeout)) 91 success = output.endswith("exit code:0") 92 logger.Log("%s... %s" % (f, success and "ok" or "failed")) 93 # Print the captured output when the test failed. 94 if not success or options.verbose: 95 pos = output.rfind("exit code") 96 output = output[0:pos] 97 logger.Log(output) 98 99 # Cleanup 100 adb.SendShellCommand("rm %s" % full_path) 101 102 def _CollectTestSources(self, test_list, dirname, files): 103 """For each directory, find tests source file and add them to the list. 104 105 Test files must match one of the following pattern: 106 - test_*.[cc|cpp] 107 - *_test.[cc|cpp] 108 - *_unittest.[cc|cpp] 109 110 This method is a callback for os.path.walk. 111 112 Args: 113 test_list: Where new tests should be inserted. 114 dirname: Current directory. 115 files: List of files in the current directory. 116 """ 117 for f in files: 118 (name, ext) = os.path.splitext(f) 119 if ext == ".cc" or ext == ".cpp" or ext == ".c": 120 if re.search("_test$|_test_$|_unittest$|_unittest_$|^test_", name): 121 logger.SilentLog("Found %s" % f) 122 test_list.append(str(os.path.join(dirname, f))) 123 124 def _FilterOutMissing(self, path, sources): 125 """Filter out from the sources list missing tests. 126 127 Sometimes some test source are not built for the target, i.e there 128 is no binary corresponding to the source file. We need to filter 129 these out. 130 131 Args: 132 path: Where the binaries should be. 133 sources: List of tests source path. 134 Returns: 135 A list of relative paths to the test binaries built from the sources. 136 """ 137 binaries = [] 138 for f in sources: 139 binary = os.path.basename(f) 140 binary = os.path.splitext(binary)[0] 141 found = self._FindFileRecursively(path, binary) 142 if found: 143 binary = os.path.relpath(os.path.abspath(found), 144 os.path.abspath(path)) 145 binaries.append(binary) 146 return binaries 147 148 def _FindFileRecursively(self, path, match): 149 """Finds the first executable binary in a given path that matches the name. 150 151 Args: 152 path: Where to search for binaries. Can be nested directories. 153 binary: Which binary to search for. 154 Returns: 155 first matched file in the path or None if none is found. 156 """ 157 for root, dirs, files in os.walk(path): 158 for f in files: 159 if f == match: 160 return os.path.join(root, f) 161 for d in dirs: 162 found = self._FindFileRecursively(os.path.join(root, d), match) 163 if found: 164 return found 165 return None 166 167 def _RunHostCommand(self, binary, valgrind=False): 168 """Run a command on the host (opt using valgrind). 169 170 Runs the host binary and returns the exit code. 171 If successfull, the output (stdout and stderr) are discarded, 172 but printed in case of error. 173 The command can be run under valgrind in which case all the 174 output are always discarded. 175 176 Args: 177 binary: basename of the file to be run. It is expected to be under 178 $ANDROID_HOST_OUT/bin. 179 valgrind: If True the command will be run under valgrind. 180 181 Returns: 182 The command exit code (int) 183 """ 184 full_path = os.path.join(android_build.GetHostBin(), binary) 185 return run_command.RunHostCommand(full_path, valgrind=valgrind) 186