blob: 6d15b96c1ac2f50b5a4e77b20a5d8450f1eca35b [file]
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import itertools
from recipe_engine import post_process
from recipe_engine.config_types import Path
from recipe_engine.post_process import LogEquals
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb
from PB.recipes.build.chromium.fuzz import InputProperties
from RECIPE_MODULES.build import chromium_tests_builder_config as ctbc
from dataclasses import dataclass
from recipe_engine.recipe_api import RecipeScriptApi
from recipe_engine.recipe_test_api import RecipeTestApi
from RECIPE_MODULES.build import (
archive,
chromium,
chromium_checkout,
chromium_tests,
chromium_tests_builder_config,
code_coverage,
filter as filter_module,
gn,
profiles,
)
from RECIPE_MODULES.depot_tools import depot_tools, gsutil, tryserver
from RECIPE_MODULES.recipe_engine import (
buildbucket,
context,
file,
json,
path,
platform,
properties,
raw_io,
runtime,
step,
)
@dataclass
class DEPS(RecipeScriptApi):
archive: archive.API
buildbucket: buildbucket.API
chromium: chromium.API
chromium_checkout: chromium_checkout.API
chromium_tests: chromium_tests.API
chromium_tests_builder_config: chromium_tests_builder_config.API
code_coverage: code_coverage.API
context: context.API
depot_tools: depot_tools.API
file: file.API
filter: filter_module.API
gn: gn.API
gsutil: gsutil.API
json: json.API
path: path.API
platform: platform.API
profiles: profiles.API
properties: properties.API
raw_io: raw_io.API
runtime: runtime.API
step: step.API
tryserver: tryserver.API
@dataclass
class TEST_DEPS(RecipeTestApi):
chromium_tests_builder_config: chromium_tests_builder_config.TEST_API
file: file.TEST_API
filter: filter_module.TEST_API
gn: gn.TEST_API
path: path.TEST_API
platform: platform.TEST_API
properties: properties.TEST_API
raw_io: raw_io.TEST_API
PROPERTIES = InputProperties
# The name of the manifest file that will be written to the archive's build
# directory to store metadata about the archive for use by ClusterFuzz.
MANIFEST_FILENAME = 'clusterfuzz_manifest.json'
# The archive schema version that builders without a specified version should
# archive in.
# 0: Archive root is at the build directory and contains `src_root` directory.
# 1: Archive root is at the source directory.
DEFAULT_ARCHIVE_SCHEMA_VERSION = 0
def gn_refs(
api: DEPS, build_dir: Path, step_name, target, output_type='executable'
):
"""Runs gn refs to calculate targets depending on target.
Returns: the set of matched targets.
"""
raw_output = api.gn.refs(
build_dir,
[target],
output_type=output_type,
step_name=step_name,
output_format='label',
)
# Filter out lines starting with "Warning: is_asan "
return {
line for line in raw_output if not line.startswith('Warning: is_asan ')
}
def copy_path(api: DEPS, source_dir: Path, build_dir: Path, path_name: str):
"""Copies the path_name, which could be a file or a directory
into ${build_dir}/src_root.
"""
src = build_dir / path_name
# relative_to() raises ValueError if src is not inside source_dir.
dest = build_dir / 'src_root' / src.relative_to(source_dir)
if api.path.exists(dest):
# Nothing to be done as this file/directory already exists in destination.
return
if api.path.isfile(src):
dstfolder = api.path.dirname(dest)
if not api.path.exists(dstfolder):
# Make sure the destination folder exists.
api.file.ensure_directory('ensure builder cache dir', dstfolder)
api.file.copy('copying file:' + str(src), src, dest)
else:
api.file.copytree('copying directory:' + str(src), src, dest)
def filter_runtime_deps(
runtime_deps: list[str], gn_targets: set[str]
) -> set[str]:
"""Get the subset of runtime dependencies for the specified targets.
The list of runtime dependencies has the following format:
some compiler warning
some compiler warning2
Target target1
runtime_deps
./path/to/dependency1
../../path/to/dependency2
Target target2
runtime_deps
./path/to/dependency3
../../path/to/dependency4
"""
result = set()
is_collecting = False
TARGET_PREFIX = 'Target '
for line in runtime_deps:
if line.startswith(TARGET_PREFIX):
target_name = line[len(TARGET_PREFIX) :]
is_collecting = target_name in gn_targets
elif is_collecting:
# lines corresponding to runtime dependencies start with spaces.
dependency = line.strip()
if dependency and dependency != 'runtime_deps':
result.add(dependency)
return result
# How many elements to return per batch by `batched()`.
#
# Picked by checking how many arguments was finally too much for the Windows
# command line in https://crbug.com/1477042: 629!
BATCH_SIZE = 500
def batched(iterable):
"""Batch `iterable` into lists of length `BATCH_SIZE`.
The last batch may be shorter.
For example, for `BATCH_SIZE == 3`, `batched('ABCDEFG')` yields:
['A', 'B', 'C']
['D', 'E', 'F']
['G']
Shamelessly cribbed from the `itertools` python module's docs.
"""
it = iter(iterable)
# `islice(it, n)` returns an iterator over the first `n` elements of `it`.
while batch := list(itertools.islice(it, BATCH_SIZE)):
yield batch
def get_target_cpu(api: DEPS, gn_args):
args = api.gn.parse_gn_args(gn_args)
return str(args.get('target_cpu')).replace('"', '')
def gn_ls_with_filter(api: DEPS, build_dir, inputs, **kwargs):
"""Wraps api.gn.ls to filter out 'is_asan' warnings."""
raw_output = api.gn.ls(build_dir, inputs, **kwargs)
return {
line for line in raw_output if not line.startswith('Warning: is_asan ')
}
def RunSteps(api: DEPS, properties):
builder_id, builder_config = (
api.chromium_tests_builder_config.lookup_builder()
)
api.chromium_tests.configure_build(builder_config)
# Most CI fuzz bots clobber. But we don't want the trybots clobbering, since
# some of them are on the CQ.
should_clobber = (
api.chromium.c.clobber_before_runhooks and not api.tryserver.is_tryserver
)
update_result = api.chromium_checkout.ensure_checkout()
checkout_dir = update_result.checkout_dir
source_dir = update_result.source_root.path
api.profiles.source_dir = source_dir
build_dir = api.chromium.default_build_dir(source_dir)
api.chromium.ensure_toolchains(checkout_dir)
api.chromium.runhooks(source_dir, build_dir, clobber=should_clobber)
gn_args = api.chromium.mb_gen(source_dir, build_dir, builder_id)
with api.context(cwd=source_dir, env=api.chromium.get_env(source_dir)):
if properties.fuzz_engine == 'fuzzilli':
# compile d8 to run fuzzilli fuzz tests
targets = ['d8']
raw_result = api.chromium.compile(source_dir, build_dir, targets=targets)
if raw_result.status != common_pb.SUCCESS:
return raw_result
else:
# compile targets for libfuzzer or centipede
gn_output_type = 'executable'
if api.chromium.c.TARGET_PLATFORM == 'android':
gn_output_type = 'shared_library'
all_fuzzers = gn_refs(
api,
build_dir,
'calculate all_fuzzers',
'//testing/libfuzzer:fuzzing_engine',
output_type=gn_output_type,
)
if properties.v8_targets_only:
# Some builders only need the V8 targets as the only difference is code
# generated by V8 simulators.
v8_fuzzers = gn_refs(
api,
build_dir,
'calculate v8_fuzzers',
'//v8:fuzzer_support',
output_type=gn_output_type,
)
all_fuzzers = all_fuzzers & v8_fuzzers
elif properties.ios_targets_only:
ios_fuzzers = gn_refs(
api,
build_dir,
'calculate ios_fuzzers',
'//testing/libfuzzer:build_for_ios_clusterfuzz_job',
output_type=gn_output_type,
)
all_fuzzers = all_fuzzers & ios_fuzzers
no_clusterfuzz = gn_refs(
api,
build_dir,
'calculate no_clusterfuzz',
'//testing/libfuzzer:no_clusterfuzz',
output_type=gn_output_type,
)
# If we're collecting coverage, build the main `chrome` binary to ensure
# that all files that make up the binary show up in the coverage report.
# Otherwise, the report includes only files that get built for at least
# one fuzzer, which misses a big proportion of Chromium code, and we get a
# false picture of how well the codebase is covered.
if properties.collect_fuzz_coverage:
# `gn ls ... chrome --as=output` will output `phony/chrome/chrome` [0],
# which can be passed to `compile()` but doesn't represent a path to
# the real `chrome` executable. On Linux we need to give `gn ls` the
# `//chrome:chrome_initial` label that actually outputs the executable.
#
# On macOS, `//chrome:chrome_app` is a `create_bundle` target that
# resolves to `phony/chrome/chrome_app`, which `llvm-cov` cannot open.
# Use `//chrome:chrome_framework_shared_library` instead, which outputs
# the real `Chromium Framework` Mach-O binary containing Chrome's code.
# On Windows, `//chrome:chrome_initial` builds the thin launcher
# (`initialexe/chrome.exe`), whereas Chrome's code lives in
# `//chrome:chrome_dll`.
#
# [0]: https://gn.googlesource.com/gn/+/master/docs/reference.md#phony-rules
if api.platform.is_win:
all_fuzzers.add('//chrome:chrome_dll')
elif api.platform.is_linux:
all_fuzzers.add('//chrome:chrome_initial')
elif api.platform.is_mac:
all_fuzzers.add('//chrome:chrome_framework_shared_library')
targets = sorted(all_fuzzers - no_clusterfuzz)
api.step.active_result.presentation.logs['all_fuzzers'] = sorted(
all_fuzzers
)
api.step.active_result.presentation.logs['no_clusterfuzz'] = sorted(
no_clusterfuzz
)
api.step.active_result.presentation.logs['targets'] = targets
should_analyze = (
not api.runtime.is_experimental
and api.tryserver.is_tryserver
and not properties.collect_fuzz_coverage
)
if should_analyze:
# Filter out all targets that the patch doesn't affect.
affected_files = api.chromium_checkout.get_files_affected_by_patch()
test_targets, compile_targets = api.filter.analyze(
source_dir, build_dir, affected_files, None, targets
)
targets = sorted(test_targets + compile_targets)
if not targets:
return
# On android the fuzzers are built as shared libraries inside APKs, so the
# strategy is to find the library but then recover the main target name by
# removing the __library suffix
if api.chromium.c.TARGET_PLATFORM == 'android':
targets = [t.removesuffix('__library') for t in targets]
# Up until now we work in terms of GN labels so that we can use
# api.filter.analyze above in the trybot case. We now convert the
# GN labels to ninja targets and pass them into compile.
gn_targets = targets
# `targets` can grow to be quite large, larger than can fit on a Windows
# command line... We work around this by batching `gn ls` invocations.
#
# TODO(https://crbug.com/1477042): Define a static list of fuzzer targets
# in the build instead of dynamically generating it based on `gn refs` in
# this manner.
target_set = set()
for target_batch in batched(targets):
target_set |= gn_ls_with_filter(
api, build_dir, target_batch, output_format='output'
)
targets = list(target_set)
targets.sort() # Ensure stable order for tests.
# For iOS, the target list from |api.gn.refs| is a list of paths like
# obj/.../XXX_fuzzer. The last part of the path is the target name to be
# compiled.
if api.chromium.c.TARGET_PLATFORM == 'ios':
targets = [api.path.basename(target) for target in targets]
# Same as above, the list of targets can grow so large that Windows chokes
# on a single `ninja` invocation against the full list.
#
# TODO(https://crbug.com/1477042): Define a static target that builds all
# fuzzers for ClusterFuzz instead of dynamically generating it like this.
raw_result = None
for target_batch in batched(targets):
raw_result = api.chromium.compile(
source_dir, build_dir, targets=target_batch
)
if raw_result.status != common_pb.SUCCESS:
return raw_result
if not properties.collect_fuzz_coverage and not api.tryserver.is_tryserver:
assert properties.upload_bucket, 'upload_bucket property is required'
if properties.collect_fuzz_coverage:
try:
corpora_dir = source_dir / 'current-corpora-from-clusterfuzz'
profdata_dir = source_dir / 'out' / 'profdata-output-dir'
api.file.rmtree('ensure corpora directory blank', corpora_dir)
api.file.ensure_directory('make corpora directory', corpora_dir)
api.file.rmtree('ensure profdata directory blank', profdata_dir)
api.file.ensure_directory('make profdata directory', profdata_dir)
download_cmd = [
'python3',
'tools/code_coverage/download_fuzz_corpora.py',
'--download-dir',
corpora_dir,
'--build-dir',
build_dir,
'--corpora-type',
properties.fuzz_engine,
]
if properties.fuzz_engine == 'fuzzilli':
download_cmd.extend(['--arch', get_target_cpu(api, gn_args)])
api.step('download corpora', download_cmd)
run_cmd = [
'python3',
'tools/code_coverage/run_all_fuzzers.py',
'--fuzzer-binaries-dir',
build_dir,
'--fuzzer-corpora-dir',
corpora_dir,
'--profdata-outdir',
profdata_dir,
'--fuzzer',
properties.fuzz_engine,
]
if properties.fuzz_engine != 'fuzzilli':
target_list_dir = source_dir / 'out' / 'target-list-dir'
api.file.rmtree('ensure target list directory blank', target_list_dir)
api.file.ensure_directory(
'make target list directory', target_list_dir
)
run_cmd.extend(['--target-list-dir', target_list_dir])
if api.platform.is_linux:
withxvfb_path = api.repo_resource('recipes', 'withxvfb.py')
withxvfb_args = [
'--target',
api.chromium.c.build_config_fs,
'--build-dir=%s' % build_dir,
]
run_cmd = (
['python3', withxvfb_path] + withxvfb_args + ['--'] + run_cmd
)
api.step('run all fuzzers', run_cmd)
if properties.fuzz_engine != 'fuzzilli':
# For libfuzzer or centipede, upload the succeeded targets in a
# json file to GCS
gcs_dir = f'fuzz-targets/{properties.fuzz_engine}/'
revision = api.buildbucket.gitiles_commit.id[:7]
targets_file = api.path.join(
target_list_dir, properties.fuzz_engine + '.json'
)
api.gsutil.upload(
targets_file, 'code-coverage-data', f'{gcs_dir}{revision}/'
)
successful_targets = api.file.read_json(
'read successful targets', targets_file
)
for target in successful_targets:
profdata_path = profdata_dir / f'{target}.profdata'
api.code_coverage.get_chromium_fuzz_coverage(
source_dir,
build_dir,
profdata_path,
{target},
overall=False,
use_cache=True,
)
profdata_path = source_dir / 'total_fuzz_coverage.profdata'
llvm_profdata_path = api.profiles.llvm_profdata_exec
api.step(
'merge all fuzzers',
[
'python3',
'tools/code_coverage/merge_all_profdata.py',
'--profdata-dir',
profdata_dir,
'--outfile',
profdata_path,
'--llvm-profdata',
llvm_profdata_path,
],
)
api.code_coverage.get_chromium_fuzz_coverage(
source_dir, build_dir, profdata_path, targets, use_cache=True
)
except api.step.StepFailure:
api.step.empty('could not process fuzz coverage')
raise
else:
# copy data deps outside the build directory
# Needed for tests
api.path.mock_add_file('[CACHE]/builder/src/path2')
archive_schema_version = (
properties.archive_schema_version
if properties.HasField('archive_schema_version')
else DEFAULT_ARCHIVE_SCHEMA_VERSION
)
# Archive schema version >= 1 compiles runtime dependencies and archives
# based on `.runtime_deps` files.
if archive_schema_version != 0:
archive_root = source_dir
api.archive.clusterfuzz_archive_targets(
source_dir=source_dir,
archive_root=archive_root,
update_properties=update_result.properties,
gs_bucket=properties.upload_bucket,
archive_path=properties.archive_path,
compile_targets=targets,
build_dir=build_dir,
archive_schema_version=archive_schema_version,
fuzz_targets=targets,
gs_acl='public-read',
)
return
if api.tryserver.is_tryserver:
return
archive_root = build_dir
with api.step.nest('collect all runtime dependencies') as step_result:
set_of_gn_targets = set(gn_targets)
raw_list_of_runtime_deps = api.gn.desc(
build_dir,
'*',
'runtime_deps',
step_name='get runtime dependencies with pattern *',
use_cache=True,
)
runtime_deps = filter_runtime_deps(
raw_list_of_runtime_deps, set_of_gn_targets
)
runtime_deps = sorted(runtime_deps)
step_result.logs['runtime_dependencies'] = runtime_deps
with api.step.nest('copy runtime dependencies to build directory'):
for path in runtime_deps:
dep_path = build_dir / path
if build_dir not in dep_path.parents and dep_path != build_dir:
copy_path(api, source_dir, build_dir, path)
fuzz_target_paths = [
api.path.relpath(build_dir / target, archive_root) for target in targets
]
manifest_dict = {
'archive_schema_version': 0,
'fuzz_targets': fuzz_target_paths,
}
api.file.write_json(
'write archive manifest',
archive_root / MANIFEST_FILENAME,
manifest_dict,
)
api.archive.clusterfuzz_archive(
source_dir=source_dir,
archive_root=archive_root,
update_properties=update_result.properties,
gs_bucket=properties.upload_bucket,
archive_path=properties.archive_path,
gs_acl='public-read',
)
def GenTests(api: TEST_DEPS):
def generate_test(
is_try=False,
is_coverage=False,
is_ios=False,
is_v8=False,
coverage_metadata_failure=False,
engine='libfuzzer',
archive_schema_version=None,
archive_path='linux-release/libfuzzer-linux-release',
drop_expectation=True,
):
test = api.properties(
upload_bucket='chromium-browser-libfuzzer',
upload_directory='fuzz',
v8_targets_only=is_v8,
ios_targets_only=is_ios,
collect_fuzz_coverage=is_coverage,
fuzz_engine=engine,
archive_path=archive_path,
)
if archive_schema_version is not None:
test += api.properties(archive_schema_version=archive_schema_version)
else:
archive_schema_version = DEFAULT_ARCHIVE_SCHEMA_VERSION
if engine != 'fuzzilli':
targets = api.raw_io.output_text(
'target1\ntarget2\ntarget3\nfuzzer.exe\n'
)
test += api.step_data('calculate all_fuzzers', stdout=targets)
test += api.step_data(
'calculate no_clusterfuzz', stdout=api.raw_io.output_text('target1')
)
if is_v8:
test += api.step_data('calculate v8_fuzzers', stdout=targets)
if is_ios:
test += api.step_data('calculate ios_fuzzers', stdout=targets)
# Tryjobs provide their own mocks for 'list gn targets'
if not is_try:
test += api.step_data('list gn targets', stdout=targets)
if not (is_try or is_coverage):
if archive_schema_version == 0:
test += api.post_process(
post_process.MustRun, 'collect all runtime dependencies'
)
# this will lead to us having ../../path2 and ../../path4 as runtime
# dependencies to copy
step_output = (
'some warning1\n\n'
'some warning2\n'
'Target target1\n'
'runtime_deps\n'
' ./target1\n'
' ../../path14\n'
' ./path15\n\n\n'
'Target target2\n'
'runtime_deps\n'
' ./target2\n'
' ../../path1\n'
' ../../path2\n'
'Target target3\n'
'runtime_deps\n'
' ./target3\n'
' ./path3\n'
' ../../path4\n'
'Target fuzzer.exe\n'
'runtime_deps\n'
' ./fuzzer.exe\n'
'Target target5\n'
'runtime_deps\n'
' ./path16\n'
' ../../path17\n'
)
test += api.step_data(
'collect all runtime dependencies.get runtime '
'dependencies with pattern *',
stdout=api.raw_io.output_text(step_output),
)
# ../../path1, ../../path2 and ../../path4 need to be copied.
test += api.post_process(
LogEquals,
'collect all runtime dependencies',
'runtime_dependencies',
'../../path1\n'
'../../path2\n'
'../../path4\n'
'./fuzzer.exe\n'
'./path3\n'
'./target2\n'
'./target3',
)
test += api.post_process(
post_process.MustRun, 'copy runtime dependencies to build directory'
)
test += api.post_process(
post_process.MustRun,
'copy runtime dependencies to build directory.copying file:'
'[CACHE]/builder/src/path2',
)
test += api.post_process(
post_process.MustRun,
'copy runtime dependencies to build directory.copying directory:'
'[CACHE]/builder/src/path4',
)
else:
test += api.post_process(
post_process.MustRun,
'collect runtime deps for compile targets',
)
test += api.post_process(
post_process.MustRun,
'collect runtime deps for compile targets.read fuzzer.runtime_deps',
)
test += api.post_process(
post_process.MustRun,
'collect runtime deps for compile targets.read target1.runtime_deps',
)
test += api.post_process(
post_process.MustRun,
'collect runtime deps for compile targets.read target2.runtime_deps',
)
test += api.post_process(
post_process.MustRun,
'collect runtime deps for compile targets.read target3.runtime_deps',
)
expected_paths = '\n'.join(
[
'out/1826-some-ci-bot/fuzzer.exe',
'out/1826-some-ci-bot/fuzzer.runtime_deps',
'out/1826-some-ci-bot/fuzzer_dependency',
'out/1826-some-ci-bot/target1',
'out/1826-some-ci-bot/target1.runtime_deps',
'out/1826-some-ci-bot/target1_dependency',
'out/1826-some-ci-bot/target2',
'out/1826-some-ci-bot/target2.runtime_deps',
'out/1826-some-ci-bot/target2_dependency',
'out/1826-some-ci-bot/target3',
'out/1826-some-ci-bot/target3.runtime_deps',
'out/1826-some-ci-bot/target3_dependency',
'testing/data/fuzzer_seed.txt',
]
)
test += api.post_process(
LogEquals,
'collect runtime deps for compile targets',
'paths_to_archive',
expected_paths,
)
test += api.post_process(post_process.MustRun, 'gsutil upload')
if is_coverage:
test += api.post_process(
post_process.MustRun, 'process fuzz coverage (overall)'
)
retcode = 0
if coverage_metadata_failure:
retcode = 1
if engine != 'fuzzilli':
test += api.step_data(
'read successful targets',
api.file.read_json(['target1', 'target2', 'target3']),
)
test += api.post_process(
post_process.MustRun, 'process fuzz coverage (target1)'
)
test += api.post_process(
post_process.MustRun, 'process fuzz coverage (target2)'
)
test += api.post_process(
post_process.MustRun, 'process fuzz coverage (target3)'
)
test += api.step_data(
'process fuzz coverage (overall).generate coverage metadata',
retcode=retcode,
)
if is_ios:
test += api.properties(xcode_build_version='12345')
if archive_schema_version != 0:
test += api.post_process(
post_process.DoesNotRun,
'copy runtime dependencies to build directory',
)
if not is_try:
# Verify that targets and runtime_deps are in the archive.
# targets in test are fuzzer.exe, target1, target2, target3
# runtime_deps in test (filtered) are ../../path1, ../../path2,
# ../../path4, ./path3
expected_paths = (
'["clusterfuzz_manifest.json", '
'"out/1826-some-ci-bot/args.gn", '
'"out/1826-some-ci-bot/fuzzer.exe", '
'"out/1826-some-ci-bot/fuzzer.runtime_deps", '
'"out/1826-some-ci-bot/fuzzer_dependency", '
'"out/1826-some-ci-bot/target1", '
'"out/1826-some-ci-bot/target1.runtime_deps", '
'"out/1826-some-ci-bot/target1_dependency", '
'"out/1826-some-ci-bot/target2", '
'"out/1826-some-ci-bot/target2.runtime_deps", '
'"out/1826-some-ci-bot/target2_dependency", '
'"out/1826-some-ci-bot/target3", '
'"out/1826-some-ci-bot/target3.runtime_deps", '
'"out/1826-some-ci-bot/target3_dependency", '
'"testing/data/fuzzer_seed.txt"]'
)
test += api.post_process(
post_process.StepCommandContains,
'zipping',
[expected_paths],
)
expected_manifest_dict = (
'{"archive_schema_version": 1, '
'"fuzz_targets": ['
'"out/1826-some-ci-bot/fuzzer.exe", '
'"out/1826-some-ci-bot/target1", '
'"out/1826-some-ci-bot/target2", '
'"out/1826-some-ci-bot/target3"]}'
)
test += api.post_process(
LogEquals,
'write archive manifest',
'clusterfuzz_manifest.json',
expected_manifest_dict,
)
if drop_expectation:
test += api.post_process(post_process.DropExpectation)
return test
yield api.test(
'linux-32',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
chromium_apply_config=['clobber'],
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_BITS': 32,
},
),
},
}
),
),
api.platform.name('linux'),
api.post_process(post_process.MustRun, 'clobber'),
generate_test(),
)
yield api.test(
'linux-dbg',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'BUILD_CONFIG': 'Debug',
},
),
},
}
),
),
api.path.exists(
api.path.cache_dir / 'builder/src/out/1826-some-ci-bot/src_root/path1'
),
api.platform.name('linux'),
generate_test(),
)
yield api.test(
'schema_v1',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
generate_test(archive_schema_version=1),
)
yield api.test(
'v8',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
generate_test(is_v8=True),
)
yield api.test(
'ios',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'ios',
},
),
},
}
),
),
api.platform.name('mac'),
generate_test(is_ios=True),
)
yield api.test(
'android',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'android',
},
),
},
}
),
),
api.platform.name('linux'),
generate_test(
archive_path='linux-release-fuzz/libfuzzer-linux-release',
drop_expectation=False,
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text(
'//foo:target1__library\n//foo:target2__library\n'
'//foo:fuzzer.exe__library\n'
),
),
api.step_data(
'list gn targets',
stdout=api.raw_io.output_text('fuzzer.exe\ntarget1\ntarget2'),
),
api.step_data(
'collect all runtime dependencies.'
'get runtime dependencies with pattern *',
stdout=api.raw_io.output_text(
'Target //foo:target1\n'
'runtime_deps\n'
' ../../path1\n'
' ../../path2\n'
' ../../path4\n'
' ./path3\n'
' ./target2\n'
' ./target3\n'
'Target //foo:fuzzer.exe\n'
'runtime_deps\n'
' ./fuzzer.exe\n'
),
),
api.post_check(
post_process.StepCommandContains, 'list gn targets', ['//foo:target1']
),
api.post_check(
post_process.StepCommandDoesNotContain,
'list gn targets',
['//foo:target1__library'],
),
)
yield api.test(
'coverage',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
generate_test(is_coverage=True, drop_expectation=False),
api.post_check(
post_process.MustRun,
'ensure corpora directory blank',
),
api.post_check(
post_process.StepCommandContains,
'run all fuzzers',
['RECIPE_REPO[build]/recipes/withxvfb.py'],
),
api.post_check(
post_process.StepCommandContains,
'merge all fuzzers',
[
'[CACHE]/builder/src/third_party/llvm-build/Release+Asserts/bin/llvm-profdata'
],
),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'mac-coverage',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'mac',
},
),
},
}
),
),
api.platform.name('mac'),
generate_test(is_coverage=True, drop_expectation=False),
api.post_check(
post_process.MustRun,
'ensure corpora directory blank',
),
api.post_check(
post_process.StepCommandDoesNotContain,
'run all fuzzers',
['RECIPE_REPO[build]/recipes/withxvfb.py'],
),
api.post_check(
post_process.StepCommandContains,
'merge all fuzzers',
[
'[CACHE]/builder/src/third_party/llvm-build/Release+Asserts/bin/llvm-profdata'
],
),
api.post_process(post_process.DropExpectation),
)
# Verify macOS fuzz coverage builds //chrome:chrome_framework_shared_library
# rather than the phony //chrome:chrome_app bundle target.
yield api.test(
'mac-coverage-chrome-binary-is-not-phony',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'mac',
},
),
},
}
),
),
api.platform.name('mac'),
generate_test(is_coverage=True, drop_expectation=False),
api.post_check(
post_process.StepCommandContains,
'list gn targets',
['//chrome:chrome_framework_shared_library'],
),
api.post_check(
post_process.StepCommandDoesNotContain,
'list gn targets',
['//chrome:chrome_app'],
),
api.post_process(post_process.DropExpectation),
)
# Verify Windows fuzz coverage builds //chrome:chrome_dll rather than the
# //chrome:chrome_initial launcher executable.
yield api.test(
'win-coverage-chrome-binary-is-not-the-launcher',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'win',
},
),
},
}
),
),
api.platform.name('win'),
generate_test(is_coverage=True, drop_expectation=False),
api.post_check(
post_process.StepCommandContains,
'list gn targets',
['//chrome:chrome_dll'],
),
api.post_check(
post_process.StepCommandDoesNotContain,
'list gn targets',
['//chrome:chrome_initial'],
),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'win-coverage',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
chromium_config_kwargs={
'TARGET_PLATFORM': 'win',
},
),
},
}
),
),
api.platform.name('win'),
generate_test(is_coverage=True, drop_expectation=False),
api.post_check(
post_process.MustRun,
'ensure corpora directory blank',
),
api.post_check(
post_process.StepCommandDoesNotContain,
'run all fuzzers',
['RECIPE_REPO[build]\\recipes\\withxvfb.py'],
),
api.post_check(
post_process.StepCommandContains,
'merge all fuzzers',
[
'[CACHE]\\builder\\src\\third_party\\llvm-build\\Release+Asserts\\bin\\llvm-profdata.exe'
],
),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'coverage_metadata_generation_failure',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
api.expect_status('FAILURE'),
generate_test(is_coverage=True, coverage_metadata_failure=True),
)
yield api.test(
'fuzzilli-coverage',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
generate_test(is_coverage=True, engine='fuzzilli'),
)
yield api.test(
'centipede-coverage',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
generate_test(is_coverage=True, engine='centipede'),
)
yield api.test(
'd8-compile-failure',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
api.properties(fuzz_engine='fuzzilli'),
api.step_data('compile', retcode=1),
api.expect_status('FAILURE'),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'compile_failure',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='Libfuzzer Upload Mac ASan',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'Libfuzzer Upload Mac ASan': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('mac'),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text('//foo/bar:target1'),
),
api.step_data('list gn targets', stdout=api.raw_io.output_text('target1')),
api.step_data('compile', retcode=1),
api.expect_status('FAILURE'),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'basic_linux_tryjob_no_compile',
api.chromium_tests_builder_config.try_build(
builder_group='tryserver.chromium.linux',
builder='linux-libfuzzer-asan-rel',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'fuzz-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
try_db=ctbc.TryDatabase.create(
{
'tryserver.chromium.linux': {
'linux-libfuzzer-asan-rel': ctbc.TrySpec.create_for_single_mirror(
'chromium.fuzz', 'fuzz-ci-bot'
),
},
}
),
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text(
'//foo/bar:target1\n//foo/bar:target2\n//foo/bar:target3'
),
),
api.step_data(
'calculate no_clusterfuzz',
stdout=api.raw_io.output_text('//foo/bar:target1'),
),
api.filter.no_dependency(),
)
yield api.test(
'basic_linux_tryjob_with_compile',
api.chromium_tests_builder_config.try_build(
builder_group='tryserver.chromium.linux',
builder='linux-libfuzzer-asan-rel',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'fuzz-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
chromium_apply_config=['clobber'],
gclient_config='chromium',
),
},
}
),
try_db=ctbc.TryDatabase.create(
{
'tryserver.chromium.linux': {
'linux-libfuzzer-asan-rel': ctbc.TrySpec.create_for_single_mirror(
'chromium.fuzz', 'fuzz-ci-bot'
),
},
}
),
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text(
'\n'.join(
['//foo/bar:target1', '//foo/bar:target2', '//foo/bar:target3']
)
),
),
api.step_data(
'calculate no_clusterfuzz',
stdout=api.raw_io.output_text('//foo/bar:target1'),
),
api.filter.analyze_output(
status='Found dependency',
test_targets=[],
compile_targets=['//foo/bar:target2'],
),
api.step_data('list gn targets', stdout=api.raw_io.output_text('target2')),
api.post_process(post_process.DoesNotRun, 'clobber'),
)
yield api.test(
'basic_linux_tryjob_with_compile_many_targets',
api.chromium_tests_builder_config.try_build(
builder_group='tryserver.chromium.linux',
builder='linux-libfuzzer-asan-rel',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'fuzz-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
try_db=ctbc.TryDatabase.create(
{
'tryserver.chromium.linux': {
'linux-libfuzzer-asan-rel': ctbc.TrySpec.create_for_single_mirror(
'chromium.fuzz', 'fuzz-ci-bot'
),
},
}
),
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text(
'\n'.join(f'//foo/bar:target{i}' for i in range(1500))
),
),
api.step_data(
'calculate no_clusterfuzz', stdout=api.raw_io.output_text('')
),
api.step_data(
'list gn targets',
stdout=api.raw_io.output_text(
'\n'.join(f'target{i}' for i in range(500))
),
),
api.step_data(
'list gn targets (2)',
stdout=api.raw_io.output_text(
'\n'.join(f'target{i}' for i in range(500, 1000))
),
),
api.step_data(
'list gn targets (3)',
stdout=api.raw_io.output_text(
'\n'.join(f'target{i}' for i in range(1000, 1500))
),
),
# So many targets get batched into several gn ls invocations.
api.post_check(post_process.MustRun, 'list gn targets'),
api.post_check(post_process.MustRun, 'list gn targets (2)'),
api.post_check(post_process.MustRun, 'list gn targets (3)'),
api.post_check(post_process.DoesNotRun, 'list gn targets (4)'),
# Same goes for ninja invocations.
api.post_check(post_process.MustRun, 'compile'),
api.post_check(post_process.MustRun, 'compile (3)'),
api.post_check(post_process.MustRun, 'compile (3)'),
api.post_check(post_process.DoesNotRun, 'compile (4)'),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'filter_asan_warning',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
api.properties(
upload_bucket='chromium-browser-libfuzzer',
upload_directory='fuzz',
archive_path='linux-release/libfuzzer-linux-release',
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text(
'Warning: is_asan is not defined\n//foo/bar:target1\n'
),
),
api.step_data(
'calculate no_clusterfuzz', stdout=api.raw_io.output_text('')
),
api.step_data('list gn targets', stdout=api.raw_io.output_text('target1')),
api.post_check(
post_process.StepCommandDoesNotContain,
'list gn targets',
['Warning: is_asan is not defined'],
),
api.post_process(post_process.DropExpectation),
)
yield api.test(
'filter_asan_warning_in_gn_ls',
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
api.platform.name('linux'),
api.properties(
upload_bucket='chromium-browser-libfuzzer',
upload_directory='fuzz',
archive_path='linux-release/libfuzzer-linux-release',
),
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text('//foo/bar:target1\n'),
),
api.step_data(
'calculate no_clusterfuzz', stdout=api.raw_io.output_text('')
),
api.step_data(
'list gn targets',
stdout=api.raw_io.output_text(
'Warning: is_asan is not defined\ntarget1\n'
),
),
api.post_check(post_process.StepCommandContains, 'compile', ['target1']),
api.post_check(
post_process.StepCommandDoesNotContain,
'compile',
['Warning: is_asan is not defined'],
),
api.post_process(post_process.DropExpectation),
)
def upload_test(name, *args, upload_path=''):
test_args = [
api.chromium_tests_builder_config.ci_build(
builder_group='chromium.fuzz',
builder='some-ci-bot',
builder_db=ctbc.BuilderDatabase.create(
{
'chromium.fuzz': {
'some-ci-bot': ctbc.BuilderSpec.create(
chromium_config='chromium',
gclient_config='chromium',
),
},
}
),
),
*args,
api.step_data(
'calculate all_fuzzers',
stdout=api.raw_io.output_text('//foo/bar:target1\n'),
),
api.step_data(
'calculate no_clusterfuzz', stdout=api.raw_io.output_text('')
),
]
if upload_path:
test_args.append(
api.post_check(
post_process.StepCommandContains,
'gsutil upload',
[f'{upload_path}-170242.zip'],
)
)
test_args.append(api.post_process(post_process.DropExpectation))
return api.test(f'upload path {name}', *test_args)
yield upload_test(
'basic',
api.platform.name('linux'),
api.properties(
upload_bucket='bucket',
archive_path='linux-release/libfuzzer-linux-release',
),
upload_path='gs://bucket/linux-release/libfuzzer-linux-release',
)
yield upload_test(
'custom path',
api.platform.name('linux'),
api.properties(
upload_bucket='bucket',
archive_path='bleep-bloop/foo-bar',
),
upload_path='gs://bucket/bleep-bloop/foo-bar',
)
yield upload_test(
'missing archive_path',
api.platform.name('linux'),
api.properties(
upload_bucket='bucket',
),
api.expect_exception('AssertionError'),
)