Aart Bik | d432acd | 2018-03-08 11:48:27 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2016 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 | """Performs bisection bug search on methods and optimizations. |
| 18 | |
| 19 | See README.md. |
| 20 | |
| 21 | Example usage: |
| 22 | ./bisection-search.py -cp classes.dex --expected-output output Test |
| 23 | """ |
| 24 | |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 25 | import abc |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 26 | import argparse |
Aart Bik | e034748 | 2016-09-20 14:34:13 -0700 | [diff] [blame] | 27 | import os |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 28 | import re |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 29 | import shlex |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 30 | import sys |
Aart Bik | e034748 | 2016-09-20 14:34:13 -0700 | [diff] [blame] | 31 | |
| 32 | from subprocess import call |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 33 | from tempfile import NamedTemporaryFile |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 34 | |
Aart Bik | e034748 | 2016-09-20 14:34:13 -0700 | [diff] [blame] | 35 | sys.path.append(os.path.dirname(os.path.dirname( |
| 36 | os.path.realpath(__file__)))) |
| 37 | |
| 38 | from common.common import DeviceTestEnv |
| 39 | from common.common import FatalError |
| 40 | from common.common import GetEnvVariableOrError |
| 41 | from common.common import HostTestEnv |
| 42 | from common.common import LogSeverity |
| 43 | from common.common import RetCode |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 44 | |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 45 | |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 46 | # Passes that are never disabled during search process because disabling them |
| 47 | # would compromise correctness. |
| 48 | MANDATORY_PASSES = ['dex_cache_array_fixups_arm', |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 49 | 'instruction_simplifier$before_codegen', |
| 50 | 'pc_relative_fixups_x86', |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 51 | 'x86_memory_operand_generation'] |
| 52 | |
| 53 | # Passes that show up as optimizations in compiler verbose output but aren't |
| 54 | # driven by run-passes mechanism. They are mandatory and will always run, we |
| 55 | # never pass them to --run-passes. |
| 56 | NON_PASSES = ['builder', 'prepare_for_register_allocation', |
| 57 | 'liveness', 'register'] |
| 58 | |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 59 | # If present in raw cmd, this tag will be replaced with runtime arguments |
| 60 | # controlling the bisection search. Otherwise arguments will be placed on second |
| 61 | # position in the command. |
| 62 | RAW_CMD_RUNTIME_ARGS_TAG = '{ARGS}' |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 63 | |
David Srbecky | 6355d69 | 2020-03-26 14:10:26 +0000 | [diff] [blame] | 64 | # Default boot image path relative to ANDROID_HOST_OUT. |
| 65 | DEFAULT_IMAGE_RELATIVE_PATH = 'apex/com.android.art/javalib/boot.art' |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 66 | |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 67 | class Dex2OatWrapperTestable(object): |
| 68 | """Class representing a testable compilation. |
| 69 | |
| 70 | Accepts filters on compiled methods and optimization passes. |
| 71 | """ |
| 72 | |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 73 | def __init__(self, base_cmd, test_env, expected_retcode=None, |
| 74 | output_checker=None, verbose=False): |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 75 | """Constructor. |
| 76 | |
| 77 | Args: |
| 78 | base_cmd: list of strings, base command to run. |
| 79 | test_env: ITestEnv. |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 80 | expected_retcode: RetCode, expected normalized return code. |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 81 | output_checker: IOutputCheck, output checker. |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 82 | verbose: bool, enable verbose output. |
| 83 | """ |
| 84 | self._base_cmd = base_cmd |
| 85 | self._test_env = test_env |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 86 | self._expected_retcode = expected_retcode |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 87 | self._output_checker = output_checker |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 88 | self._compiled_methods_path = self._test_env.CreateFile('compiled_methods') |
| 89 | self._passes_to_run_path = self._test_env.CreateFile('run_passes') |
| 90 | self._verbose = verbose |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 91 | if RAW_CMD_RUNTIME_ARGS_TAG in self._base_cmd: |
| 92 | self._arguments_position = self._base_cmd.index(RAW_CMD_RUNTIME_ARGS_TAG) |
| 93 | self._base_cmd.pop(self._arguments_position) |
| 94 | else: |
| 95 | self._arguments_position = 1 |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 96 | |
| 97 | def Test(self, compiled_methods, passes_to_run=None): |
| 98 | """Tests compilation with compiled_methods and run_passes switches active. |
| 99 | |
| 100 | If compiled_methods is None then compiles all methods. |
| 101 | If passes_to_run is None then runs default passes. |
| 102 | |
| 103 | Args: |
| 104 | compiled_methods: list of strings representing methods to compile or None. |
| 105 | passes_to_run: list of strings representing passes to run or None. |
| 106 | |
| 107 | Returns: |
| 108 | True if test passes with given settings. False otherwise. |
| 109 | """ |
| 110 | if self._verbose: |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 111 | print('Testing methods: {0} passes: {1}.'.format( |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 112 | compiled_methods, passes_to_run)) |
| 113 | cmd = self._PrepareCmd(compiled_methods=compiled_methods, |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 114 | passes_to_run=passes_to_run) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 115 | (output, ret_code) = self._test_env.RunCommand( |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 116 | cmd, LogSeverity.ERROR) |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 117 | res = True |
| 118 | if self._expected_retcode: |
| 119 | res = self._expected_retcode == ret_code |
| 120 | if self._output_checker: |
| 121 | res = res and self._output_checker.Check(output) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 122 | if self._verbose: |
| 123 | print('Test passed: {0}.'.format(res)) |
| 124 | return res |
| 125 | |
| 126 | def GetAllMethods(self): |
| 127 | """Get methods compiled during the test. |
| 128 | |
| 129 | Returns: |
| 130 | List of strings representing methods compiled during the test. |
| 131 | |
| 132 | Raises: |
| 133 | FatalError: An error occurred when retrieving methods list. |
| 134 | """ |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 135 | cmd = self._PrepareCmd() |
| 136 | (output, _) = self._test_env.RunCommand(cmd, LogSeverity.INFO) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 137 | match_methods = re.findall(r'Building ([^\n]+)\n', output) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 138 | if not match_methods: |
| 139 | raise FatalError('Failed to retrieve methods list. ' |
| 140 | 'Not recognized output format.') |
| 141 | return match_methods |
| 142 | |
| 143 | def GetAllPassesForMethod(self, compiled_method): |
| 144 | """Get all optimization passes ran for a method during the test. |
| 145 | |
| 146 | Args: |
| 147 | compiled_method: string representing method to compile. |
| 148 | |
| 149 | Returns: |
| 150 | List of strings representing passes ran for compiled_method during test. |
| 151 | |
| 152 | Raises: |
| 153 | FatalError: An error occurred when retrieving passes list. |
| 154 | """ |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 155 | cmd = self._PrepareCmd(compiled_methods=[compiled_method]) |
| 156 | (output, _) = self._test_env.RunCommand(cmd, LogSeverity.INFO) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 157 | match_passes = re.findall(r'Starting pass: ([^\n]+)\n', output) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 158 | if not match_passes: |
| 159 | raise FatalError('Failed to retrieve passes list. ' |
| 160 | 'Not recognized output format.') |
| 161 | return [p for p in match_passes if p not in NON_PASSES] |
| 162 | |
Aart Bik | e034748 | 2016-09-20 14:34:13 -0700 | [diff] [blame] | 163 | def _PrepareCmd(self, compiled_methods=None, passes_to_run=None): |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 164 | """Prepare command to run.""" |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 165 | cmd = self._base_cmd[0:self._arguments_position] |
| 166 | # insert additional arguments before the first argument |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 167 | if passes_to_run is not None: |
| 168 | self._test_env.WriteLines(self._passes_to_run_path, passes_to_run) |
| 169 | cmd += ['-Xcompiler-option', '--run-passes={0}'.format( |
| 170 | self._passes_to_run_path)] |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 171 | cmd += ['-Xcompiler-option', '--runtime-arg', '-Xcompiler-option', |
| 172 | '-verbose:compiler', '-Xcompiler-option', '-j1'] |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 173 | cmd += self._base_cmd[self._arguments_position:] |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 174 | return cmd |
| 175 | |
| 176 | |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 177 | class IOutputCheck(object): |
| 178 | """Abstract output checking class. |
| 179 | |
| 180 | Checks if output is correct. |
| 181 | """ |
| 182 | __meta_class__ = abc.ABCMeta |
| 183 | |
| 184 | @abc.abstractmethod |
| 185 | def Check(self, output): |
| 186 | """Check if output is correct. |
| 187 | |
| 188 | Args: |
| 189 | output: string, output to check. |
| 190 | |
| 191 | Returns: |
| 192 | boolean, True if output is correct, False otherwise. |
| 193 | """ |
| 194 | |
| 195 | |
| 196 | class EqualsOutputCheck(IOutputCheck): |
| 197 | """Concrete output checking class checking for equality to expected output.""" |
| 198 | |
| 199 | def __init__(self, expected_output): |
| 200 | """Constructor. |
| 201 | |
| 202 | Args: |
| 203 | expected_output: string, expected output. |
| 204 | """ |
| 205 | self._expected_output = expected_output |
| 206 | |
| 207 | def Check(self, output): |
| 208 | """See base class.""" |
| 209 | return self._expected_output == output |
| 210 | |
| 211 | |
| 212 | class ExternalScriptOutputCheck(IOutputCheck): |
| 213 | """Concrete output checking class calling an external script. |
| 214 | |
| 215 | The script should accept two arguments, path to expected output and path to |
| 216 | program output. It should exit with 0 return code if outputs are equivalent |
| 217 | and with different return code otherwise. |
| 218 | """ |
| 219 | |
| 220 | def __init__(self, script_path, expected_output_path, logfile): |
| 221 | """Constructor. |
| 222 | |
| 223 | Args: |
| 224 | script_path: string, path to checking script. |
| 225 | expected_output_path: string, path to file with expected output. |
| 226 | logfile: file handle, logfile. |
| 227 | """ |
| 228 | self._script_path = script_path |
| 229 | self._expected_output_path = expected_output_path |
| 230 | self._logfile = logfile |
| 231 | |
| 232 | def Check(self, output): |
| 233 | """See base class.""" |
| 234 | ret_code = None |
| 235 | with NamedTemporaryFile(mode='w', delete=False) as temp_file: |
| 236 | temp_file.write(output) |
| 237 | temp_file.flush() |
| 238 | ret_code = call( |
| 239 | [self._script_path, self._expected_output_path, temp_file.name], |
| 240 | stdout=self._logfile, stderr=self._logfile, universal_newlines=True) |
| 241 | return ret_code == 0 |
| 242 | |
| 243 | |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 244 | def BinarySearch(start, end, test): |
| 245 | """Binary search integers using test function to guide the process.""" |
| 246 | while start < end: |
| 247 | mid = (start + end) // 2 |
| 248 | if test(mid): |
| 249 | start = mid + 1 |
| 250 | else: |
| 251 | end = mid |
| 252 | return start |
| 253 | |
| 254 | |
| 255 | def FilterPasses(passes, cutoff_idx): |
| 256 | """Filters passes list according to cutoff_idx but keeps mandatory passes.""" |
| 257 | return [opt_pass for idx, opt_pass in enumerate(passes) |
| 258 | if opt_pass in MANDATORY_PASSES or idx < cutoff_idx] |
| 259 | |
| 260 | |
| 261 | def BugSearch(testable): |
| 262 | """Find buggy (method, optimization pass) pair for a given testable. |
| 263 | |
| 264 | Args: |
| 265 | testable: Dex2OatWrapperTestable. |
| 266 | |
| 267 | Returns: |
| 268 | (string, string) tuple. First element is name of method which when compiled |
| 269 | exposes test failure. Second element is name of optimization pass such that |
| 270 | for aforementioned method running all passes up to and excluding the pass |
| 271 | results in test passing but running all passes up to and including the pass |
| 272 | results in test failing. |
| 273 | |
| 274 | (None, None) if test passes when compiling all methods. |
| 275 | (string, None) if a method is found which exposes the failure, but the |
| 276 | failure happens even when running just mandatory passes. |
| 277 | |
| 278 | Raises: |
| 279 | FatalError: Testable fails with no methods compiled. |
| 280 | AssertionError: Method failed for all passes when bisecting methods, but |
| 281 | passed when bisecting passes. Possible sporadic failure. |
| 282 | """ |
| 283 | all_methods = testable.GetAllMethods() |
| 284 | faulty_method_idx = BinarySearch( |
| 285 | 0, |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 286 | len(all_methods) + 1, |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 287 | lambda mid: testable.Test(all_methods[0:mid])) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 288 | if faulty_method_idx == len(all_methods) + 1: |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 289 | return (None, None) |
| 290 | if faulty_method_idx == 0: |
Wojciech Staszkiewicz | f68312d | 2016-09-26 17:39:26 -0700 | [diff] [blame] | 291 | raise FatalError('Testable fails with no methods compiled.') |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 292 | faulty_method = all_methods[faulty_method_idx - 1] |
| 293 | all_passes = testable.GetAllPassesForMethod(faulty_method) |
| 294 | faulty_pass_idx = BinarySearch( |
| 295 | 0, |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 296 | len(all_passes) + 1, |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 297 | lambda mid: testable.Test([faulty_method], |
| 298 | FilterPasses(all_passes, mid))) |
| 299 | if faulty_pass_idx == 0: |
| 300 | return (faulty_method, None) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 301 | assert faulty_pass_idx != len(all_passes) + 1, ('Method must fail for some ' |
| 302 | 'passes.') |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 303 | faulty_pass = all_passes[faulty_pass_idx - 1] |
| 304 | return (faulty_method, faulty_pass) |
| 305 | |
| 306 | |
| 307 | def PrepareParser(): |
| 308 | """Prepares argument parser.""" |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 309 | parser = argparse.ArgumentParser( |
| 310 | description='Tool for finding compiler bugs. Either --raw-cmd or both ' |
| 311 | '-cp and --class are required.') |
| 312 | command_opts = parser.add_argument_group('dalvikvm command options') |
| 313 | command_opts.add_argument('-cp', '--classpath', type=str, help='classpath') |
| 314 | command_opts.add_argument('--class', dest='classname', type=str, |
| 315 | help='name of main class') |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 316 | command_opts.add_argument('--lib', type=str, default='libart.so', |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 317 | help='lib to use, default: libart.so') |
| 318 | command_opts.add_argument('--dalvikvm-option', dest='dalvikvm_opts', |
| 319 | metavar='OPT', nargs='*', default=[], |
| 320 | help='additional dalvikvm option') |
| 321 | command_opts.add_argument('--arg', dest='test_args', nargs='*', default=[], |
| 322 | metavar='ARG', help='argument passed to test') |
| 323 | command_opts.add_argument('--image', type=str, help='path to image') |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 324 | command_opts.add_argument('--raw-cmd', type=str, |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 325 | help='bisect with this command, ignore other ' |
| 326 | 'command options') |
| 327 | bisection_opts = parser.add_argument_group('bisection options') |
| 328 | bisection_opts.add_argument('--64', dest='x64', action='store_true', |
| 329 | default=False, help='x64 mode') |
| 330 | bisection_opts.add_argument( |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 331 | '--device', action='store_true', default=False, help='run on device') |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 332 | bisection_opts.add_argument( |
| 333 | '--device-serial', help='device serial number, implies --device') |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 334 | bisection_opts.add_argument('--expected-output', type=str, |
| 335 | help='file containing expected output') |
| 336 | bisection_opts.add_argument( |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 337 | '--expected-retcode', type=str, help='expected normalized return code', |
| 338 | choices=[RetCode.SUCCESS.name, RetCode.TIMEOUT.name, RetCode.ERROR.name]) |
| 339 | bisection_opts.add_argument( |
| 340 | '--check-script', type=str, |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 341 | help='script comparing output and expected output') |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 342 | bisection_opts.add_argument( |
| 343 | '--logfile', type=str, help='custom logfile location') |
| 344 | bisection_opts.add_argument('--cleanup', action='store_true', |
| 345 | default=False, help='clean up after bisecting') |
| 346 | bisection_opts.add_argument('--timeout', type=int, default=60, |
| 347 | help='if timeout seconds pass assume test failed') |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 348 | bisection_opts.add_argument('--verbose', action='store_true', |
| 349 | default=False, help='enable verbose output') |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 350 | return parser |
| 351 | |
| 352 | |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 353 | def PrepareBaseCommand(args, classpath): |
| 354 | """Prepares base command used to run test.""" |
| 355 | if args.raw_cmd: |
| 356 | return shlex.split(args.raw_cmd) |
| 357 | else: |
| 358 | base_cmd = ['dalvikvm64'] if args.x64 else ['dalvikvm32'] |
| 359 | if not args.device: |
| 360 | base_cmd += ['-XXlib:{0}'.format(args.lib)] |
| 361 | if not args.image: |
Wojciech Staszkiewicz | 698e4b3 | 2016-09-16 13:44:09 -0700 | [diff] [blame] | 362 | image_path = (GetEnvVariableOrError('ANDROID_HOST_OUT') + |
| 363 | DEFAULT_IMAGE_RELATIVE_PATH) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 364 | else: |
| 365 | image_path = args.image |
| 366 | base_cmd += ['-Ximage:{0}'.format(image_path)] |
| 367 | if args.dalvikvm_opts: |
| 368 | base_cmd += args.dalvikvm_opts |
| 369 | base_cmd += ['-cp', classpath, args.classname] + args.test_args |
| 370 | return base_cmd |
| 371 | |
| 372 | |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 373 | def main(): |
| 374 | # Parse arguments |
| 375 | parser = PrepareParser() |
| 376 | args = parser.parse_args() |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 377 | if not args.raw_cmd and (not args.classpath or not args.classname): |
| 378 | parser.error('Either --raw-cmd or both -cp and --class are required') |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 379 | if args.device_serial: |
| 380 | args.device = True |
| 381 | if args.expected_retcode: |
| 382 | args.expected_retcode = RetCode[args.expected_retcode] |
| 383 | if not args.expected_retcode and not args.check_script: |
| 384 | args.expected_retcode = RetCode.SUCCESS |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 385 | |
| 386 | # Prepare environment |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 387 | classpath = args.classpath |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 388 | if args.device: |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 389 | test_env = DeviceTestEnv( |
| 390 | 'bisection_search_', args.cleanup, args.logfile, args.timeout, |
| 391 | args.device_serial) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 392 | if classpath: |
| 393 | classpath = test_env.PushClasspath(classpath) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 394 | else: |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 395 | test_env = HostTestEnv( |
| 396 | 'bisection_search_', args.cleanup, args.logfile, args.timeout, args.x64) |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 397 | base_cmd = PrepareBaseCommand(args, classpath) |
| 398 | output_checker = None |
| 399 | if args.expected_output: |
| 400 | if args.check_script: |
| 401 | output_checker = ExternalScriptOutputCheck( |
| 402 | args.check_script, args.expected_output, test_env.logfile) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 403 | else: |
Wojciech Staszkiewicz | 8637994 | 2016-09-01 14:36:13 -0700 | [diff] [blame] | 404 | with open(args.expected_output, 'r') as expected_output_file: |
| 405 | output_checker = EqualsOutputCheck(expected_output_file.read()) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 406 | |
| 407 | # Perform the search |
| 408 | try: |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 409 | testable = Dex2OatWrapperTestable(base_cmd, test_env, args.expected_retcode, |
| 410 | output_checker, args.verbose) |
Wojciech Staszkiewicz | f68312d | 2016-09-26 17:39:26 -0700 | [diff] [blame] | 411 | if testable.Test(compiled_methods=[]): |
| 412 | (method, opt_pass) = BugSearch(testable) |
| 413 | else: |
| 414 | print('Testable fails with no methods compiled.') |
| 415 | sys.exit(1) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 416 | except Exception as e: |
Wojciech Staszkiewicz | 0d0fd4a | 2016-09-07 18:52:52 -0700 | [diff] [blame] | 417 | print('Error occurred.\nLogfile: {0}'.format(test_env.logfile.name)) |
Wojciech Staszkiewicz | 0fa3cbd | 2016-08-11 14:04:20 -0700 | [diff] [blame] | 418 | test_env.logfile.write('Exception: {0}\n'.format(e)) |
| 419 | raise |
| 420 | |
| 421 | # Report results |
| 422 | if method is None: |
| 423 | print('Couldn\'t find any bugs.') |
| 424 | elif opt_pass is None: |
| 425 | print('Faulty method: {0}. Fails with just mandatory passes.'.format( |
| 426 | method)) |
| 427 | else: |
| 428 | print('Faulty method and pass: {0}, {1}.'.format(method, opt_pass)) |
| 429 | print('Logfile: {0}'.format(test_env.logfile.name)) |
| 430 | sys.exit(0) |
| 431 | |
| 432 | |
| 433 | if __name__ == '__main__': |
| 434 | main() |