You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
6.3 KiB
185 lines
6.3 KiB
#!/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/<os>-<arch>/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)
|
|
|