| #!/usr/bin/env python |
| # |
| # Copyright 2017 the V8 project authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # for py2/py3 compatibility |
| from __future__ import absolute_import |
| from __future__ import print_function |
| |
| import random |
| import sys |
| |
| # Adds testrunner to the path hence it has to be imported at the beggining. |
| from . import base_runner |
| |
| from testrunner.local import utils |
| |
| from testrunner.testproc import fuzzer |
| from testrunner.testproc.base import TestProcProducer |
| from testrunner.testproc.combiner import CombinerProc |
| from testrunner.testproc.execution import ExecutionProc |
| from testrunner.testproc.expectation import ForgiveTimeoutProc |
| from testrunner.testproc.filter import StatusFileFilterProc, NameFilterProc |
| from testrunner.testproc.loader import LoadProc |
| from testrunner.testproc.progress import ResultsTracker |
| from testrunner.utils import random_utils |
| |
| |
| DEFAULT_SUITES = ["mjsunit", "webkit", "benchmarks"] |
| |
| |
| class NumFuzzer(base_runner.BaseTestRunner): |
| def __init__(self, *args, **kwargs): |
| super(NumFuzzer, self).__init__(*args, **kwargs) |
| |
| @property |
| def framework_name(self): |
| return 'num_fuzzer' |
| |
| def _add_parser_options(self, parser): |
| parser.add_option("--fuzzer-random-seed", default=0, |
| help="Default seed for initializing fuzzer random " |
| "generator") |
| parser.add_option("--tests-count", default=5, type="int", |
| help="Number of tests to generate from each base test. " |
| "Can be combined with --total-timeout-sec with " |
| "value 0 to provide infinite number of subtests. " |
| "When --combine-tests is set it indicates how many " |
| "tests to create in total") |
| |
| # Stress gc |
| parser.add_option("--stress-marking", default=0, type="int", |
| help="probability [0-10] of adding --stress-marking " |
| "flag to the test") |
| parser.add_option("--stress-scavenge", default=0, type="int", |
| help="probability [0-10] of adding --stress-scavenge " |
| "flag to the test") |
| parser.add_option("--stress-compaction", default=0, type="int", |
| help="probability [0-10] of adding --stress-compaction " |
| "flag to the test") |
| parser.add_option("--stress-gc", default=0, type="int", |
| help="probability [0-10] of adding --random-gc-interval " |
| "flag to the test") |
| |
| # Stress tasks |
| parser.add_option("--stress-delay-tasks", default=0, type="int", |
| help="probability [0-10] of adding --stress-delay-tasks " |
| "flag to the test") |
| parser.add_option("--stress-thread-pool-size", default=0, type="int", |
| help="probability [0-10] of adding --thread-pool-size " |
| "flag to the test") |
| |
| # Stress deopt |
| parser.add_option("--stress-deopt", default=0, type="int", |
| help="probability [0-10] of adding --deopt-every-n-times " |
| "flag to the test") |
| parser.add_option("--stress-deopt-min", default=1, type="int", |
| help="extends --stress-deopt to have minimum interval " |
| "between deopt points") |
| |
| # Combine multiple tests |
| parser.add_option("--combine-tests", default=False, action="store_true", |
| help="Combine multiple tests as one and run with " |
| "try-catch wrapper") |
| parser.add_option("--combine-max", default=100, type="int", |
| help="Maximum number of tests to combine") |
| parser.add_option("--combine-min", default=2, type="int", |
| help="Minimum number of tests to combine") |
| |
| # Miscellaneous |
| parser.add_option("--variants", default='default', |
| help="Comma-separated list of testing variants") |
| |
| return parser |
| |
| |
| def _process_options(self, options): |
| if not options.fuzzer_random_seed: |
| options.fuzzer_random_seed = random_utils.random_seed() |
| |
| if options.total_timeout_sec: |
| options.tests_count = 0 |
| |
| if options.combine_tests: |
| if options.combine_min > options.combine_max: |
| print(('min_group_size (%d) cannot be larger than max_group_size (%d)' % |
| options.min_group_size, options.max_group_size)) |
| raise base_runner.TestRunnerError() |
| |
| if options.variants != 'default': |
| print ('Only default testing variant is supported with numfuzz') |
| raise base_runner.TestRunnerError() |
| |
| return True |
| |
| def _get_default_suite_names(self): |
| return DEFAULT_SUITES |
| |
| def _runner_flags(self): |
| """Extra default flags specific to the test runner implementation.""" |
| return ['--no-abort-on-contradictory-flags'] |
| |
| def _get_statusfile_variables(self, options): |
| variables = ( |
| super(NumFuzzer, self)._get_statusfile_variables(options)) |
| variables.update({ |
| 'deopt_fuzzer': bool(options.stress_deopt), |
| 'endurance_fuzzer': bool(options.combine_tests), |
| 'gc_stress': bool(options.stress_gc), |
| 'gc_fuzzer': bool(max([options.stress_marking, |
| options.stress_scavenge, |
| options.stress_compaction, |
| options.stress_gc, |
| options.stress_delay_tasks, |
| options.stress_thread_pool_size])), |
| }) |
| return variables |
| |
| def _do_execute(self, tests, args, options): |
| loader = LoadProc(tests) |
| fuzzer_rng = random.Random(options.fuzzer_random_seed) |
| |
| combiner = self._create_combiner(fuzzer_rng, options) |
| results = self._create_result_tracker(options) |
| execproc = ExecutionProc(options.j) |
| sigproc = self._create_signal_proc() |
| indicators = self._create_progress_indicators( |
| tests.test_count_estimate, options) |
| procs = [ |
| loader, |
| NameFilterProc(args) if args else None, |
| StatusFileFilterProc(None, None), |
| # TODO(majeski): Improve sharding when combiner is present. Maybe select |
| # different random seeds for shards instead of splitting tests. |
| self._create_shard_proc(options), |
| ForgiveTimeoutProc(), |
| combiner, |
| self._create_fuzzer(fuzzer_rng, options), |
| sigproc, |
| ] + indicators + [ |
| results, |
| self._create_timeout_proc(options), |
| self._create_rerun_proc(options), |
| execproc, |
| ] |
| self._prepare_procs(procs) |
| loader.load_initial_tests(initial_batch_size=float('inf')) |
| |
| # TODO(majeski): maybe some notification from loader would be better? |
| if combiner: |
| combiner.generate_initial_tests(options.j * 4) |
| |
| # This starts up worker processes and blocks until all tests are |
| # processed. |
| execproc.run() |
| |
| for indicator in indicators: |
| indicator.finished() |
| |
| print('>>> %d tests ran' % results.total) |
| if results.failed: |
| return utils.EXIT_CODE_FAILURES |
| |
| # Indicate if a SIGINT or SIGTERM happened. |
| return sigproc.exit_code |
| |
| def _is_testsuite_supported(self, suite, options): |
| return not options.combine_tests or suite.test_combiner_available() |
| |
| def _create_combiner(self, rng, options): |
| if not options.combine_tests: |
| return None |
| return CombinerProc(rng, options.combine_min, options.combine_max, |
| options.tests_count) |
| |
| def _create_fuzzer(self, rng, options): |
| return fuzzer.FuzzerProc( |
| rng, |
| self._tests_count(options), |
| self._create_fuzzer_configs(options), |
| self._disable_analysis(options), |
| ) |
| |
| def _tests_count(self, options): |
| if options.combine_tests: |
| return 1 |
| return options.tests_count |
| |
| def _disable_analysis(self, options): |
| """Disable analysis phase when options are used that don't support it.""" |
| return options.combine_tests |
| |
| def _create_fuzzer_configs(self, options): |
| fuzzers = [] |
| def add(name, prob, *args): |
| if prob: |
| fuzzers.append(fuzzer.create_fuzzer_config(name, prob, *args)) |
| |
| add('compaction', options.stress_compaction) |
| add('marking', options.stress_marking) |
| add('scavenge', options.stress_scavenge) |
| add('gc_interval', options.stress_gc) |
| add('threads', options.stress_thread_pool_size) |
| add('delay', options.stress_delay_tasks) |
| add('deopt', options.stress_deopt, options.stress_deopt_min) |
| return fuzzers |
| |
| |
| if __name__ == '__main__': |
| sys.exit(NumFuzzer().execute()) |