The data contained in this repository can be downloaded to your computer using one of several clients.
Please see the documentation of your version control software client for more information.

Please select the desired protocol below to get the URL.

This URL has Read-Only access.

Statistics
| Branch: | Revision:

main_repo / deps / v8 / tools / run-deopt-fuzzer.py @ f230a1cf

History | View | Annotate | Download (15.8 KB)

1
#!/usr/bin/env python
2
#
3
# Copyright 2012 the V8 project authors. All rights reserved.
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are
6
# met:
7
#
8
#     * Redistributions of source code must retain the above copyright
9
#       notice, this list of conditions and the following disclaimer.
10
#     * Redistributions in binary form must reproduce the above
11
#       copyright notice, this list of conditions and the following
12
#       disclaimer in the documentation and/or other materials provided
13
#       with the distribution.
14
#     * Neither the name of Google Inc. nor the names of its
15
#       contributors may be used to endorse or promote products derived
16
#       from this software without specific prior written permission.
17
#
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29

    
30

    
31
import json
32
import math
33
import multiprocessing
34
import optparse
35
import os
36
from os.path import join
37
import random
38
import shlex
39
import subprocess
40
import sys
41
import time
42

    
43
from testrunner.local import execution
44
from testrunner.local import progress
45
from testrunner.local import testsuite
46
from testrunner.local import utils
47
from testrunner.local import verbose
48
from testrunner.objects import context
49

    
50

    
51
ARCH_GUESS = utils.DefaultArch()
52
DEFAULT_TESTS = ["mjsunit", "webkit"]
53
TIMEOUT_DEFAULT = 60
54
TIMEOUT_SCALEFACTOR = {"debug"   : 4,
55
                       "release" : 1 }
56

    
57
MODE_FLAGS = {
58
    "debug"   : ["--nobreak-on-abort", "--nodead-code-elimination",
59
                 "--nofold-constants", "--enable-slow-asserts",
60
                 "--debug-code", "--verify-heap",
61
                 "--noconcurrent-recompilation"],
62
    "release" : ["--nobreak-on-abort", "--nodead-code-elimination",
63
                 "--nofold-constants", "--noconcurrent-recompilation"]}
64

    
65
SUPPORTED_ARCHS = ["android_arm",
66
                   "android_ia32",
67
                   "arm",
68
                   "ia32",
69
                   "mipsel",
70
                   "nacl_ia32",
71
                   "nacl_x64",
72
                   "x64"]
73
# Double the timeout for these:
74
SLOW_ARCHS = ["android_arm",
75
              "android_ia32",
76
              "arm",
77
              "mipsel",
78
              "nacl_ia32",
79
              "nacl_x64"]
80
MAX_DEOPT = 1000000000
81
DISTRIBUTION_MODES = ["smooth", "random"]
82

    
83

    
84
class RandomDistribution:
85
  def __init__(self, seed=None):
86
    seed = seed or random.randint(1, sys.maxint)
87
    print "Using random distribution with seed %d" % seed
88
    self._random = random.Random(seed)
89

    
90
  def Distribute(self, n, m):
91
    if n > m:
92
      n = m
93
    return self._random.sample(xrange(1, m + 1), n)
94

    
95

    
96
class SmoothDistribution:
97
  """Distribute n numbers into the interval [1:m].
98
  F1: Factor of the first derivation of the distribution function.
99
  F2: Factor of the second derivation of the distribution function.
100
  With F1 and F2 set to 0, the distribution will be equal.
101
  """
102
  def __init__(self, factor1=2.0, factor2=0.2):
103
    self._factor1 = factor1
104
    self._factor2 = factor2
105

    
106
  def Distribute(self, n, m):
107
    if n > m:
108
      n = m
109
    if n <= 1:
110
      return [ 1 ]
111

    
112
    result = []
113
    x = 0.0
114
    dx = 1.0
115
    ddx = self._factor1
116
    dddx = self._factor2
117
    for i in range(0, n):
118
      result += [ x ]
119
      x += dx
120
      dx += ddx
121
      ddx += dddx
122

    
123
    # Project the distribution into the interval [0:M].
124
    result = [ x * m / result[-1] for x in result ]
125

    
126
    # Equalize by n. The closer n is to m, the more equal will be the
127
    # distribution.
128
    for (i, x) in enumerate(result):
129
      # The value of x if it was equally distributed.
130
      equal_x = i / float(n - 1) * float(m - 1) + 1
