Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright 2017, 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 | |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 17 | """ |
| 18 | Command line utility for running Android tests through TradeFederation. |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 19 | |
| 20 | atest helps automate the flow of building test modules across the Android |
| 21 | code base and executing the tests via the TradeFederation test harness. |
| 22 | |
| 23 | atest is designed to support any test types that can be ran by TradeFederation. |
| 24 | """ |
| 25 | |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 26 | import logging |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 27 | import os |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 28 | import subprocess |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 29 | import sys |
mikehoran | 95091b2 | 2017-10-31 15:55:26 -0700 | [diff] [blame] | 30 | import tempfile |
| 31 | import time |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 32 | |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 33 | import atest_error |
Simran Basi | cf2189b | 2017-11-06 23:40:24 -0800 | [diff] [blame] | 34 | import atest_utils |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 35 | import cli_translator |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 36 | # pylint: disable=import-error |
| 37 | import constants |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 38 | import module_info |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 39 | import test_runner_handler |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 40 | from test_runners import regression_test_runner |
Simran Basi | cf2189b | 2017-11-06 23:40:24 -0800 | [diff] [blame] | 41 | |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 42 | EXPECTED_VARS = frozenset([ |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 43 | constants.ANDROID_BUILD_TOP, |
mikehoran | 43ed32d | 2017-08-18 17:13:36 -0700 | [diff] [blame] | 44 | 'ANDROID_TARGET_OUT_TESTCASES', |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 45 | constants.ANDROID_OUT]) |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 46 | BUILD_STEP = 'build' |
mikehoran | c327dca | 2017-11-27 16:24:22 -0800 | [diff] [blame] | 47 | INSTALL_STEP = 'install' |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 48 | TEST_STEP = 'test' |
mikehoran | c327dca | 2017-11-27 16:24:22 -0800 | [diff] [blame] | 49 | ALL_STEPS = [BUILD_STEP, INSTALL_STEP, TEST_STEP] |
mikehoran | 95091b2 | 2017-10-31 15:55:26 -0700 | [diff] [blame] | 50 | TEST_RUN_DIR_PREFIX = 'atest_run_%s_' |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 51 | HELP_DESC = '''Build, install and run Android tests locally.''' |
Simran Basi | 05384b8 | 2018-01-03 16:19:30 -0800 | [diff] [blame] | 52 | REBUILD_MODULE_INFO_FLAG = '--rebuild-module-info' |
Kevin Cheng | 21ea910 | 2018-02-22 10:52:42 -0800 | [diff] [blame] | 53 | CUSTOM_ARG_FLAG = '--' |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 54 | |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 55 | EPILOG_TEXT = ''' |
| 56 | |
| 57 | |
| 58 | - - - - - - - - - |
| 59 | IDENTIFYING TESTS |
| 60 | - - - - - - - - - |
| 61 | |
| 62 | The positional argument <tests> should be a reference to one or more of the |
| 63 | tests you'd like to run. Multiple tests can be run in one command by |
| 64 | separating test references with spaces. |
| 65 | |
| 66 | Usage Template: atest <reference_to_test_1> <reference_to_test_2> |
| 67 | |
| 68 | A <reference_to_test> can be satisfied by the test's MODULE NAME, |
mikehoran | 32861ae | 2017-12-15 11:53:17 -0800 | [diff] [blame] | 69 | MODULE:CLASS, CLASS NAME, TF INTEGRATION TEST or FILE PATH. Explanations |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 70 | and examples of each follow. |
| 71 | |
| 72 | |
| 73 | < MODULE NAME > |
| 74 | |
| 75 | Identifying a test by its module name will run the entire module. Input |
| 76 | the name as it appears in the LOCAL_MODULE or LOCAL_PACKAGE_NAME |
| 77 | variables in that test's Android.mk or Android.bp file. |
| 78 | |
| 79 | Note: Use < TF INTEGRATION TEST > to run non-module tests integrated |
| 80 | directly into TradeFed. |
| 81 | |
| 82 | Examples: |
| 83 | atest FrameworksServicesTests |
| 84 | atest CtsJankDeviceTestCases |
| 85 | |
| 86 | |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 87 | < MODULE:CLASS > |
| 88 | |
| 89 | Identifying a test by its class name will run just the tests in that |
| 90 | class and not the whole module. MODULE:CLASS is the preferred way to run |
| 91 | a single class. MODULE is the same as described above. CLASS is the |
| 92 | name of the test class in the .java file. It can either be the fully |
| 93 | qualified class name or just the basic name. |
| 94 | |
| 95 | Examples: |
| 96 | atest PtsBatteryTestCases:BatteryTest |
| 97 | atest PtsBatteryTestCases:com.google.android.battery.pts.BatteryTest |
| 98 | atest CtsJankDeviceTestCases:CtsDeviceJankUi |
| 99 | |
| 100 | |
| 101 | < CLASS NAME > |
| 102 | |
| 103 | A single class can also be run by referencing the class name without |
| 104 | the module name. However, this will take more time than the equivalent |
| 105 | MODULE:CLASS reference, so we suggest using a MODULE:CLASS reference |
| 106 | whenever possible. |
| 107 | |
| 108 | Examples: |
| 109 | atest ScreenDecorWindowTests |
| 110 | atest com.google.android.battery.pts.BatteryTest |
| 111 | atest CtsDeviceJankUi |
| 112 | |
| 113 | |
mikehoran | 32861ae | 2017-12-15 11:53:17 -0800 | [diff] [blame] | 114 | < TF INTEGRATION TEST > |
| 115 | |
| 116 | To run tests that are integrated directly into TradeFed (non-modules), |
| 117 | input the name as it appears in the output of the "tradefed.sh list |
| 118 | configs" cmd. |
| 119 | |
| 120 | Examples: |
| 121 | atest example/reboot |
| 122 | atest native-benchmark |
| 123 | |
| 124 | |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 125 | < FILE PATH > |
| 126 | |
| 127 | Both module-based tests and integration-based tests can be run by |
| 128 | inputting the path to their test file or dir as appropriate. A single |
| 129 | class can also be run by inputting the path to the class's java file. |
| 130 | Both relative and absolute paths are supported. |
| 131 | |
| 132 | Example - run module from android repo root: |
| 133 | atest cts/tests/jank/jank |
| 134 | |
| 135 | Example - same module but from <repo root>/cts/tests/jank: |
| 136 | atest . |
| 137 | |
| 138 | Example - run just class from android repo root: |
| 139 | atest cts/tests/jank/src/android/jank/cts/ui/CtsDeviceJankUi.java |
| 140 | |
| 141 | Example - run tf integration test from android repo root: |
| 142 | atest tools/tradefederation/contrib/res/config/example/reboot.xml |
| 143 | |
| 144 | |
| 145 | - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 146 | SPECIFYING INDIVIDUAL STEPS: BUILD, INSTALL OR RUN |
| 147 | - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 148 | |
| 149 | The -b, -i and -t options allow you to specify which steps you want to run. |
| 150 | If none of those options are given, then all steps are run. If any of these |
| 151 | options are provided then only the listed steps are run. |
| 152 | |
| 153 | Note: -i alone is not currently support and can only be included with -t. |
| 154 | Both -b and -t can be run alone. |
| 155 | |
| 156 | Examples: |
| 157 | atest -b <test> (just build targets) |
| 158 | atest -bt <test> (build targets, run tests, but skip installing apk) |
| 159 | atest -t <test> (just run test, skip build/install) |
| 160 | atest -it <test> (install and run tests, skip building) |
| 161 | |
| 162 | |
| 163 | - - - - - - - - - - - - - |
| 164 | RUNNING SPECIFIC METHODS |
| 165 | - - - - - - - - - - - - - |
| 166 | |
| 167 | It is possible to run only specific methods within a test class. To run |
| 168 | only specific methods, identify the class in any of the ways supported |
| 169 | for identifying a class (MODULE:CLASS, FILE PATH, etc) and then append the |
| 170 | name of the method or method using the following template: |
| 171 | |
| 172 | <reference_to_class>#<method1>,<method2>,<method3>... |
| 173 | |
| 174 | Examples: |
| 175 | FrameworksServicesTests:ScreenDecorWindowTests#testFlagChange,testRemoval |
| 176 | com.google.android.battery.pts.BatteryTest#testDischarge |
| 177 | |
| 178 | |
| 179 | - - - - - - - - - - - - - |
| 180 | RUNNING MULTIPLE CLASSES |
| 181 | - - - - - - - - - - - - - |
| 182 | |
| 183 | To run multiple classes, deliminate them with spaces just like you would |
| 184 | if running multiple tests. Atest will automatically build and run |
| 185 | multiple classes in the most efficient way possible. |
| 186 | |
| 187 | |
| 188 | Example - two classes in same module: |
| 189 | atest FrameworksServicesTests:ScreenDecorWindowTests FrameworksServicesTest:DimmerTests |
| 190 | |
| 191 | Example - two classes, different modules: |
| 192 | atest FrameworksServicesTests:ScreenDecorWindowTests CtsJankDeviceTestCases:CtsDeviceJankUi |
| 193 | |
Mike Ma | 150a61d | 2017-12-15 10:53:35 -0800 | [diff] [blame] | 194 | |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 195 | - - - - - - - - - - - |
| 196 | REGRESSION DETECTION |
| 197 | - - - - - - - - - - - |
| 198 | |
| 199 | Generate pre-patch or post-patch metrics without running regression detection: |
| 200 | |
| 201 | Example: |
| 202 | atest <test> --generate-baseline <optional iter> |
| 203 | atest <test> --generate-new-metrics <optional iter> |
| 204 | |
| 205 | Local regression detection can be run in three options: |
| 206 | |
| 207 | 1) Provide a folder containing baseline (pre-patch) metrics (generated previously). Atest will |
| 208 | run the tests n (default 5) iterations, generate a new set of post-patch metrics, and |
| 209 | compare those against existing metrics. |
| 210 | |
| 211 | Example: |
| 212 | atest <test> --detect-regression </path/to/baseline> --generate-new-metrics <optional iter> |
| 213 | |
| 214 | 2) Provide a folder containing post-patch metrics (generated previously). Atest will run the |
| 215 | tests n (default 5) iterations, generate a new set of pre-patch metrics, and compare those |
| 216 | against those provided. Note: the developer needs to revert the device/tests to pre-patch |
| 217 | state to generate baseline metrics. |
| 218 | |
| 219 | Example: |
| 220 | atest <test> --detect-regression </path/to/new> --generate-baseline <optional iter> |
| 221 | |
| 222 | 3) Provide 2 folders containing both pre-patch and post-patch metrics. Atest will run no tests |
| 223 | but the regression detection algorithm. |
| 224 | |
| 225 | Example: |
| 226 | atest --detect-regression </path/to/baseline> </path/to/new> |
| 227 | |
| 228 | |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 229 | ''' |
| 230 | |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 231 | |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 232 | def _parse_args(argv): |
| 233 | """Parse command line arguments. |
| 234 | |
| 235 | Args: |
| 236 | argv: A list of arguments. |
| 237 | |
| 238 | Returns: |
| 239 | An argspace.Namespace class instance holding parsed args. |
| 240 | """ |
| 241 | import argparse |
| 242 | parser = argparse.ArgumentParser( |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 243 | description=HELP_DESC, |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 244 | epilog=EPILOG_TEXT, |
mikehoran | 7592624 | 2017-09-07 11:01:35 -0700 | [diff] [blame] | 245 | formatter_class=argparse.RawTextHelpFormatter) |
mikehoran | d229b1b | 2017-12-01 15:23:58 -0800 | [diff] [blame] | 246 | parser.add_argument('tests', nargs='*', help='Tests to build and/or run.') |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 247 | parser.add_argument('-b', '--build', action='append_const', dest='steps', |
| 248 | const=BUILD_STEP, help='Run a build.') |
mikehoran | c327dca | 2017-11-27 16:24:22 -0800 | [diff] [blame] | 249 | parser.add_argument('-i', '--install', action='append_const', dest='steps', |
| 250 | const=INSTALL_STEP, help='Install an APK.') |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 251 | parser.add_argument('-t', '--test', action='append_const', dest='steps', |
mikehoran | 458b2b1 | 2018-02-28 16:07:13 -0800 | [diff] [blame] | 252 | const=TEST_STEP, |
| 253 | help='Run the tests. WARNING: Many test configs force cleanup ' |
| 254 | 'of device after test run. In this case, -d must be used in previous ' |
| 255 | 'test run to disable cleanup, for -t to work. Otherwise, ' |
| 256 | 'device will need to be setup again with -i.') |
easoncylee | 8809be0 | 2018-03-27 12:28:07 +0800 | [diff] [blame^] | 257 | parser.add_argument('-s', '--serial', |
| 258 | help='The device to run the test on.') |
mikehoran | 458b2b1 | 2018-02-28 16:07:13 -0800 | [diff] [blame] | 259 | parser.add_argument('-d', '--disable-teardown', action='store_true', |
| 260 | help='Disables test teardown and cleanup.') |
Simran Basi | 05384b8 | 2018-01-03 16:19:30 -0800 | [diff] [blame] | 261 | parser.add_argument('-m', REBUILD_MODULE_INFO_FLAG, action='store_true', |
| 262 | help='Forces a rebuild of the module-info.json file. ' |
| 263 | 'This may be necessary following a repo sync or ' |
| 264 | 'when writing a new test.') |
Simran Basi | 67ba20b | 2017-11-01 19:01:48 -0700 | [diff] [blame] | 265 | parser.add_argument('-w', '--wait-for-debugger', action='store_true', |
| 266 | help='Only for instrumentation tests. Waits for ' |
| 267 | 'debugger prior to execution.') |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 268 | parser.add_argument('-v', '--verbose', action='store_true', |
| 269 | help='Display DEBUG level logging.') |
Mike Ma | 150a61d | 2017-12-15 10:53:35 -0800 | [diff] [blame] | 270 | parser.add_argument('--generate-baseline', nargs='?', type=int, const=5, default=0, |
| 271 | help='Generate baseline metrics, run 5 iterations by default. ' |
| 272 | 'Provide an int argument to specify # iterations.') |
| 273 | parser.add_argument('--generate-new-metrics', nargs='?', type=int, const=5, default=0, |
| 274 | help='Generate new metrics, run 5 iterations by default. ' |
| 275 | 'Provide an int argument to specify # iterations.') |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 276 | parser.add_argument('--detect-regression', nargs='*', |
| 277 | help='Run regression detection algorithm. Supply ' |
| 278 | 'path to baseline and/or new metrics folders.') |
Kevin Cheng | 21ea910 | 2018-02-22 10:52:42 -0800 | [diff] [blame] | 279 | # This arg actually doesn't consume anything, it's primarily used for the |
| 280 | # help description and creating custom_args in the NameSpace object. |
| 281 | parser.add_argument('--', dest='custom_args', nargs='*', |
| 282 | help='Specify custom args for the test runners. ' |
| 283 | 'Everything after -- will be consumed as custom ' |
| 284 | 'args.') |
| 285 | # Store everything after '--' in custom_args. |
| 286 | pruned_argv = argv |
| 287 | custom_args_index = None |
| 288 | if CUSTOM_ARG_FLAG in argv: |
| 289 | custom_args_index = argv.index(CUSTOM_ARG_FLAG) |
| 290 | pruned_argv = argv[:custom_args_index] |
| 291 | args = parser.parse_args(pruned_argv) |
| 292 | args.custom_args = [] |
| 293 | if custom_args_index is not None: |
| 294 | args.custom_args = argv[custom_args_index+1:] |
| 295 | return args |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 296 | |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 297 | |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 298 | def _configure_logging(verbose): |
| 299 | """Configure the logger. |
| 300 | |
| 301 | Args: |
| 302 | verbose: A boolean. If true display DEBUG level logs. |
| 303 | """ |
| 304 | if verbose: |
| 305 | logging.basicConfig(level=logging.DEBUG) |
| 306 | else: |
| 307 | logging.basicConfig(level=logging.INFO) |
| 308 | |
| 309 | |
| 310 | def _missing_environment_variables(): |
| 311 | """Verify the local environment has been set up to run atest. |
| 312 | |
| 313 | Returns: |
| 314 | List of strings of any missing environment variables. |
| 315 | """ |
| 316 | missing = filter(None, [x for x in EXPECTED_VARS if not os.environ.get(x)]) |
| 317 | if missing: |
| 318 | logging.error('Local environment doesn\'t appear to have been ' |
| 319 | 'initialized. Did you remember to run lunch? Expected ' |
| 320 | 'Environment Variables: %s.', missing) |
| 321 | return missing |
| 322 | |
| 323 | |
mikehoran | 95091b2 | 2017-10-31 15:55:26 -0700 | [diff] [blame] | 324 | def make_test_run_dir(): |
| 325 | """Make the test run dir in tmp. |
| 326 | |
| 327 | Returns: |
| 328 | A string of the dir path. |
| 329 | """ |
| 330 | utc_epoch_time = int(time.time()) |
| 331 | prefix = TEST_RUN_DIR_PREFIX % utc_epoch_time |
| 332 | return tempfile.mkdtemp(prefix=prefix) |
| 333 | |
| 334 | |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 335 | def run_tests(run_commands): |
| 336 | """Shell out and execute tradefed run commands. |
| 337 | |
| 338 | Args: |
| 339 | run_commands: A list of strings of Tradefed run commands. |
| 340 | """ |
| 341 | logging.info('Running tests') |
| 342 | # TODO: Build result parser for run command. Until then display raw stdout. |
| 343 | for run_command in run_commands: |
| 344 | logging.debug('Executing command: %s', run_command) |
| 345 | subprocess.check_call(run_command, shell=True, stderr=subprocess.STDOUT) |
| 346 | |
| 347 | |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 348 | def get_extra_args(args): |
| 349 | """Get extra args for test runners. |
| 350 | |
| 351 | Args: |
| 352 | args: arg parsed object. |
| 353 | |
| 354 | Returns: |
| 355 | Dict of extra args for test runners to utilize. |
| 356 | """ |
| 357 | extra_args = {} |
| 358 | if args.wait_for_debugger: |
| 359 | extra_args[constants.WAIT_FOR_DEBUGGER] = None |
| 360 | steps = args.steps or ALL_STEPS |
| 361 | if INSTALL_STEP not in steps: |
| 362 | extra_args[constants.DISABLE_INSTALL] = None |
mikehoran | 458b2b1 | 2018-02-28 16:07:13 -0800 | [diff] [blame] | 363 | if args.disable_teardown: |
| 364 | extra_args[constants.DISABLE_TEARDOWN] = args.disable_teardown |
Mike Ma | 150a61d | 2017-12-15 10:53:35 -0800 | [diff] [blame] | 365 | if args.generate_baseline: |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 366 | extra_args[constants.PRE_PATCH_ITERATIONS] = args.generate_baseline |
easoncylee | 8809be0 | 2018-03-27 12:28:07 +0800 | [diff] [blame^] | 367 | if args.serial: |
| 368 | extra_args[constants.SERIAL] = args.serial |
Mike Ma | 150a61d | 2017-12-15 10:53:35 -0800 | [diff] [blame] | 369 | if args.generate_new_metrics: |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 370 | extra_args[constants.POST_PATCH_ITERATIONS] = args.generate_new_metrics |
Kevin Cheng | 21ea910 | 2018-02-22 10:52:42 -0800 | [diff] [blame] | 371 | if args.custom_args: |
| 372 | extra_args[constants.CUSTOM_ARGS] = args.custom_args |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 373 | return extra_args |
| 374 | |
| 375 | |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 376 | def _get_regression_detection_args(args, results_dir): |
| 377 | """Get args for regression detection test runners. |
| 378 | |
| 379 | Args: |
| 380 | args: parsed args object. |
| 381 | results_dir: string directory to store atest results. |
| 382 | |
| 383 | Returns: |
| 384 | Dict of args for regression detection test runner to utilize. |
| 385 | """ |
| 386 | regression_args = {} |
| 387 | pre_patch_folder = (os.path.join(results_dir, 'baseline-metrics') if args.generate_baseline |
| 388 | else args.detect_regression.pop(0)) |
| 389 | post_patch_folder = (os.path.join(results_dir, 'new-metrics') if args.generate_new_metrics |
| 390 | else args.detect_regression.pop(0)) |
| 391 | regression_args[constants.PRE_PATCH_FOLDER] = pre_patch_folder |
| 392 | regression_args[constants.POST_PATCH_FOLDER] = post_patch_folder |
| 393 | return regression_args |
| 394 | |
| 395 | |
| 396 | def _will_run_tests(args): |
| 397 | """Determine if there are tests to run. |
| 398 | |
| 399 | Currently only used by detect_regression to skip the test if just running regression detection. |
| 400 | |
| 401 | Args: |
| 402 | args: parsed args object. |
| 403 | |
| 404 | Returns: |
| 405 | True if there are tests to run, false otherwise. |
| 406 | """ |
| 407 | return not (args.detect_regression and len(args.detect_regression) == 2) |
| 408 | |
| 409 | |
| 410 | def _has_valid_regression_detection_args(args): |
| 411 | """Validate regression detection args. |
| 412 | |
| 413 | Args: |
| 414 | args: parsed args object. |
| 415 | |
| 416 | Returns: |
| 417 | True if args are valid |
| 418 | """ |
| 419 | if args.generate_baseline and args.generate_new_metrics: |
| 420 | logging.error('Cannot collect both baseline and new metrics at the same time.') |
| 421 | return False |
| 422 | if args.detect_regression is not None: |
| 423 | if not args.detect_regression: |
| 424 | logging.error('Need to specify at least 1 arg for regression detection.') |
| 425 | return False |
| 426 | elif len(args.detect_regression) == 1: |
| 427 | if args.generate_baseline or args.generate_new_metrics: |
| 428 | return True |
| 429 | logging.error('Need to specify --generate-baseline or --generate-new-metrics.') |
| 430 | return False |
| 431 | elif len(args.detect_regression) == 2: |
| 432 | if args.generate_baseline: |
| 433 | logging.error('Specified 2 metric paths and --generate-baseline, ' |
| 434 | 'either drop --generate-baseline or drop a path') |
| 435 | return False |
| 436 | if args.generate_new_metrics: |
| 437 | logging.error('Specified 2 metric paths and --generate-new-metrics, ' |
| 438 | 'either drop --generate-new-metrics or drop a path') |
| 439 | return False |
| 440 | return True |
| 441 | else: |
| 442 | logging.error('Specified more than 2 metric paths.') |
| 443 | return False |
| 444 | return True |
| 445 | |
| 446 | |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 447 | def main(argv): |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 448 | """Entry point of atest script. |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 449 | |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 450 | Args: |
| 451 | argv: A list of arguments. |
Kevin Cheng | 09c2a2c | 2017-12-15 12:52:46 -0800 | [diff] [blame] | 452 | |
| 453 | Returns: |
| 454 | Exit code. |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 455 | """ |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 456 | args = _parse_args(argv) |
mikehoran | be9102f | 2017-08-04 16:04:03 -0700 | [diff] [blame] | 457 | _configure_logging(args.verbose) |
| 458 | if _missing_environment_variables(): |
Dan Shi | fa016d1 | 2018-02-02 00:37:19 -0800 | [diff] [blame] | 459 | return constants.EXIT_CODE_ENV_NOT_SETUP |
Mike Ma | 150a61d | 2017-12-15 10:53:35 -0800 | [diff] [blame] | 460 | if args.generate_baseline and args.generate_new_metrics: |
| 461 | logging.error('Cannot collect both baseline and new metrics at the same time.') |
Dan Shi | fa016d1 | 2018-02-02 00:37:19 -0800 | [diff] [blame] | 462 | return constants.EXIT_CODE_ERROR |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 463 | if not _has_valid_regression_detection_args(args): |
| 464 | return constants.EXIT_CODE_ERROR |
mikehoran | 95091b2 | 2017-10-31 15:55:26 -0700 | [diff] [blame] | 465 | results_dir = make_test_run_dir() |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 466 | mod_info = module_info.ModuleInfo(force_build=args.rebuild_module_info) |
| 467 | translator = cli_translator.CLITranslator(module_info=mod_info) |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 468 | build_targets = set() |
| 469 | test_infos = set() |
| 470 | if _will_run_tests(args): |
| 471 | try: |
| 472 | build_targets, test_infos = translator.translate(args.tests) |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 473 | except atest_error.TestDiscoveryException: |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 474 | logging.exception('Error occured in test discovery:') |
| 475 | logging.info('This can happen after a repo sync or if the test is ' |
| 476 | 'new. Running: with "%s" may resolve the issue.', |
| 477 | REBUILD_MODULE_INFO_FLAG) |
| 478 | return constants.EXIT_CODE_TEST_NOT_FOUND |
Kevin Cheng | 8b2c94c | 2017-12-18 14:43:26 -0800 | [diff] [blame] | 479 | build_targets |= test_runner_handler.get_test_runner_reqs(mod_info, |
| 480 | test_infos) |
| 481 | # We don't initialize module-info if it already exists or |
| 482 | # --rebuild-module-info is passed in. Add it to the list of build targets to |
| 483 | # keep the file up to date. |
| 484 | build_targets.add(mod_info.module_info_target) |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 485 | extra_args = get_extra_args(args) |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 486 | if args.detect_regression: |
| 487 | build_targets |= (regression_test_runner.RegressionTestRunner('') |
| 488 | .get_test_runner_build_reqs()) |
mikehoran | c327dca | 2017-11-27 16:24:22 -0800 | [diff] [blame] | 489 | # args.steps will be None if none of -bit set, else list of params set. |
| 490 | steps = args.steps if args.steps else ALL_STEPS |
| 491 | if BUILD_STEP in steps: |
mikehoran | c80dc53 | 2017-11-14 14:30:06 -0800 | [diff] [blame] | 492 | success = atest_utils.build(build_targets, args.verbose) |
| 493 | if not success: |
Dan Shi | fa016d1 | 2018-02-02 00:37:19 -0800 | [diff] [blame] | 494 | return constants.EXIT_CODE_BUILD_FAILURE |
mikehoran | c327dca | 2017-11-27 16:24:22 -0800 | [diff] [blame] | 495 | elif TEST_STEP not in steps: |
| 496 | logging.warn('Install step without test step currently not ' |
| 497 | 'supported, installing AND testing instead.') |
| 498 | steps.append(TEST_STEP) |
| 499 | if TEST_STEP in steps: |
Kevin Cheng | 7edb0b9 | 2017-12-14 15:00:25 -0800 | [diff] [blame] | 500 | test_runner_handler.run_all_tests(results_dir, test_infos, extra_args) |
Mike Ma | 0126b9b | 2018-01-11 19:11:16 -0800 | [diff] [blame] | 501 | if args.detect_regression: |
| 502 | regression_args = _get_regression_detection_args(args, results_dir) |
| 503 | regression_test_runner.RegressionTestRunner('').run_tests(None, regression_args) |
Dan Shi | fa016d1 | 2018-02-02 00:37:19 -0800 | [diff] [blame] | 504 | return constants.EXIT_CODE_SUCCESS |
mikehoran | 63d61b4 | 2017-07-28 15:28:50 -0700 | [diff] [blame] | 505 | |
Simran Basi | 259a2b5 | 2017-06-21 16:14:07 -0700 | [diff] [blame] | 506 | if __name__ == '__main__': |
| 507 | sys.exit(main(sys.argv[1:])) |