blob: c21c83a2ed60a2cfe3b326dcb61aa48deda0eaf7 [file] [log] [blame]
#!/usr/bin/env python2.7
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
import unittest
from host import Host
from fuzzer import Fuzzer
# This file contains the top-level implementations for each of the subcommands
# for "fx fuzz".
def list_fuzzers(args, factory):
"""Implementation of "fx fuzz list"."""
buildenv = factory.buildenv
fuzzer_tests = buildenv.fuzzer_tests(args.name)
match_prefix = ' matching' if args.name else ''
match_suffix = ' for "{}"'.format(args.name) if args.name else ''
if fuzzer_tests:
plural = 's' if len(fuzzer_tests) != 1 else ''
factory.host.echo(
'Found {}{} fuzzer test{}{}:'.format(
len(fuzzer_tests), match_prefix, plural, match_suffix))
for fuzzer_test in fuzzer_tests:
factory.host.echo(' ' + str(fuzzer_test) + '_test')
if plural:
target_was_not_selected = 'These tests correspond to fuzzers, but were not selected by the build arguments'
else:
target_was_not_selected = 'This test corresponds to a fuzzer, but was not selected by the build arguments'
factory.host.echo(
'', target_was_not_selected,
'to be built with a fuzzer toolchain variant.', '',
'To select them, you can use `fx set ... --fuzz-with <sanitizer>`.',
'See https://fuchsia.dev/fuchsia-src/development/testing/fuzzing/build-a-fuzzer',
'for additional details.', '')
fuzzers = buildenv.fuzzers(args.name)
if fuzzers:
plural = 's' if len(fuzzers) != 1 else ''
factory.host.echo(
'Found {}{} fuzzer{}{}:'.format(
len(fuzzers), match_prefix, plural, match_suffix))
for fuzzer in fuzzers:
factory.host.echo(' ' + str(fuzzer))
else:
factory.host.echo(
'No{} fuzzers found{}.'.format(match_prefix, match_suffix))
def start_fuzzer(args, factory):
"""Implementation of "fx fuzz start"."""
fuzzer = factory.create_fuzzer(args)
if not args.monitor:
factory.host.echo(
'Starting {}.'.format(fuzzer),
'Outputs will be written to: {}'.format(fuzzer.output))
if not args.foreground:
factory.host.echo(
'Check status with "fx fuzz check {}".'.format(fuzzer),
'Stop manually with "fx fuzz stop {}".'.format(fuzzer))
fuzzer.start()
if not args.foreground:
cmd = ['python', sys.argv[0], 'start', '--monitor']
if fuzzer.output:
cmd += ['--output', fuzzer.output]
cmd.append(str(fuzzer))
return factory.host.create_process(cmd).popen()
else:
fuzzer.monitor()
factory.host.echo(
'{} has stopped.'.format(fuzzer),
'Output written to: {}.'.format(fuzzer.output))
def check_fuzzer(args, factory):
"""Implementation of "fx fuzz check"."""
blank = True
for fuzzer in factory.buildenv.fuzzers(args.name):
if not args.name and not fuzzer.is_running():
continue
if not fuzzer.is_resolved():
factory.host.echo('{}: NOT INSTALLED'.format(fuzzer))
elif fuzzer.is_running(refresh=True):
factory.host.echo('{}: RUNNING'.format(fuzzer))
else:
factory.host.echo('{}: STOPPED'.format(fuzzer))
if fuzzer.is_resolved():
num, size = fuzzer.corpus.measure()
factory.host.echo(
' Corpus size: {} inputs / {} bytes'.format(num, size))
artifacts = fuzzer.list_artifacts()
if artifacts:
factory.host.echo(' Artifacts:')
for artifact in artifacts:
factory.host.echo(' {}'.format(artifact))
factory.host.echo('')
blank = False
if blank:
factory.host.echo(
'No fuzzers are running.',
'Include \'name\' to check specific fuzzers.')
def stop_fuzzer(args, factory):
"""Implementation of "fx fuzz stop"."""
fuzzer = factory.create_fuzzer(args)
if fuzzer.is_running():
factory.host.echo('Stopping {}.'.format(fuzzer))
fuzzer.stop()
else:
factory.host.echo('{} is already stopped.'.format(fuzzer))
def repro_units(args, factory):
"""Implementation of "fx fuzz repro"."""
fuzzer = factory.create_fuzzer(args)
fuzzer.repro()
def analyze_fuzzer(args, factory):
"""Implementation of "fx fuzz analyze"."""
fuzzer = factory.create_fuzzer(args)
if args.corpora:
for corpus in args.corpora:
fuzzer.corpus.add_from_host(corpus)
if args.dict:
fuzzer.dictionary.replace(args.dict)
if not args.local:
gcs_url = 'gs://corpus.internal.clusterfuzz.com/libFuzzer/fuchsia_{}-{}'.format(
fuzzer.package, fuzzer.executable)
fuzzer.corpus.add_from_gcs(gcs_url)
fuzzer.analyze()
def update_corpus(args, factory):
"""Implementation of "fx fuzz update"."""
# Factory.create_fuzzer interprets args.output as the place to store logs, which is not what we
# want here.
build_gn = None
if args.output:
build_gn = factory.buildenv.abspath(args.output)
args.output = None
fuzzer = factory.create_fuzzer(args, include_tests=True)
elems = fuzzer.corpus.generate_buildfile(build_gn=build_gn)
if len(elems) == 0:
factory.host.echo('Empty corpus added.')
else:
factory.host.echo('Added:')
for elem in elems:
factory.host.echo(' ' + elem)
if not build_gn:
build_gn = '{}/BUILD.gn'.format(fuzzer.corpus.srcdir)
else:
build_gn = '//' + os.path.relpath(
build_gn, factory.buildenv.fuchsia_dir)
factory.host.echo('', build_gn + ' updated.')
def _run_tests(pattern, factory):
lib_dir = os.path.dirname(os.path.abspath(__file__))
test_dir = os.path.join(os.path.dirname(lib_dir), 'test')
tests = unittest.defaultTestLoader.discover(test_dir, pattern=pattern)
if factory.host.tracing:
os.environ[Host.TRACE_ENVVAR] = '1'
verbosity = 2
else:
verbosity = 1
unittest.runner.TextTestRunner(verbosity=verbosity).run(tests)
def run_unittests(args, factory):
"""Runs unittests under the test directory."""
_run_tests('*_test.py', factory)
def run_e2e_test(args, factory):
"""Runs the end-to-end test against a (resolved) fuzzer, if specified."""
if args.name:
# Use Factory.create_fuzzer to resolve the fuzzer name, prompting the
# user if needed.
fuzzer = factory.create_fuzzer(args)
sys.argv[sys.argv.index(args.name)] = str(fuzzer)
_run_tests('test_e2e.py', factory)
def measure_coverage(args, factory):
"""Generates a coverage report of specified fuzzers."""
fuzzer = factory.create_fuzzer(args, include_tests=True)
fuzzer.generate_coverage_report()