131

    
132
      # Difference factor between actual and equal distribution.
133
      diff = 1 - (x / equal_x)
134

    
135
      # Equalize x dependent on the number of values to distribute.
136
      result[i] = int(x + (i + 1) * diff)
137
    return result
138

    
139

    
140
def Distribution(options):
141
  if options.distribution_mode == "random":
142
    return RandomDistribution(options.seed)
143
  if options.distribution_mode == "smooth":
144
    return SmoothDistribution(options.distribution_factor1,
145
                              options.distribution_factor2)
146

    
147

    
148
def BuildOptions():
149
  result = optparse.OptionParser()
150
  result.add_option("--arch",
151
                    help=("The architecture to run tests for, "
152
                          "'auto' or 'native' for auto-detect"),
153
                    default="ia32,x64,arm")
154
  result.add_option("--arch-and-mode",
155
                    help="Architecture and mode in the format 'arch.mode'",
156
                    default=None)
157
  result.add_option("--buildbot",
158
                    help="Adapt to path structure used on buildbots",
159
                    default=False, action="store_true")
160
  result.add_option("--command-prefix",
161
                    help="Prepended to each shell command used to run a test",
162
                    default="")
163
  result.add_option("--coverage", help=("Exponential test coverage "
164
                    "(range 0.0, 1.0) -- 0.0: one test, 1.0 all tests (slow)"),
165
                    default=0.4, type="float")
166
  result.add_option("--coverage-lift", help=("Lifts test coverage for tests "
167
                    "with a small number of deopt points (range 0, inf)"),
168
                    default=20, type="int")
169
  result.add_option("--download-data", help="Download missing test suite data",
170
                    default=False, action="store_true")
171
  result.add_option("--distribution-factor1", help=("Factor of the first "
172
                    "derivation of the distribution function"), default=2.0,
173
                    type="float")
174
  result.add_option("--distribution-factor2", help=("Factor of the second "
175
                    "derivation of the distribution function"), default=0.7,
176
                    type="float")
177
  result.add_option("--distribution-mode", help=("How to select deopt points "
178
                    "for a given test (smooth|random)"),
179
                    default="smooth")
180
  result.add_option("--dump-results-file", help=("Dump maximum number of "
181
                    "deopt points per test to a file"))
182
  result.add_option("--extra-flags",
183
                    help="Additional flags to pass to each test command",
184
                    default="")
185
  result.add_option("--isolates", help="Whether to test isolates",
186
                    default=False, action="store_true")
187
  result.add_option("-j", help="The number of parallel tasks to run",
188
                    default=0, type="int")
189
  result.add_option("-m", "--mode",
190
                    help="The test modes in which to run (comma-separated)",
191
                    default="release,debug")
192
  result.add_option("--outdir", help="Base directory with compile output",
193
                    default="out")
194
  result.add_option("-p", "--progress",
195
                    help=("The style of progress indicator"
196
                          " (verbose, dots, color, mono)"),
197
                    choices=progress.PROGRESS_INDICATORS.keys(),
198
                    default="mono")
199
  result.add_option("--shard-count",
200
                    help="Split testsuites into this number of shards",
201
                    default=1, type="int")
202
  result.add_option("--shard-run",
203
                    help="Run this shard from the split up tests.",
204
                    default=1, type="int")
205
  result.add_option("--shell-dir", help="Directory containing executables",
206
                    default="")
207
  result.add_option("--seed", help="The seed for the random distribution",
208
                    type="int")
209
  result.add_option("-t", "--timeout", help="Timeout in seconds",
210
                    default= -1, type="int")
211
  result.add_option("-v", "--verbose", help="Verbose output",
212
                    default=False, action="store_true")
213
  return result
214

    
215

    
216
def ProcessOptions(options):
217
  global VARIANT_FLAGS
218

    
219
  # Architecture and mode related stuff.
220
  if options.arch_and_mode:
221
    tokens = options.arch_and_mode.split(".")
222
    options.arch = tokens[0]
223
    options.mode = tokens[1]
224
  options.mode = options.mode.split(",")
225
  for mode in options.mode:
226
    if not mode.lower() in ["debug", "release"]:
227
      print "Unknown mode %s" % mode
228
      return False
229
  if options.arch in ["auto", "native"]:
230
    options.arch = ARCH_GUESS
231
  options.arch = options.arch.split(",")
232
  for arch in options.arch:
233
    if not arch in SUPPORTED_ARCHS:
234
      print "Unknown architecture %s" % arch
