blob: 42745d247ac4becd84914d0a61f0658e2ea6c53e [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:
Aart Bik38f63012016-12-14 17:16:10 -0800417 # No divergence 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):
Aart Bik38f63012016-12-14 17:16:10 -0800423 # Divergence in output.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700424 self.ReportDivergence(retc1, retc2, is_output_divergence=True)
Aart Bik7593b992016-08-17 16:51:12 -0700425 else:
Aart Bik38f63012016-12-14 17:16:10 -0800426 # No divergence in output.
Aart Bik7593b992016-08-17 16:51:12 -0700427 self._num_success += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700428 elif retc1 == RetCode.TIMEOUT:
Aart Bik7593b992016-08-17 16:51:12 -0700429 self._num_timed_out += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700430 elif retc1 == RetCode.NOTCOMPILED:
Aart Bik7593b992016-08-17 16:51:12 -0700431 self._num_not_compiled += 1
432 else:
433 self._num_not_run += 1
Aart Bik38f63012016-12-14 17:16:10 -0800434 elif self._true_divergence_only and RetCode.TIMEOUT in (retc1, retc2):
435 # When only true divergences are requested, any divergence in return
436 # code where one is a time out is treated as a regular time out.
437 self._num_timed_out += 1
Aart Bik7593b992016-08-17 16:51:12 -0700438 else:
Aart Bik38f63012016-12-14 17:16:10 -0800439 # Divergence in return code.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700440 self.ReportDivergence(retc1, retc2, is_output_divergence=False)
Aart Bik7593b992016-08-17 16:51:12 -0700441
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700442 def GetCurrentDivergenceDir(self):
443 return self._results_dir + '/divergence' + str(self._num_divergences)
444
445 def ReportDivergence(self, retc1, retc2, is_output_divergence):
Aart Bik7593b992016-08-17 16:51:12 -0700446 """Reports and saves a divergence."""
447 self._num_divergences += 1
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700448 print('\n' + str(self._num_divergences), end='')
449 if is_output_divergence:
450 print(' divergence in output')
451 else:
452 print(' divergence in return code: ' + retc1.name + ' vs. ' +
453 retc2.name)
Aart Bik7593b992016-08-17 16:51:12 -0700454 # Save.
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700455 ddir = self.GetCurrentDivergenceDir()
456 os.mkdir(ddir)
457 for f in glob('*.txt') + ['Test.java']:
458 shutil.copy(f, ddir)
Aart Bik38f63012016-12-14 17:16:10 -0800459 # Maybe run bisection bug search.
460 if retc1 in BISECTABLE_RET_CODES and retc2 in BISECTABLE_RET_CODES:
461 self.MaybeBisectDivergence(retc1, retc2, is_output_divergence)
462 # Call reporting script.
463 if self._report_script:
464 self.RunReportScript(retc1, retc2, is_output_divergence)
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700465
466 def RunReportScript(self, retc1, retc2, is_output_divergence):
467 """Runs report script."""
468 try:
469 title = "Divergence between {0} and {1} (found with fuzz testing)".format(
470 self._runner1.description, self._runner2.description)
471 # Prepare divergence comment.
472 jfuzz_cmd_and_version = subprocess.check_output(
473 ['grep', '-o', 'jfuzz.*', 'Test.java'], universal_newlines=True)
474 (jfuzz_cmd_str, jfuzz_ver) = jfuzz_cmd_and_version.split('(')
475 # Strip right parenthesis and new line.
476 jfuzz_ver = jfuzz_ver[:-2]
477 jfuzz_args = ['\'-{0}\''.format(arg)
478 for arg in jfuzz_cmd_str.strip().split(' -')][1:]
479 wrapped_args = ['--jfuzz_arg={0}'.format(opt) for opt in jfuzz_args]
480 repro_cmd_str = (os.path.basename(__file__) + ' --num_tests 1 ' +
481 ' '.join(wrapped_args))
482 comment = 'jfuzz {0}\nReproduce test:\n{1}\nReproduce divergence:\n{2}\n'.format(
483 jfuzz_ver, jfuzz_cmd_str, repro_cmd_str)
484 if is_output_divergence:
485 (output, _, _) = RunCommandForOutput(
486 ['diff', self._runner1.output_file, self._runner2.output_file],
487 None, subprocess.PIPE, subprocess.STDOUT)
488 comment += 'Diff:\n' + output
489 else:
490 comment += '{0} vs {1}\n'.format(retc1, retc2)
491 # Prepare report script command.
492 script_cmd = [self._report_script, title, comment]
493 ddir = self.GetCurrentDivergenceDir()
494 bisection_out_files = glob(ddir + '/*_bisection_out.txt')
495 if bisection_out_files:
496 script_cmd += ['--bisection_out', bisection_out_files[0]]
497 subprocess.check_call(script_cmd, stdout=DEVNULL, stderr=DEVNULL)
498 except subprocess.CalledProcessError as err:
499 print('Failed to run report script.\n', err)
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700500
501 def RunBisectionSearch(self, args, expected_retcode, expected_output,
502 runner_id):
503 ddir = self.GetCurrentDivergenceDir()
504 outfile_path = ddir + '/' + runner_id + '_bisection_out.txt'
505 logfile_path = ddir + '/' + runner_id + '_bisection_log.txt'
506 errfile_path = ddir + '/' + runner_id + '_bisection_err.txt'
507 args = list(args) + ['--logfile', logfile_path, '--cleanup']
508 args += ['--expected-retcode', expected_retcode.name]
509 if expected_output:
510 args += ['--expected-output', expected_output]
511 bisection_search_path = os.path.join(
512 GetEnvVariableOrError('ANDROID_BUILD_TOP'),
513 'art/tools/bisection_search/bisection_search.py')
514 if RunCommand([bisection_search_path] + args, out=outfile_path,
515 err=errfile_path, timeout=300) == RetCode.TIMEOUT:
516 print('Bisection search TIMEOUT')
517
518 def MaybeBisectDivergence(self, retc1, retc2, is_output_divergence):
519 bisection_args1 = self._runner1.GetBisectionSearchArgs()
520 bisection_args2 = self._runner2.GetBisectionSearchArgs()
521 if is_output_divergence:
522 maybe_output1 = self._runner1.output_file
523 maybe_output2 = self._runner2.output_file
524 else:
525 maybe_output1 = maybe_output2 = None
526 if bisection_args1 is not None:
527 self.RunBisectionSearch(bisection_args1, retc2, maybe_output2,
528 self._runner1.id)
529 if bisection_args2 is not None:
530 self.RunBisectionSearch(bisection_args2, retc1, maybe_output1,
531 self._runner2.id)
Aart Bik7593b992016-08-17 16:51:12 -0700532
533 def CleanupTest(self):
534 """Cleans up after a single test run."""
Aart Bik842a4f32016-09-21 15:45:18 -0700535 for file_name in os.listdir(self._jfuzz_dir):
536 file_path = os.path.join(self._jfuzz_dir, file_name)
Aart Bike0347482016-09-20 14:34:13 -0700537 if os.path.isfile(file_path):
538 os.unlink(file_path)
539 elif os.path.isdir(file_path):
540 shutil.rmtree(file_path)
Aart Bik7593b992016-08-17 16:51:12 -0700541
542
543def main():
544 # Handle arguments.
545 parser = argparse.ArgumentParser()
546 parser.add_argument('--num_tests', default=10000,
547 type=int, help='number of tests to run')
Aart Bikb16d4132016-08-19 15:45:11 -0700548 parser.add_argument('--device', help='target device serial number')
Aart Bik7593b992016-08-17 16:51:12 -0700549 parser.add_argument('--mode1', default='ri',
550 help='execution mode 1 (default: ri)')
551 parser.add_argument('--mode2', default='hopt',
552 help='execution mode 2 (default: hopt)')
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700553 parser.add_argument('--report_script', help='script called for each'
554 'divergence')
555 parser.add_argument('--jfuzz_arg', default=[], dest='jfuzz_args',
556 action='append', help='argument for jfuzz')
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700557 parser.add_argument('--true_divergence', default=False, action='store_true',
558 help='don\'t bisect timeout divergences')
Aart Bik7593b992016-08-17 16:51:12 -0700559 args = parser.parse_args()
560 if args.mode1 == args.mode2:
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700561 raise FatalError('Identical execution modes given')
Aart Bik842a4f32016-09-21 15:45:18 -0700562 # Run the JFuzz tester.
Wojciech Staszkiewicz18be7b32016-09-21 15:12:54 -0700563 with JFuzzTester(args.num_tests, args.device, args.mode1, args.mode2,
Wojciech Staszkiewicz8569e522016-09-23 18:02:55 -0700564 args.jfuzz_args, args.report_script,
565 args.true_divergence) as fuzzer:
Aart Bik7593b992016-08-17 16:51:12 -0700566 fuzzer.Run()
567
Wojciech Staszkiewicz0d0fd4a2016-09-07 18:52:52 -0700568if __name__ == '__main__':
Aart Bik7593b992016-08-17 16:51:12 -0700569 main()