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