235
      return False
236

    
237
  # Special processing of other options, sorted alphabetically.
238
  options.command_prefix = shlex.split(options.command_prefix)
239
  options.extra_flags = shlex.split(options.extra_flags)
240
  if options.j == 0:
241
    options.j = multiprocessing.cpu_count()
242
  if not options.distribution_mode in DISTRIBUTION_MODES:
243
    print "Unknown distribution mode %s" % options.distribution_mode
244
    return False
245
  if options.distribution_factor1 < 0.0:
246
    print ("Distribution factor1 %s is out of range. Defaulting to 0.0"
247
        % options.distribution_factor1)
248
    options.distribution_factor1 = 0.0
249
  if options.distribution_factor2 < 0.0:
250
    print ("Distribution factor2 %s is out of range. Defaulting to 0.0"
251
        % options.distribution_factor2)
252
    options.distribution_factor2 = 0.0
253
  if options.coverage < 0.0 or options.coverage > 1.0:
254
    print ("Coverage %s is out of range. Defaulting to 0.4"
255
        % options.coverage)
256
    options.coverage = 0.4
257
  if options.coverage_lift < 0:
258
    print ("Coverage lift %s is out of range. Defaulting to 0"
259
        % options.coverage_lift)
260
    options.coverage_lift = 0
261
  return True
262

    
263

    
264
def ShardTests(tests, shard_count, shard_run):
265
  if shard_count < 2:
266
    return tests
267
  if shard_run < 1 or shard_run > shard_count:
268
    print "shard-run not a valid number, should be in [1:shard-count]"
269
    print "defaulting back to running all tests"
270
    return tests
271
  count = 0
272
  shard = []
273
  for test in tests:
274
    if count % shard_count == shard_run - 1:
275
      shard.append(test)
276
    count += 1
277
  return shard
278

    
279

    
280
def Main():
281
  parser = BuildOptions()
282
  (options, args) = parser.parse_args()
283
  if not ProcessOptions(options):
284
    parser.print_help()
285
    return 1
286

    
287
  exit_code = 0
288
  workspace = os.path.abspath(join(os.path.dirname(sys.argv[0]), ".."))
289

    
290
  suite_paths = utils.GetSuitePaths(join(workspace, "test"))
291

    
292
  if len(args) == 0:
293
    suite_paths = [ s for s in suite_paths if s in DEFAULT_TESTS ]
294
  else:
295
    args_suites = set()
296
    for arg in args:
297
      suite = arg.split(os.path.sep)[0]
298
      if not suite in args_suites:
299
        args_suites.add(suite)
300
    suite_paths = [ s for s in suite_paths if s in args_suites ]
301

    
302
  suites = []
303
  for root in suite_paths:
304
    suite = testsuite.TestSuite.LoadTestSuite(
305
        os.path.join(workspace, "test", root))
306
    if suite:
307
      suites.append(suite)
308

    
309
  if options.download_data:
310
    for s in suites:
311
      s.DownloadData()
312

    
313
  for mode in options.mode:
314
    for arch in options.arch:
315
      code = Execute(arch, mode, args, options, suites, workspace)
316
      exit_code = exit_code or code
317
  return exit_code
318

    
319

    
320
def CalculateNTests(m, options):
321
  """Calculates the number of tests from m deopt points with exponential
322
  coverage.
323
  The coverage is expected to be between 0.0 and 1.0.
324
  The 'coverage lift' lifts the coverage for tests with smaller m values.
325
  """
326
  c = float(options.coverage)
327
  l = float(options.coverage_lift)
328
  return int(math.pow(m, (m * c + l) / (m + l)))
329

    
330

    
331
def Execute(arch, mode, args, options, suites, workspace):
332
  print(">>> Running tests for %s.%s" % (arch, mode))
333

    
334
  dist = Distribution(options)
335

    
336
  shell_dir = options.shell_dir
337
  if not shell_dir:
338
    if options.buildbot:
339
      shell_dir = os.path.join(workspace, options.outdir, mode)
340
      mode = mode.lower()
341
    else:
342
      shell_dir = os.path.join(workspace, options.outdir,
343
                               "%s.%s" % (arch, mode))
344
  shell_dir = os.path.relpath(shell_dir)
345

    
346
  # Populate context object.
347
  mode_flags = MODE_FLAGS[mode]
348
  timeout = options.timeout
349
  if timeout == -1:
350
    # Simulators are slow, therefore allow a longer default timeout.
