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/system/bin.
64    target_list = self._FilterOutMissing(android_build.GetTargetSystemBin(),
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, "system", "bin", 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 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      full_path = os.path.join(path, binary)
142      if os.path.exists(full_path):
143        binaries.append(binary)
144    return binaries
145
146  def _RunHostCommand(self, binary, valgrind=False):
147    """Run a command on the host (opt using valgrind).
148
149    Runs the host binary and returns the exit code.
150    If successfull, the output (stdout and stderr) are discarded,
151    but printed in case of error.
152    The command can be run under valgrind in which case all the
153    output are always discarded.
154
155    Args:
156      binary: basename of the file to be run. It is expected to be under
157            $ANDROID_HOST_OUT/bin.
158      valgrind: If True the command will be run under valgrind.
159
160    Returns:
161      The command exit code (int)
162    """
163    full_path = os.path.join(android_build.GetHostBin(), binary)
164    return run_command.RunHostCommand(full_path, valgrind=valgrind)
165