The Android Open Source Project | 6ffae01 | 2009-03-18 17:39:43 -0700 | [diff] [blame^] | 1 | #!/usr/bin/python2.4 |
| 2 | # |
| 3 | # Copyright 2008, The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | """Command line utility for running a pre-defined test. |
| 18 | |
| 19 | Based on previous <androidroot>/development/tools/runtest shell script. |
| 20 | """ |
| 21 | |
| 22 | # Python imports |
| 23 | import glob |
| 24 | import optparse |
| 25 | import os |
| 26 | from sets import Set |
| 27 | import sys |
| 28 | |
| 29 | # local imports |
| 30 | import adb_interface |
| 31 | import android_build |
| 32 | import coverage |
| 33 | import errors |
| 34 | import logger |
| 35 | import run_command |
| 36 | import test_defs |
| 37 | |
| 38 | |
| 39 | class TestRunner(object): |
| 40 | """Command line utility class for running pre-defined Android test(s).""" |
| 41 | |
| 42 | # file path to android core platform tests, relative to android build root |
| 43 | # TODO move these test data files to another directory |
| 44 | _CORE_TEST_PATH = os.path.join("development", "testrunner", "tests.xml") |
| 45 | |
| 46 | # vendor glob file path patterns to tests, relative to android |
| 47 | # build root |
| 48 | _VENDOR_TEST_PATH = os.path.join("vendor", "*", "tests", "testinfo", |
| 49 | "tests.xml") |
| 50 | |
| 51 | _RUNTEST_USAGE = ( |
| 52 | "usage: runtest.py [options] short-test-name[s]\n\n" |
| 53 | "The runtest script works in two ways. You can query it " |
| 54 | "for a list of tests, or you can launch one or more tests.") |
| 55 | |
| 56 | def _ProcessOptions(self): |
| 57 | """Processes command-line options.""" |
| 58 | # TODO error messages on once-only or mutually-exclusive options. |
| 59 | user_test_default = os.path.join(os.environ.get("HOME"), ".android", |
| 60 | "tests.xml") |
| 61 | |
| 62 | parser = optparse.OptionParser(usage=self._RUNTEST_USAGE) |
| 63 | |
| 64 | parser.add_option("-l", "--list-tests", dest="only_list_tests", |
| 65 | default=False, action="store_true", |
| 66 | help="To view the list of tests") |
| 67 | parser.add_option("-b", "--skip-build", dest="skip_build", default=False, |
| 68 | action="store_true", help="Skip build - just launch") |
| 69 | parser.add_option("-n", "--skip_execute", dest="preview", default=False, |
| 70 | action="store_true", |
| 71 | help="Do not execute, just preview commands") |
| 72 | parser.add_option("-r", "--raw-mode", dest="raw_mode", default=False, |
| 73 | action="store_true", |
| 74 | help="Raw mode (for output to other tools)") |
| 75 | parser.add_option("-a", "--suite-assign", dest="suite_assign_mode", |
| 76 | default=False, action="store_true", |
| 77 | help="Suite assignment (for details & usage see " |
| 78 | "InstrumentationTestRunner)") |
| 79 | parser.add_option("-v", "--verbose", dest="verbose", default=False, |
| 80 | action="store_true", |
| 81 | help="Increase verbosity of %s" % sys.argv[0]) |
| 82 | parser.add_option("-w", "--wait-for-debugger", dest="wait_for_debugger", |
| 83 | default=False, action="store_true", |
| 84 | help="Wait for debugger before launching tests") |
| 85 | parser.add_option("-c", "--test-class", dest="test_class", |
| 86 | help="Restrict test to a specific class") |
| 87 | parser.add_option("-m", "--test-method", dest="test_method", |
| 88 | help="Restrict test to a specific method") |
| 89 | parser.add_option("-u", "--user-tests-file", dest="user_tests_file", |
| 90 | metavar="FILE", default=user_test_default, |
| 91 | help="Alternate source of user test definitions") |
| 92 | parser.add_option("-o", "--coverage", dest="coverage", |
| 93 | default=False, action="store_true", |
| 94 | help="Generate code coverage metrics for test(s)") |
| 95 | parser.add_option("-t", "--all-tests", dest="all_tests", |
| 96 | default=False, action="store_true", |
| 97 | help="Run all defined tests") |
| 98 | parser.add_option("--continuous", dest="continuous_tests", |
| 99 | default=False, action="store_true", |
| 100 | help="Run all tests defined as part of the continuous " |
| 101 | "test set") |
| 102 | |
| 103 | group = optparse.OptionGroup( |
| 104 | parser, "Targets", "Use these options to direct tests to a specific " |
| 105 | "Android target") |
| 106 | group.add_option("-e", "--emulator", dest="emulator", default=False, |
| 107 | action="store_true", help="use emulator") |
| 108 | group.add_option("-d", "--device", dest="device", default=False, |
| 109 | action="store_true", help="use device") |
| 110 | group.add_option("-s", "--serial", dest="serial", |
| 111 | help="use specific serial") |
| 112 | parser.add_option_group(group) |
| 113 | |
| 114 | self._options, self._test_args = parser.parse_args() |
| 115 | |
| 116 | if (not self._options.only_list_tests and not self._options.all_tests |
| 117 | and not self._options.continuous_tests and len(self._test_args) < 1): |
| 118 | parser.print_help() |
| 119 | logger.SilentLog("at least one test name must be specified") |
| 120 | raise errors.AbortError |
| 121 | |
| 122 | self._adb = adb_interface.AdbInterface() |
| 123 | if self._options.emulator: |
| 124 | self._adb.SetEmulatorTarget() |
| 125 | elif self._options.device: |
| 126 | self._adb.SetDeviceTarget() |
| 127 | elif self._options.serial is not None: |
| 128 | self._adb.SetTargetSerial(self._options.serial) |
| 129 | |
| 130 | if self._options.verbose: |
| 131 | logger.SetVerbose(True) |
| 132 | |
| 133 | self._root_path = android_build.GetTop() |
| 134 | |
| 135 | self._known_tests = self._ReadTests() |
| 136 | |
| 137 | self._coverage_gen = coverage.CoverageGenerator( |
| 138 | android_root_path=self._root_path, adb_interface=self._adb) |
| 139 | |
| 140 | def _ReadTests(self): |
| 141 | """Parses the set of test definition data. |
| 142 | |
| 143 | Returns: |
| 144 | A TestDefinitions object that contains the set of parsed tests. |
| 145 | Raises: |
| 146 | AbortError: If a fatal error occurred when parsing the tests. |
| 147 | """ |
| 148 | core_test_path = os.path.join(self._root_path, self._CORE_TEST_PATH) |
| 149 | try: |
| 150 | known_tests = test_defs.TestDefinitions() |
| 151 | known_tests.Parse(core_test_path) |
| 152 | # read all <android root>/vendor/*/tests/testinfo/tests.xml paths |
| 153 | vendor_tests_pattern = os.path.join(self._root_path, |
| 154 | self._VENDOR_TEST_PATH) |
| 155 | test_file_paths = glob.glob(vendor_tests_pattern) |
| 156 | for test_file_path in test_file_paths: |
| 157 | known_tests.Parse(test_file_path) |
| 158 | if os.path.isfile(self._options.user_tests_file): |
| 159 | known_tests.Parse(self._options.user_tests_file) |
| 160 | return known_tests |
| 161 | except errors.ParseError: |
| 162 | raise errors.AbortError |
| 163 | |
| 164 | def _DumpTests(self): |
| 165 | """Prints out set of defined tests.""" |
| 166 | print "The following tests are currently defined:" |
| 167 | for test in self._known_tests: |
| 168 | print test.GetName() |
| 169 | |
| 170 | def _DoBuild(self): |
| 171 | logger.SilentLog("Building tests...") |
| 172 | target_set = Set() |
| 173 | for test_suite in self._GetTestsToRun(): |
| 174 | self._AddBuildTarget(test_suite.GetBuildPath(), target_set) |
| 175 | |
| 176 | if target_set: |
| 177 | if self._options.coverage: |
| 178 | self._coverage_gen.EnableCoverageBuild() |
| 179 | self._AddBuildTarget(self._coverage_gen.GetEmmaBuildPath(), target_set) |
| 180 | target_build_string = " ".join(list(target_set)) |
| 181 | logger.Log("Building %s" % target_build_string) |
| 182 | cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files' % (target_build_string, |
| 183 | self._root_path) |
| 184 | if not self._options.preview: |
| 185 | run_command.RunCommand(cmd, return_output=False) |
| 186 | logger.Log("Syncing to device...") |
| 187 | self._adb.Sync() |
| 188 | |
| 189 | def _AddBuildTarget(self, build_dir, target_set): |
| 190 | if build_dir is not None: |
| 191 | build_file_path = os.path.join(build_dir, "Android.mk") |
| 192 | if os.path.isfile(os.path.join(self._root_path, build_file_path)): |
| 193 | target_set.add(build_file_path) |
| 194 | |
| 195 | def _GetTestsToRun(self): |
| 196 | """Get a list of TestSuite objects to run, based on command line args.""" |
| 197 | if self._options.all_tests: |
| 198 | return self._known_tests.GetTests() |
| 199 | if self._options.continuous_tests: |
| 200 | return self._known_tests.GetContinuousTests() |
| 201 | tests = [] |
| 202 | for name in self._test_args: |
| 203 | test = self._known_tests.GetTest(name) |
| 204 | if test is None: |
| 205 | logger.Log("Error: Could not find test %s" % name) |
| 206 | self._DumpTests() |
| 207 | raise errors.AbortError |
| 208 | tests.append(test) |
| 209 | return tests |
| 210 | |
| 211 | def _RunTest(self, test_suite): |
| 212 | """Run the provided test suite. |
| 213 | |
| 214 | Builds up an adb instrument command using provided input arguments. |
| 215 | |
| 216 | Args: |
| 217 | test_suite: TestSuite to run |
| 218 | """ |
| 219 | |
| 220 | test_class = test_suite.GetClassName() |
| 221 | if self._options.test_class is not None: |
| 222 | test_class = self._options.test_class |
| 223 | if self._options.test_method is not None: |
| 224 | test_class = "%s#%s" % (test_class, self._options.test_method) |
| 225 | |
| 226 | instrumentation_args = {} |
| 227 | if test_class is not None: |
| 228 | instrumentation_args["class"] = test_class |
| 229 | if self._options.wait_for_debugger: |
| 230 | instrumentation_args["debug"] = "true" |
| 231 | if self._options.suite_assign_mode: |
| 232 | instrumentation_args["suiteAssignment"] = "true" |
| 233 | if self._options.coverage: |
| 234 | instrumentation_args["coverage"] = "true" |
| 235 | if self._options.preview: |
| 236 | adb_cmd = self._adb.PreviewInstrumentationCommand( |
| 237 | package_name=test_suite.GetPackageName(), |
| 238 | runner_name=test_suite.GetRunnerName(), |
| 239 | raw_mode=self._options.raw_mode, |
| 240 | instrumentation_args=instrumentation_args) |
| 241 | logger.Log(adb_cmd) |
| 242 | else: |
| 243 | self._adb.StartInstrumentationNoResults( |
| 244 | package_name=test_suite.GetPackageName(), |
| 245 | runner_name=test_suite.GetRunnerName(), |
| 246 | raw_mode=self._options.raw_mode, |
| 247 | instrumentation_args=instrumentation_args) |
| 248 | if self._options.coverage and test_suite.GetTargetName() is not None: |
| 249 | coverage_file = self._coverage_gen.ExtractReport(test_suite) |
| 250 | if coverage_file is not None: |
| 251 | logger.Log("Coverage report generated at %s" % coverage_file) |
| 252 | |
| 253 | def RunTests(self): |
| 254 | """Main entry method - executes the tests according to command line args.""" |
| 255 | try: |
| 256 | run_command.SetAbortOnError() |
| 257 | self._ProcessOptions() |
| 258 | if self._options.only_list_tests: |
| 259 | self._DumpTests() |
| 260 | return |
| 261 | |
| 262 | if not self._options.skip_build: |
| 263 | self._DoBuild() |
| 264 | |
| 265 | for test_suite in self._GetTestsToRun(): |
| 266 | self._RunTest(test_suite) |
| 267 | except KeyboardInterrupt: |
| 268 | logger.Log("Exiting...") |
| 269 | except errors.AbortError: |
| 270 | logger.SilentLog("Exiting due to AbortError...") |
| 271 | except errors.WaitForResponseTimedOutError: |
| 272 | logger.Log("Timed out waiting for response") |
| 273 | |
| 274 | |
| 275 | def RunTests(): |
| 276 | runner = TestRunner() |
| 277 | runner.RunTests() |
| 278 | |
| 279 | if __name__ == "__main__": |
| 280 | RunTests() |