351
    if arch in SLOW_ARCHS:
352
      timeout = 2 * TIMEOUT_DEFAULT;
353
    else:
354
      timeout = TIMEOUT_DEFAULT;
355

    
356
  timeout *= TIMEOUT_SCALEFACTOR[mode]
357
  ctx = context.Context(arch, mode, shell_dir,
358
                        mode_flags, options.verbose,
359
                        timeout, options.isolates,
360
                        options.command_prefix,
361
                        options.extra_flags,
362
                        False)
363

    
364
  # Find available test suites and read test cases from them.
365
  variables = {
366
    "mode": mode,
367
    "arch": arch,
368
    "system": utils.GuessOS(),
369
    "isolates": options.isolates,
370
    "deopt_fuzzer": True,
371
    "no_i18n": False,
372
  }
373
  all_tests = []
374
  num_tests = 0
375
  test_id = 0
376

    
377
  # Remember test case prototypes for the fuzzing phase.
378
  test_backup = dict((s, []) for s in suites)
379

    
380
  for s in suites:
381
    s.ReadStatusFile(variables)
382
    s.ReadTestCases(ctx)
383
    if len(args) > 0:
384
      s.FilterTestCasesByArgs(args)
385
    all_tests += s.tests
386
    s.FilterTestCasesByStatus(False)
387
    test_backup[s] = s.tests
388
    analysis_flags = ["--deopt-every-n-times", "%d" % MAX_DEOPT,
389
                      "--print-deopt-stress"]
390
    s.tests = [ t.CopyAddingFlags(analysis_flags) for t in s.tests ]
391
    num_tests += len(s.tests)
392
    for t in s.tests:
393
      t.id = test_id
394
      test_id += 1
395

    
396
  if num_tests == 0:
397
    print "No tests to run."
398
    return 0
399

    
400
  try:
401
    print(">>> Collection phase")
402
    progress_indicator = progress.PROGRESS_INDICATORS[options.progress]()
403
    runner = execution.Runner(suites, progress_indicator, ctx)
404

    
405
    exit_code = runner.Run(options.j)
406
    if runner.terminate:
407
      return exit_code
408

    
409
  except KeyboardInterrupt:
410
    return 1
411

    
412
  print(">>> Analysis phase")
413
  num_tests = 0
414
  test_id = 0
415
  for s in suites:
416
    test_results = {}
417
    for t in s.tests:
418
      for line in t.output.stdout.splitlines():
419
        if line.startswith("=== Stress deopt counter: "):
420
          test_results[t.path] = MAX_DEOPT - int(line.split(" ")[-1])
421
    for t in s.tests:
422
      if t.path not in test_results:
423
        print "Missing results for %s" % t.path
424
    if options.dump_results_file:
425
      results_dict = dict((t.path, n) for (t, n) in test_results.iteritems())
426
      with file("%s.%d.txt" % (dump_results_file, time.time()), "w") as f:
427
        f.write(json.dumps(results_dict))
428

    
429
    # Reset tests and redistribute the prototypes from the collection phase.
430
    s.tests = []
431
    if options.verbose:
432
      print "Test distributions:"
433
    for t in test_backup[s]:
434
      max_deopt = test_results.get(t.path, 0)
435
      if max_deopt == 0:
436
        continue
437
      n_deopt = CalculateNTests(max_deopt, options)
438
      distribution = dist.Distribute(n_deopt, max_deopt)
439
      if options.verbose:
440
        print "%s %s" % (t.path, distribution)
441
      for i in distribution:
442
        fuzzing_flags = ["--deopt-every-n-times", "%d" % i]
443
        s.tests.append(t.CopyAddingFlags(fuzzing_flags))
444
    num_tests += len(s.tests)
445
    for t in s.tests:
446
      t.id = test_id
447
      test_id += 1
448

    
449
  if num_tests == 0:
450
    print "No tests to run."
451
    return 0
452

    
453
  try:
454
    print(">>> Deopt fuzzing phase (%d test cases)" % num_tests)
455
    progress_indicator = progress.PROGRESS_INDICATORS[options.progress]()
456
    runner = execution.Runner(suites, progress_indicator, ctx)
457

    
458
    exit_code = runner.Run(options.j)
459
    if runner.terminate:
460
      return exit_code
461

    
462
  except KeyboardInterrupt:
463
    return 1
464

    
465
  return exit_code
466

    
467

    
468
if __name__ == "__main__":
469
  sys.exit(Main())