blob: 54f9bb494e29f05dc5f6ad07ffde75685681cc57 [file] [log] [blame]
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -07001#!/usr/bin/env python3.4
Aart Bik7593b992016-08-17 16:51:12 -07002#
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
17import abc
18import argparse
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070019import filecmp
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070020import os
21import shlex
22import shutil
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -070023import subprocess
Aart Bik7593b992016-08-17 16:51:12 -070024import sys
Aart Bik7593b992016-08-17 16:51:12 -070025
Aart Bike0347482016-09-20 14:34:13 -070026from glob import glob
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -070027from subprocess import DEVNULL
Aart Bik7593b992016-08-17 16:51:12 -070028from tempfile import mkdtemp
Aart Bik7593b992016-08-17 16:51:12 -070029
Wojciech Staszkiewiczf64837d2016-09-15 11:41:16 -070030sys.path.append(os.path.dirname(os.path.dirname(
31 os.path.realpath(__file__))))
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070032
Aart Bike0347482016-09-20 14:34:13 -070033from common.common import RetCode
34from common.common import CommandListToCommandString
35from common.common import FatalError
36from common.common import GetJackClassPath
37from common.common import GetEnvVariableOrError
38from common.common import RunCommand
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -070039from common.common import RunCommandForOutput
Aart Bike0347482016-09-20 14:34:13 -070040from common.common import DeviceTestEnv
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070041
42# Return codes supported by bisection bug search.
43BISECTABLE_RET_CODES = (RetCode.SUCCESS, RetCode.ERROR, RetCode.TIMEOUT)
Aart Bik7593b992016-08-17 16:51:12 -070044
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070045
Aart Bikb16d4132016-08-19 15:45:11 -070046def GetExecutionModeRunner(device, mode):
Aart Bik7593b992016-08-17 16:51:12 -070047 """Returns a runner for the given execution mode.
48
49 Args:
Aart Bikb16d4132016-08-19 15:45:11 -070050 device: string, target device serial number (or None)
Aart Bik7593b992016-08-17 16:51:12 -070051 mode: string, execution mode
52 Returns:
53 TestRunner with given execution mode
54 Raises:
55 FatalError: error for unknown execution mode
56 """
57 if mode == 'ri':
58 return TestRunnerRIOnHost()
59 if mode == 'hint':
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070060 return TestRunnerArtIntOnHost()
Aart Bik7593b992016-08-17 16:51:12 -070061 if mode == 'hopt':
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070062 return TestRunnerArtOptOnHost()
Aart Bik7593b992016-08-17 16:51:12 -070063 if mode == 'tint':
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070064 return TestRunnerArtIntOnTarget(device)
Aart Bik7593b992016-08-17 16:51:12 -070065 if mode == 'topt':
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070066 return TestRunnerArtOptOnTarget(device)
Aart Bik7593b992016-08-17 16:51:12 -070067 raise FatalError('Unknown execution mode')
68
Aart Bike0347482016-09-20 14:34:13 -070069
Aart Bik7593b992016-08-17 16:51:12 -070070#
71# Execution mode classes.
72#
73
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070074
Aart Bik7593b992016-08-17 16:51:12 -070075class TestRunner(object):
76 """Abstraction for running a test in a particular execution mode."""
77 __meta_class__ = abc.ABCMeta
78
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070079 @abc.abstractproperty
80 def description(self):
Aart Bik7593b992016-08-17 16:51:12 -070081 """Returns a description string of the execution mode."""
Aart Bik7593b992016-08-17 16:51:12 -070082
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070083 @abc.abstractproperty
84 def id(self):
Aart Bik7593b992016-08-17 16:51:12 -070085 """Returns a short string that uniquely identifies the execution mode."""
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -070086
87 @property
88 def output_file(self):
89 return self.id + '_out.txt'
90
91 @abc.abstractmethod
92 def GetBisectionSearchArgs(self):
93 """Get arguments to pass to bisection search tool.
94
95 Returns:
96 list of strings - arguments for bisection search tool, or None if
97 runner is not bisectable
98 """
Aart Bik7593b992016-08-17 16:51:12 -070099
100 @abc.abstractmethod
101 def CompileAndRunTest(self):
102 """Compile and run the generated test.
103
104 Ensures that the current Test.java in the temporary directory is compiled
105 and executed under the current execution mode. On success, transfers the
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700106 generated output to the file self.output_file in the temporary directory.
Aart Bik7593b992016-08-17 16:51:12 -0700107
108 Most nonzero return codes are assumed non-divergent, since systems may
109 exit in different ways. This is enforced by normalizing return codes.
110
111 Returns:
112 normalized return code
113 """
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700114
Aart Bik7593b992016-08-17 16:51:12 -0700115
116class TestRunnerRIOnHost(TestRunner):
117 """Concrete test runner of the reference implementation on host."""
118
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700119 @property
120 def description(self):
121 return 'RI on host'
122
123 @property
124 def id(self):
125 return 'RI'
Aart Bik7593b992016-08-17 16:51:12 -0700126
127 def CompileAndRunTest(self):
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700128 if RunCommand(['javac', 'Test.java'],
129 out=None, err=None, timeout=30) == RetCode.SUCCESS:
130 retc = RunCommand(['java', 'Test'], self.output_file, err=None)
Aart Bik7593b992016-08-17 16:51:12 -0700131 else:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700132 retc = RetCode.NOTCOMPILED
Aart Bik7593b992016-08-17 16:51:12 -0700133 return retc
134
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700135 def GetBisectionSearchArgs(self):
136 return None
Aart Bik7593b992016-08-17 16:51:12 -0700137
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700138
139class TestRunnerArtOnHost(TestRunner):
140 """Abstract test runner of Art on host."""
141
142 def __init__(self, extra_args=None):
Aart Bik7593b992016-08-17 16:51:12 -0700143 """Constructor for the Art on host tester.
144
145 Args:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700146 extra_args: list of strings, extra arguments for dalvikvm
Aart Bik7593b992016-08-17 16:51:12 -0700147 """
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700148 self._art_cmd = ['/bin/bash', 'art', '-cp', 'classes.dex']
149 if extra_args is not None:
150 self._art_cmd += extra_args
151 self._art_cmd.append('Test')
152 self._jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.',
153 'Test.java']
Aart Bik7593b992016-08-17 16:51:12 -0700154
155 def CompileAndRunTest(self):
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700156 if RunCommand(['jack'] + self._jack_args, out=None, err='jackerr.txt',
157 timeout=30) == RetCode.SUCCESS:
158 retc = RunCommand(self._art_cmd, self.output_file, 'arterr.txt')
Aart Bik7593b992016-08-17 16:51:12 -0700159 else:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700160 retc = RetCode.NOTCOMPILED
Aart Bik7593b992016-08-17 16:51:12 -0700161 return retc
162
Aart Bik7593b992016-08-17 16:51:12 -0700163
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700164class TestRunnerArtIntOnHost(TestRunnerArtOnHost):
165 """Concrete test runner of interpreter mode Art on host."""
166
167 def __init__(self):
168 """Constructor."""
169 super().__init__(['-Xint'])
170
171 @property
172 def description(self):
173 return 'Art interpreter on host'
174
175 @property
176 def id(self):
177 return 'HInt'
178
179 def GetBisectionSearchArgs(self):
180 return None
181
182
183class TestRunnerArtOptOnHost(TestRunnerArtOnHost):
184 """Concrete test runner of optimizing compiler mode Art on host."""
185
186 def __init__(self):
187 """Constructor."""
188 super().__init__(None)
189
190 @property
191 def description(self):
192 return 'Art optimizing on host'
193
194 @property
195 def id(self):
196 return 'HOpt'
197
198 def GetBisectionSearchArgs(self):
199 cmd_str = CommandListToCommandString(
200 self._art_cmd[0:2] + ['{ARGS}'] + self._art_cmd[2:])
201 return ['--raw-cmd={0}'.format(cmd_str), '--timeout', str(30)]
202
203
204class TestRunnerArtOnTarget(TestRunner):
205 """Abstract test runner of Art on target."""
206
207 def __init__(self, device, extra_args=None):
Aart Bik7593b992016-08-17 16:51:12 -0700208 """Constructor for the Art on target tester.
209
210 Args:
Aart Bikb16d4132016-08-19 15:45:11 -0700211 device: string, target device serial number (or None)
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700212 extra_args: list of strings, extra arguments for dalvikvm
Aart Bik7593b992016-08-17 16:51:12 -0700213 """
Aart Bik842a4f32016-09-21 15:45:18 -0700214 self._test_env = DeviceTestEnv('jfuzz_', specific_device=device)
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700215 self._dalvik_cmd = ['dalvikvm']
216 if extra_args is not None:
217 self._dalvik_cmd += extra_args
218 self._device = device
219 self._jack_args = ['-cp', GetJackClassPath(), '--output-dex', '.',
220 'Test.java']
221 self._device_classpath = None
Aart Bik7593b992016-08-17 16:51:12 -0700222
223 def CompileAndRunTest(self):
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700224 if RunCommand(['jack'] + self._jack_args, out=None, err='jackerr.txt',
225 timeout=30) == RetCode.SUCCESS:
226 self._device_classpath = self._test_env.PushClasspath('classes.dex')
227 cmd = self._dalvik_cmd + ['-cp', self._device_classpath, 'Test']
228 (output, retc) = self._test_env.RunCommand(
229 cmd, {'ANDROID_LOG_TAGS': '*:s'})
230 with open(self.output_file, 'w') as run_out:
231 run_out.write(output)
Aart Bik7593b992016-08-17 16:51:12 -0700232 else:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700233 retc = RetCode.NOTCOMPILED
Aart Bik7593b992016-08-17 16:51:12 -0700234 return retc
235
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700236 def GetBisectionSearchArgs(self):
237 cmd_str = CommandListToCommandString(
238 self._dalvik_cmd + ['-cp',self._device_classpath, 'Test'])
239 cmd = ['--raw-cmd={0}'.format(cmd_str), '--timeout', str(30)]
240 if self._device:
241 cmd += ['--device-serial', self._device]
242 else:
243 cmd.append('--device')
244 return cmd
245
246
247class TestRunnerArtIntOnTarget(TestRunnerArtOnTarget):
248 """Concrete test runner of interpreter mode Art on target."""
249
250 def __init__(self, device):
251 """Constructor.
252
253 Args:
254 device: string, target device serial number (or None)
255 """
256 super().__init__(device, ['-Xint'])
257
258 @property
259 def description(self):
260 return 'Art interpreter on target'
261
262 @property
263 def id(self):
264 return 'TInt'
265
266 def GetBisectionSearchArgs(self):
267 return None
268
269
270class TestRunnerArtOptOnTarget(TestRunnerArtOnTarget):
271 """Concrete test runner of optimizing compiler mode Art on target."""
272
273 def __init__(self, device):
274 """Constructor.
275
276 Args:
277 device: string, target device serial number (or None)
278 """
279 super().__init__(device, None)
280
281 @property
282 def description(self):
283 return 'Art optimizing on target'
284
285 @property
286 def id(self):
287 return 'TOpt'
288
289 def GetBisectionSearchArgs(self):
290 cmd_str = CommandListToCommandString(
291 self._dalvik_cmd + ['-cp', self._device_classpath, 'Test'])
292 cmd = ['--raw-cmd={0}'.format(cmd_str), '--timeout', str(30)]
293 if self._device:
294 cmd += ['--device-serial', self._device]
295 else:
296 cmd.append('--device')
297 return cmd
298
299
Aart Bik7593b992016-08-17 16:51:12 -0700300#
Aart Bike0347482016-09-20 14:34:13 -0700301# Tester class.
Aart Bik7593b992016-08-17 16:51:12 -0700302#
303
Aart Bik7593b992016-08-17 16:51:12 -0700304
Aart Bik842a4f32016-09-21 15:45:18 -0700305class JFuzzTester(object):
306 """Tester that runs JFuzz many times and report divergences."""
Aart Bik7593b992016-08-17 16:51:12 -0700307
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700308 def __init__(self, num_tests, device, mode1, mode2, jfuzz_args,
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700309 report_script, true_divergence_only):
Aart Bik7593b992016-08-17 16:51:12 -0700310 """Constructor for the tester.
311
312 Args:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700313 num_tests: int, number of tests to run
314 device: string, target device serial number (or None)
315 mode1: string, execution mode for first runner
316 mode2: string, execution mode for second runner
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700317 jfuzz_args: list of strings, additional arguments for jfuzz
318 report_script: string, path to script called for each divergence
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700319 true_divergence_only: boolean, if True don't bisect timeout divergences
Aart Bik7593b992016-08-17 16:51:12 -0700320 """
321 self._num_tests = num_tests
Aart Bikb16d4132016-08-19 15:45:11 -0700322 self._device = device
323 self._runner1 = GetExecutionModeRunner(device, mode1)
324 self._runner2 = GetExecutionModeRunner(device, mode2)
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700325 self._jfuzz_args = jfuzz_args
326 self._report_script = report_script
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700327 self._true_divergence_only = true_divergence_only
Aart Bik7593b992016-08-17 16:51:12 -0700328 self._save_dir = None
Aart Bike0347482016-09-20 14:34:13 -0700329 self._results_dir = None
Aart Bik842a4f32016-09-21 15:45:18 -0700330 self._jfuzz_dir = None
Aart Bik7593b992016-08-17 16:51:12 -0700331 # Statistics.
332 self._test = 0
333 self._num_success = 0
334 self._num_not_compiled = 0
335 self._num_not_run = 0
336 self._num_timed_out = 0
337 self._num_divergences = 0
338
339 def __enter__(self):
340 """On entry, enters new temp directory after saving current directory.
341
342 Raises:
343 FatalError: error when temp directory cannot be constructed
344 """
345 self._save_dir = os.getcwd()
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700346 self._results_dir = mkdtemp(dir='/tmp/')
Aart Bik842a4f32016-09-21 15:45:18 -0700347 self._jfuzz_dir = mkdtemp(dir=self._results_dir)
348 if self._results_dir is None or self._jfuzz_dir is None:
Aart Bik7593b992016-08-17 16:51:12 -0700349 raise FatalError('Cannot obtain temp directory')
Aart Bik842a4f32016-09-21 15:45:18 -0700350 os.chdir(self._jfuzz_dir)
Aart Bik7593b992016-08-17 16:51:12 -0700351 return self
352
353 def __exit__(self, etype, evalue, etraceback):
354 """On exit, re-enters previously saved current directory and cleans up."""
355 os.chdir(self._save_dir)
Aart Bik842a4f32016-09-21 15:45:18 -0700356 shutil.rmtree(self._jfuzz_dir)
Aart Bik7593b992016-08-17 16:51:12 -0700357 if self._num_divergences == 0:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700358 shutil.rmtree(self._results_dir)
Aart Bik7593b992016-08-17 16:51:12 -0700359
360 def Run(self):
Aart Bik842a4f32016-09-21 15:45:18 -0700361 """Runs JFuzz many times and report divergences."""
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700362 print()
Aart Bik842a4f32016-09-21 15:45:18 -0700363 print('**\n**** JFuzz Testing\n**')
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700364 print()
365 print('#Tests :', self._num_tests)
366 print('Device :', self._device)
367 print('Directory :', self._results_dir)
368 print('Exec-mode1:', self._runner1.description)
369 print('Exec-mode2:', self._runner2.description)
Aart Bik9d537312016-09-15 10:42:02 -0700370 print()
Aart Bik7593b992016-08-17 16:51:12 -0700371 self.ShowStats()
372 for self._test in range(1, self._num_tests + 1):
Aart Bik842a4f32016-09-21 15:45:18 -0700373 self.RunJFuzzTest()
Aart Bik7593b992016-08-17 16:51:12 -0700374 self.ShowStats()
375 if self._num_divergences == 0:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700376 print('\n\nsuccess (no divergences)\n')
Aart Bik7593b992016-08-17 16:51:12 -0700377 else:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700378 print('\n\nfailure (divergences)\n')
Aart Bik7593b992016-08-17 16:51:12 -0700379
380 def ShowStats(self):
381 """Shows current statistics (on same line) while tester is running."""
Aart Bike0347482016-09-20 14:34:13 -0700382 print('\rTests:', self._test,
383 'Success:', self._num_success,
384 'Not-compiled:', self._num_not_compiled,
385 'Not-run:', self._num_not_run,
386 'Timed-out:', self._num_timed_out,
387 'Divergences:', self._num_divergences,
388 end='')
Aart Bik7593b992016-08-17 16:51:12 -0700389 sys.stdout.flush()
390
Aart Bik842a4f32016-09-21 15:45:18 -0700391 def RunJFuzzTest(self):
392 """Runs a single JFuzz test, comparing two execution modes."""
Aart Bik7593b992016-08-17 16:51:12 -0700393 self.ConstructTest()
394 retc1 = self._runner1.CompileAndRunTest()
395 retc2 = self._runner2.CompileAndRunTest()
396 self.CheckForDivergence(retc1, retc2)
397 self.CleanupTest()
398
399 def ConstructTest(self):
Aart Bik842a4f32016-09-21 15:45:18 -0700400 """Use JFuzz to generate next Test.java test.
Aart Bik7593b992016-08-17 16:51:12 -0700401
402 Raises:
Aart Bik842a4f32016-09-21 15:45:18 -0700403 FatalError: error when jfuzz fails
Aart Bik7593b992016-08-17 16:51:12 -0700404 """
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700405 if (RunCommand(['jfuzz'] + self._jfuzz_args, out='Test.java', err=None)
406 != RetCode.SUCCESS):
Aart Bik842a4f32016-09-21 15:45:18 -0700407 raise FatalError('Unexpected error while running JFuzz')
Aart Bik7593b992016-08-17 16:51:12 -0700408
409 def CheckForDivergence(self, retc1, retc2):
410 """Checks for divergences and updates statistics.
411
412 Args:
413 retc1: int, normalized return code of first runner
414 retc2: int, normalized return code of second runner
415 """
416 if retc1 == retc2:
417 # Non-divergent in return code.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700418 if retc1 == RetCode.SUCCESS:
Aart Bik7593b992016-08-17 16:51:12 -0700419 # Both compilations and runs were successful, inspect generated output.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700420 runner1_out = self._runner1.output_file
421 runner2_out = self._runner2.output_file
422 if not filecmp.cmp(runner1_out, runner2_out, shallow=False):
423 self.ReportDivergence(retc1, retc2, is_output_divergence=True)
Aart Bik7593b992016-08-17 16:51:12 -0700424 else:
425 self._num_success += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700426 elif retc1 == RetCode.TIMEOUT:
Aart Bik7593b992016-08-17 16:51:12 -0700427 self._num_timed_out += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700428 elif retc1 == RetCode.NOTCOMPILED:
Aart Bik7593b992016-08-17 16:51:12 -0700429 self._num_not_compiled += 1
430 else:
431 self._num_not_run += 1
432 else:
433 # Divergent in return code.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700434 self.ReportDivergence(retc1, retc2, is_output_divergence=False)
Aart Bik7593b992016-08-17 16:51:12 -0700435
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700436 def GetCurrentDivergenceDir(self):
437 return self._results_dir + '/divergence' + str(self._num_divergences)
438
439 def ReportDivergence(self, retc1, retc2, is_output_divergence):
Aart Bik7593b992016-08-17 16:51:12 -0700440 """Reports and saves a divergence."""
441 self._num_divergences += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700442 print('\n' + str(self._num_divergences), end='')
443 if is_output_divergence:
444 print(' divergence in output')
445 else:
446 print(' divergence in return code: ' + retc1.name + ' vs. ' +
447 retc2.name)
Aart Bik7593b992016-08-17 16:51:12 -0700448 # Save.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700449 ddir = self.GetCurrentDivergenceDir()
450 os.mkdir(ddir)
451 for f in glob('*.txt') + ['Test.java']:
452 shutil.copy(f, ddir)
453 # Maybe run bisection bug search.
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700454 if (retc1 in BISECTABLE_RET_CODES and retc2 in BISECTABLE_RET_CODES and
455 not (self._true_divergence_only and RetCode.TIMEOUT in (retc1, retc2))):
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700456 self.MaybeBisectDivergence(retc1, retc2, is_output_divergence)
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700457 # Call reporting script.
458 if self._report_script:
459 self.RunReportScript(retc1, retc2, is_output_divergence)
460
461 def RunReportScript(self, retc1, retc2, is_output_divergence):
462 """Runs report script."""
463 try:
464 title = "Divergence between {0} and {1} (found with fuzz testing)".format(
465 self._runner1.description, self._runner2.description)
466 # Prepare divergence comment.
467 jfuzz_cmd_and_version = subprocess.check_output(
468 ['grep', '-o', 'jfuzz.*', 'Test.java'], universal_newlines=True)
469 (jfuzz_cmd_str, jfuzz_ver) = jfuzz_cmd_and_version.split('(')
470 # Strip right parenthesis and new line.
471 jfuzz_ver = jfuzz_ver[:-2]
472 jfuzz_args = ['\'-{0}\''.format(arg)
473 for arg in jfuzz_cmd_str.strip().split(' -')][1:]
474 wrapped_args = ['--jfuzz_arg={0}'.format(opt) for opt in jfuzz_args]
475 repro_cmd_str = (os.path.basename(__file__) + ' --num_tests 1 ' +
476 ' '.join(wrapped_args))
477 comment = 'jfuzz {0}\nReproduce test:\n{1}\nReproduce divergence:\n{2}\n'.format(
478 jfuzz_ver, jfuzz_cmd_str, repro_cmd_str)
479 if is_output_divergence:
480 (output, _, _) = RunCommandForOutput(
481 ['diff', self._runner1.output_file, self._runner2.output_file],
482 None, subprocess.PIPE, subprocess.STDOUT)
483 comment += 'Diff:\n' + output
484 else:
485 comment += '{0} vs {1}\n'.format(retc1, retc2)
486 # Prepare report script command.
487 script_cmd = [self._report_script, title, comment]
488 ddir = self.GetCurrentDivergenceDir()
489 bisection_out_files = glob(ddir + '/*_bisection_out.txt')
490 if bisection_out_files:
491 script_cmd += ['--bisection_out', bisection_out_files[0]]
492 subprocess.check_call(script_cmd, stdout=DEVNULL, stderr=DEVNULL)
493 except subprocess.CalledProcessError as err:
494 print('Failed to run report script.\n', err)
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700495
496 def RunBisectionSearch(self, args, expected_retcode, expected_output,
497 runner_id):
498 ddir = self.GetCurrentDivergenceDir()
499 outfile_path = ddir + '/' + runner_id + '_bisection_out.txt'
500 logfile_path = ddir + '/' + runner_id + '_bisection_log.txt'
501 errfile_path = ddir + '/' + runner_id + '_bisection_err.txt'
502 args = list(args) + ['--logfile', logfile_path, '--cleanup']
503 args += ['--expected-retcode', expected_retcode.name]
504 if expected_output:
505 args += ['--expected-output', expected_output]
506 bisection_search_path = os.path.join(
507 GetEnvVariableOrError('ANDROID_BUILD_TOP'),
508 'art/tools/bisection_search/bisection_search.py')
509 if RunCommand([bisection_search_path] + args, out=outfile_path,
510 err=errfile_path, timeout=300) == RetCode.TIMEOUT:
511 print('Bisection search TIMEOUT')
512
513 def MaybeBisectDivergence(self, retc1, retc2, is_output_divergence):
514 bisection_args1 = self._runner1.GetBisectionSearchArgs()
515 bisection_args2 = self._runner2.GetBisectionSearchArgs()
516 if is_output_divergence:
517 maybe_output1 = self._runner1.output_file
518 maybe_output2 = self._runner2.output_file
519 else:
520 maybe_output1 = maybe_output2 = None
521 if bisection_args1 is not None:
522 self.RunBisectionSearch(bisection_args1, retc2, maybe_output2,
523 self._runner1.id)
524 if bisection_args2 is not None:
525 self.RunBisectionSearch(bisection_args2, retc1, maybe_output1,
526 self._runner2.id)
Aart Bik7593b992016-08-17 16:51:12 -0700527
528 def CleanupTest(self):
529 """Cleans up after a single test run."""
Aart Bik842a4f32016-09-21 15:45:18 -0700530 for file_name in os.listdir(self._jfuzz_dir):
531 file_path = os.path.join(self._jfuzz_dir, file_name)
Aart Bike0347482016-09-20 14:34:13 -0700532 if os.path.isfile(file_path):
533 os.unlink(file_path)
534 elif os.path.isdir(file_path):
535 shutil.rmtree(file_path)
Aart Bik7593b992016-08-17 16:51:12 -0700536
537
538def main():
539 # Handle arguments.
540 parser = argparse.ArgumentParser()
541 parser.add_argument('--num_tests', default=10000,
542 type=int, help='number of tests to run')
Aart Bikb16d4132016-08-19 15:45:11 -0700543 parser.add_argument('--device', help='target device serial number')
Aart Bik7593b992016-08-17 16:51:12 -0700544 parser.add_argument('--mode1', default='ri',
545 help='execution mode 1 (default: ri)')
546 parser.add_argument('--mode2', default='hopt',
547 help='execution mode 2 (default: hopt)')
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700548 parser.add_argument('--report_script', help='script called for each'
549 'divergence')
550 parser.add_argument('--jfuzz_arg', default=[], dest='jfuzz_args',
551 action='append', help='argument for jfuzz')
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700552 parser.add_argument('--true_divergence', default=False, action='store_true',
553 help='don\'t bisect timeout divergences')
Aart Bik7593b992016-08-17 16:51:12 -0700554 args = parser.parse_args()
555 if args.mode1 == args.mode2:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700556 raise FatalError('Identical execution modes given')
Aart Bik842a4f32016-09-21 15:45:18 -0700557 # Run the JFuzz tester.
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700558 with JFuzzTester(args.num_tests, args.device, args.mode1, args.mode2,
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700559 args.jfuzz_args, args.report_script,
560 args.true_divergence) as fuzzer:
Aart Bik7593b992016-08-17 16:51:12 -0700561 fuzzer.Run()
562
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700563if __name__ == '__main__':
Aart Bik7593b992016-08-17 16:51:12 -0700564 main()