aboutsummaryrefslogtreecommitdiff
path: root/infra
diff options
context:
space:
mode:
authorjonathanmetzman <31354670+jonathanmetzman@users.noreply.github.com>2021-06-30 07:34:42 -0700
committerGitHub <noreply@github.com>2021-06-30 07:34:42 -0700
commit0672aa4e1f2cc332f6e6a020259c35007292cd91 (patch)
treed3a6dee092ff2722c5f9e28de1d21b652026092c /infra
parenta9c49afb784f0deaaffba8b388333c36074550c2 (diff)
downloadoss-fuzz-0672aa4e1f2cc332f6e6a020259c35007292cd91.tar.gz
[CIFuzz] Don't make everything a subdirectory of /out (#5970)
Use different subdirectories of workspace for builds, old builds, coverage reports, corpora and artifacts/testscases.
Diffstat (limited to 'infra')
-rw-r--r--infra/cifuzz/affected_fuzz_targets_test.py8
-rw-r--r--infra/cifuzz/build_fuzzers.py33
-rw-r--r--infra/cifuzz/build_fuzzers_entrypoint.py6
-rw-r--r--infra/cifuzz/build_fuzzers_test.py28
-rw-r--r--infra/cifuzz/clusterfuzz_deployment.py134
-rw-r--r--infra/cifuzz/clusterfuzz_deployment_test.py77
-rw-r--r--infra/cifuzz/docker.py61
-rw-r--r--infra/cifuzz/docker_test.py39
-rw-r--r--infra/cifuzz/fuzz_target.py61
-rw-r--r--infra/cifuzz/fuzz_target_test.py43
-rw-r--r--infra/cifuzz/generate_coverage_report.py20
-rw-r--r--infra/cifuzz/generate_coverage_report_test.py22
-rw-r--r--infra/cifuzz/run_fuzzers.py69
-rw-r--r--infra/cifuzz/run_fuzzers_test.py106
-rwxr-xr-xinfra/cifuzz/test_data/build-out/example_crash_fuzzer (renamed from infra/cifuzz/test_data/out/example_crash_fuzzer)bin4375872 -> 4375872 bytes
-rwxr-xr-xinfra/cifuzz/test_data/build-out/example_nocrash_fuzzer (renamed from infra/cifuzz/test_data/out/example_nocrash_fuzzer)bin4376224 -> 4376224 bytes
-rwxr-xr-xinfra/cifuzz/test_data/memory/build-out/curl_fuzzer_memory (renamed from infra/cifuzz/test_data/memory/out/curl_fuzzer_memory)bin9768680 -> 9768680 bytes
-rwxr-xr-xinfra/cifuzz/test_data/undefined/build-out/curl_fuzzer_undefined (renamed from infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined)bin14401312 -> 14401312 bytes
-rw-r--r--infra/cifuzz/test_helpers.py8
-rw-r--r--infra/utils_test.py2
20 files changed, 374 insertions, 343 deletions
diff --git a/infra/cifuzz/affected_fuzz_targets_test.py b/infra/cifuzz/affected_fuzz_targets_test.py
index d697f4d3f..96d6df7aa 100644
--- a/infra/cifuzz/affected_fuzz_targets_test.py
+++ b/infra/cifuzz/affected_fuzz_targets_test.py
@@ -30,15 +30,15 @@ EXAMPLE_PROJECT = 'example'
EXAMPLE_FILE_CHANGED = 'test.txt'
-TEST_DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- 'test_data')
+TEST_DATA_OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ 'test_data', 'build-out')
class RemoveUnaffectedFuzzTargets(unittest.TestCase):
"""Tests remove_unaffected_fuzzers."""
- TEST_FUZZER_1 = os.path.join(TEST_DATA_PATH, 'out', 'example_crash_fuzzer')
- TEST_FUZZER_2 = os.path.join(TEST_DATA_PATH, 'out', 'example_nocrash_fuzzer')
+ TEST_FUZZER_1 = os.path.join(TEST_DATA_OUT_PATH, 'example_crash_fuzzer')
+ TEST_FUZZER_2 = os.path.join(TEST_DATA_OUT_PATH, 'example_nocrash_fuzzer')
# yapf: disable
@parameterized.parameterized.expand([
diff --git a/infra/cifuzz/build_fuzzers.py b/infra/cifuzz/build_fuzzers.py
index 4254067d3..763659749 100644
--- a/infra/cifuzz/build_fuzzers.py
+++ b/infra/cifuzz/build_fuzzers.py
@@ -50,10 +50,9 @@ class Builder: # pylint: disable=too-many-instance-attributes
def __init__(self, config, ci_system):
self.config = config
self.ci_system = ci_system
- self.out_dir = os.path.join(config.workspace, 'out')
- os.makedirs(self.out_dir, exist_ok=True)
- self.work_dir = os.path.join(config.workspace, 'work')
- os.makedirs(self.work_dir, exist_ok=True)
+ self.workspace = docker.Workspace(config)
+ self.workspace.initialize_dir(self.workspace.out)
+ self.workspace.initialize_dir(self.workspace.work)
self.image_repo_path = None
self.host_repo_path = None
self.repo_manager = None
@@ -75,13 +74,14 @@ class Builder: # pylint: disable=too-many-instance-attributes
"""Moves the source code we want to fuzz into the project builder and builds
the fuzzers from that source code. Returns True on success."""
docker_args, docker_container = docker.get_base_docker_run_args(
- self.out_dir, self.config.sanitizer, self.config.language)
+ self.workspace, self.config.sanitizer, self.config.language)
if not docker_container:
docker_args.extend(
_get_docker_build_fuzzers_args_not_container(self.host_repo_path))
if self.config.sanitizer == 'memory':
- docker_args.extend(_get_docker_build_fuzzers_args_msan(self.work_dir))
+ docker_args.extend(
+ _get_docker_build_fuzzers_args_msan(self.workspace.work))
self.handle_msan_prebuild(docker_container)
docker_args.extend([
@@ -111,7 +111,7 @@ class Builder: # pylint: disable=too-many-instance-attributes
"""Post-build step for MSAN builds. Patches the build to use MSAN
libraries."""
helper.docker_run([
- '--volumes-from', container, '-e', f'WORK={self.work_dir}',
+ '--volumes-from', container, '-e', f'WORK={self.workspace.work}',
docker.MSAN_LIBS_BUILDER_TAG, 'patch_build.py', '/out'
])
@@ -121,7 +121,7 @@ class Builder: # pylint: disable=too-many-instance-attributes
logging.info('Copying MSAN libs.')
helper.docker_run([
'--volumes-from', container, docker.MSAN_LIBS_BUILDER_TAG, 'bash', '-c',
- f'cp -r /msan {self.work_dir}'
+ f'cp -r /msan {self.workspace.work}'
])
def build(self):
@@ -146,7 +146,7 @@ class Builder: # pylint: disable=too-many-instance-attributes
changed_files = self.ci_system.get_changed_code_under_test(
self.repo_manager)
affected_fuzz_targets.remove_unaffected_fuzz_targets(
- self.config.project_name, self.out_dir, changed_files,
+ self.config.project_name, self.workspace.out, changed_files,
self.image_repo_path)
return True
@@ -178,7 +178,7 @@ def build_fuzzers(config):
return builder.build()
-def check_fuzzer_build(out_dir,
+def check_fuzzer_build(workspace,
sanitizer,
language,
allowed_broken_targets_percentage=None):
@@ -191,14 +191,15 @@ def check_fuzzer_build(out_dir,
Returns:
True if fuzzers are correct.
"""
- if not os.path.exists(out_dir):
- logging.error('Invalid out directory: %s.', out_dir)
+ if not os.path.exists(workspace.out):
+ logging.error('Invalid out directory: %s.', workspace.out)
return False
- if not os.listdir(out_dir):
- logging.error('No fuzzers found in out directory: %s.', out_dir)
+ if not os.listdir(workspace.out):
+ logging.error('No fuzzers found in out directory: %s.', workspace.out)
return False
- docker_args, _ = docker.get_base_docker_run_args(out_dir, sanitizer, language)
+ docker_args, _ = docker.get_base_docker_run_args(workspace, sanitizer,
+ language)
if allowed_broken_targets_percentage is not None:
docker_args += [
'-e',
@@ -216,7 +217,7 @@ def check_fuzzer_build(out_dir,
def _get_docker_build_fuzzers_args_not_container(host_repo_path):
"""Returns arguments to the docker build arguments that are needed to use
- |host_out_dir| when the host of the OSS-Fuzz builder container is not
+ |host_repo_path| when the host of the OSS-Fuzz builder container is not
another container."""
return ['-v', f'{host_repo_path}:{host_repo_path}']
diff --git a/infra/cifuzz/build_fuzzers_entrypoint.py b/infra/cifuzz/build_fuzzers_entrypoint.py
index 04f562068..2ab7aa361 100644
--- a/infra/cifuzz/build_fuzzers_entrypoint.py
+++ b/infra/cifuzz/build_fuzzers_entrypoint.py
@@ -13,11 +13,11 @@
# limitations under the License.
"""Builds a specific OSS-Fuzz project's fuzzers for CI tools."""
import logging
-import os
import sys
import build_fuzzers
import config_utils
+import docker
# pylint: disable=c-extension-no-member
# pylint gets confused because of the relative import of cifuzz.
@@ -71,15 +71,13 @@ def main():
config.project_name, config.commit_sha, config.pr_ref)
return returncode
- out_dir = os.path.join(config.workspace, 'out')
-
if not config.bad_build_check:
# If we've gotten to this point and we don't need to do bad_build_check,
# then the build has succeeded.
returncode = 0
# yapf: disable
elif build_fuzzers.check_fuzzer_build(
- out_dir,
+ docker.Workspace(config),
config.sanitizer,
config.language,
allowed_broken_targets_percentage=config.allowed_broken_targets_percentage
diff --git a/infra/cifuzz/build_fuzzers_test.py b/infra/cifuzz/build_fuzzers_test.py
index 35194f754..b88552301 100644
--- a/infra/cifuzz/build_fuzzers_test.py
+++ b/infra/cifuzz/build_fuzzers_test.py
@@ -136,7 +136,7 @@ class BuildFuzzersIntegrationTest(unittest.TestCase):
def setUp(self):
self.tmp_dir_obj = tempfile.TemporaryDirectory()
self.workspace = self.tmp_dir_obj.name
- self.out_dir = os.path.join(self.workspace, 'out')
+ self.out_dir = os.path.join(self.workspace, 'build-out')
test_helpers.patch_environ(self)
def tearDown(self):
@@ -268,29 +268,32 @@ class CheckFuzzerBuildTest(unittest.TestCase):
def setUp(self):
self.tmp_dir_obj = tempfile.TemporaryDirectory()
- self.test_files_path = os.path.join(self.tmp_dir_obj.name, 'test_files')
- shutil.copytree(TEST_DATA_PATH, self.test_files_path)
+ workspace_path = os.path.join(self.tmp_dir_obj.name, 'workspace')
+ self.workspace = test_helpers.create_workspace(workspace_path)
+ shutil.copytree(TEST_DATA_PATH, workspace_path)
def tearDown(self):
self.tmp_dir_obj.cleanup()
def test_correct_fuzzer_build(self):
"""Checks check_fuzzer_build function returns True for valid fuzzers."""
- test_fuzzer_dir = os.path.join(self.test_files_path, 'out')
self.assertTrue(
- build_fuzzers.check_fuzzer_build(test_fuzzer_dir, self.SANITIZER,
+ build_fuzzers.check_fuzzer_build(self.workspace, self.SANITIZER,
self.LANGUAGE))
- def test_not_a_valid_fuzz_path(self):
- """Tests that False is returned when a bad path is given."""
+ def test_not_a_valid_path(self):
+ """Tests that False is returned when a nonexistent path is given."""
+ workspace = test_helpers.create_workspace('not/a/valid/path')
self.assertFalse(
- build_fuzzers.check_fuzzer_build('not/a/valid/path', self.SANITIZER,
+ build_fuzzers.check_fuzzer_build(workspace, self.SANITIZER,
self.LANGUAGE))
- def test_not_a_valid_fuzzer(self):
- """Checks a directory that exists but does not have fuzzers is False."""
+ def test_no_valid_fuzzers(self):
+ """Tests that False is returned when an empty directory is given."""
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ workspace = test_helpers.create_workspace(tmp_dir)
self.assertFalse(
- build_fuzzers.check_fuzzer_build(self.test_files_path, self.SANITIZER,
+ build_fuzzers.check_fuzzer_build(workspace, self.SANITIZER,
self.LANGUAGE))
@mock.patch('helper.docker_run')
@@ -298,8 +301,7 @@ class CheckFuzzerBuildTest(unittest.TestCase):
"""Tests that ALLOWED_BROKEN_TARGETS_PERCENTAGE is set when running
docker if passed to check_fuzzer_build."""
mocked_docker_run.return_value = 0
- test_fuzzer_dir = os.path.join(TEST_DATA_PATH, 'out')
- build_fuzzers.check_fuzzer_build(test_fuzzer_dir,
+ build_fuzzers.check_fuzzer_build(self.workspace,
self.SANITIZER,
self.LANGUAGE,
allowed_broken_targets_percentage='0')
diff --git a/infra/cifuzz/clusterfuzz_deployment.py b/infra/cifuzz/clusterfuzz_deployment.py
index eb9f835af..798913bdb 100644
--- a/infra/cifuzz/clusterfuzz_deployment.py
+++ b/infra/cifuzz/clusterfuzz_deployment.py
@@ -30,13 +30,11 @@ import utils
class BaseClusterFuzzDeployment:
"""Base class for ClusterFuzz deployments."""
- CORPUS_DIR_NAME = 'cifuzz-corpus'
- BUILD_DIR_NAME = 'cifuzz-latest-build'
-
- def __init__(self, config):
+ def __init__(self, config, workspace):
self.config = config
+ self.workspace = workspace
- def download_latest_build(self, parent_dir):
+ def download_latest_build(self):
"""Downloads the latest build from ClusterFuzz.
Returns:
@@ -44,47 +42,37 @@ class BaseClusterFuzzDeployment:
"""
raise NotImplementedError('Child class must implement method.')
- def upload_latest_build(self, build_dir):
+ def upload_latest_build(self):
"""Uploads the latest build to the filestore.
Returns:
True on success.
"""
raise NotImplementedError('Child class must implement method.')
- def download_corpus(self, target_name, parent_dir):
- """Downloads the corpus for |target_name| from ClusterFuzz to a subdirectory
- of |parent_dir|.
+ def download_corpus(self, target_name):
+ """Downloads the corpus for |target_name| from ClusterFuzz.
Returns:
A path to where the OSS-Fuzz build was stored, or None if it wasn't.
"""
raise NotImplementedError('Child class must implement method.')
- def upload_crashes(self, crashes_dir):
+ def upload_crashes(self):
"""Uploads crashes in |crashes_dir| to filestore."""
raise NotImplementedError('Child class must implement method.')
- def get_target_corpus_dir(self, target_name, parent_dir):
- """Returns the path to the corpus dir for |target_name| within
- |parent_dir|."""
- return os.path.join(self.get_corpus_dir(parent_dir), target_name)
-
- def get_corpus_dir(self, parent_dir):
- """Returns the path to the corpus dir within |parent_dir|."""
- return os.path.join(parent_dir, self.CORPUS_DIR_NAME)
-
- def get_build_dir(self, parent_dir):
- """Returns the path to the build dir for within |parent_dir|."""
- return os.path.join(parent_dir, self.BUILD_DIR_NAME)
+ def get_target_corpus_dir(self, target_name):
+ """Returns the path to the corpus dir for |target_name|."""
+ return os.path.join(self.workspace.corpora, target_name)
- def upload_corpus(self, target_name, corpus_dir): # pylint: disable=no-self-use,unused-argument
- """Uploads the corpus for |target_name| in |corpus_dir| to filestore."""
+ def upload_corpus(self, target_name): # pylint: disable=no-self-use,unused-argument
+ """Uploads the corpus for |target_name| to filestore."""
raise NotImplementedError('Child class must implement method.')
- def make_empty_corpus_dir(self, target_name, parent_dir):
+ def make_empty_corpus_dir(self, target_name):
"""Makes an empty corpus directory for |target_name| in |parent_dir| and
returns the path to the directory."""
- corpus_dir = self.get_target_corpus_dir(target_name, parent_dir)
+ corpus_dir = self.get_target_corpus_dir(target_name)
os.makedirs(corpus_dir, exist_ok=True)
return corpus_dir
@@ -94,35 +82,35 @@ class ClusterFuzzLite(BaseClusterFuzzDeployment):
BASE_BUILD_NAME = 'cifuzz-build-'
- def __init__(self, config):
- super().__init__(config)
+ def __init__(self, config, workspace):
+ super().__init__(config, workspace)
self.filestore = filestore_utils.get_filestore(self.config)
- def download_latest_build(self, parent_dir):
- build_dir = self.get_build_dir(parent_dir)
- if os.path.exists(build_dir):
+ def download_latest_build(self):
+ if os.path.exists(self.workspace.clusterfuzz_build):
# This path is necessary because download_latest_build can be called
# multiple times.That is the case because it is called only when we need
# to see if a bug is novel, i.e. until we want to check a bug is novel we
# don't want to waste time calling this, but therefore this method can be
# called if multiple bugs are found.
- return build_dir
+ return self.workspace.clusterfuzz_build
- os.makedirs(build_dir, exist_ok=True)
+ os.makedirs(self.workspace.clusterfuzz_build, exist_ok=True)
build_name = self._get_build_name()
try:
- if self.filestore.download_latest_build(build_name, build_dir):
- return build_dir
+ if self.filestore.download_latest_build(build_name,
+ self.workspace.clusterfuzz_build):
+ return self.workspace.clusterfuzz_build
except Exception as err: # pylint: disable=broad-except
logging.error('Could not download latest build because of: %s.', err)
return None
- def download_corpus(self, target_name, parent_dir):
- corpus_dir = self.make_empty_corpus_dir(target_name, parent_dir)
+ def download_corpus(self, target_name):
+ corpus_dir = self.make_empty_corpus_dir(target_name)
logging.debug('ClusterFuzzLite: downloading corpus for %s to %s.',
- target_name, parent_dir)
+ target_name, corpus_dir)
corpus_name = self._get_corpus_name(target_name)
try:
self.filestore.download_corpus(corpus_name, corpus_dir)
@@ -142,8 +130,9 @@ class ClusterFuzzLite(BaseClusterFuzzDeployment):
"""Returns the name of the crashes artifact."""
return 'crashes'
- def upload_corpus(self, target_name, corpus_dir):
+ def upload_corpus(self, target_name):
"""Upload the corpus produced by |target_name| in |corpus_dir|."""
+ corpus_dir = self.get_target_corpus_dir(target_name)
logging.info('Uploading corpus in %s for %s.', corpus_dir, target_name)
name = self._get_corpus_name(target_name)
try:
@@ -152,25 +141,28 @@ class ClusterFuzzLite(BaseClusterFuzzDeployment):
logging.error('Failed to upload corpus for target: %s. Error: %s.',
target_name, error)
- def upload_latest_build(self, build_dir):
- logging.info('Uploading latest build in %s.', build_dir)
+ def upload_latest_build(self):
+ logging.info('Uploading latest build in %s.',
+ self.workspace.clusterfuzz_build)
build_name = self._get_build_name()
try:
- return self.filestore.upload_directory(build_name, build_dir)
+ return self.filestore.upload_directory(build_name,
+ self.workspace.clusterfuzz_build)
except Exception as error: # pylint: disable=broad-except
- logging.error('Failed to upload latest build: %s. Error: %s.', build_dir,
- error)
+ logging.error('Failed to upload latest build: %s. Error: %s.',
+ self.workspace.clusterfuzz_build, error)
- def upload_crashes(self, crashes_dir):
- if not os.listdir(crashes_dir):
- logging.info('No crashes in %s. Not uploading.', crashes_dir)
+ def upload_crashes(self):
+ if not os.listdir(self.workspace.artifacts):
+ logging.info('No crashes in %s. Not uploading.', self.workspace.artifacts)
return
crashes_artifact_name = self._get_crashes_artifact_name()
- logging.info('Uploading crashes in %s', crashes_dir)
+ logging.info('Uploading crashes in %s', self.workspace.artifacts)
try:
- self.filestore.upload_directory(crashes_artifact_name, crashes_dir)
+ self.filestore.upload_directory(crashes_artifact_name,
+ self.workspace.artifacts)
except Exception as error: # pylint: disable=broad-except
logging.error('Failed to upload crashes. Error: %s.', error)
@@ -202,19 +194,18 @@ class OSSFuzz(BaseClusterFuzzDeployment):
return None
return response.read().decode()
- def download_latest_build(self, parent_dir):
+ def download_latest_build(self):
"""Downloads the latest OSS-Fuzz build from GCS.
Returns:
A path to where the OSS-Fuzz build was stored, or None if it wasn't.
"""
- build_dir = self.get_build_dir(parent_dir)
- if os.path.exists(build_dir):
+ if os.path.exists(self.workspace.clusterfuzz_build):
# This function can be called multiple times, don't download the build
# again.
- return build_dir
+ return self.workspace.clusterfuzz_build
- os.makedirs(build_dir, exist_ok=True)
+ os.makedirs(self.workspace.clusterfuzz_build, exist_ok=True)
latest_build_name = self.get_latest_build_name()
if not latest_build_name:
@@ -224,30 +215,31 @@ class OSSFuzz(BaseClusterFuzzDeployment):
self.CLUSTERFUZZ_BUILDS,
self.config.project_name,
latest_build_name)
- if http_utils.download_and_unpack_zip(oss_fuzz_build_url, build_dir):
- return build_dir
+ if http_utils.download_and_unpack_zip(oss_fuzz_build_url,
+ self.workspace.clusterfuzz_build):
+ return self.workspace.clusterfuzz_build
return None
- def upload_latest_build(self, build_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_latest_build(self): # pylint: disable=no-self-use
"""Noop Impelementation of upload_latest_build."""
logging.info('Not uploading latest build because on OSS-Fuzz.')
- def upload_corpus(self, target_name, corpus_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_corpus(self, target_name): # pylint: disable=no-self-use,unused-argument
"""Noop Impelementation of upload_corpus."""
logging.info('Not uploading corpus because on OSS-Fuzz.')
- def upload_crashes(self, crashes_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_crashes(self): # pylint: disable=no-self-use
"""Noop Impelementation of upload_crashes."""
logging.info('Not uploading crashes because on OSS-Fuzz.')
- def download_corpus(self, target_name, parent_dir):
+ def download_corpus(self, target_name):
"""Downloads the latest OSS-Fuzz corpus for the target.
Returns:
The local path to to corpus or None if download failed.
"""
- corpus_dir = self.make_empty_corpus_dir(target_name, parent_dir)
+ corpus_dir = self.make_empty_corpus_dir(target_name)
project_qualified_fuzz_target_name = target_name
qualified_name_prefix = self.config.project_name + '_'
if not target_name.startswith(qualified_name_prefix):
@@ -267,38 +259,38 @@ class NoClusterFuzzDeployment(BaseClusterFuzzDeployment):
"""ClusterFuzzDeployment implementation used when there is no deployment of
ClusterFuzz to use."""
- def upload_latest_build(self, build_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_latest_build(self): # pylint: disable=no-self-use
"""Noop Impelementation of upload_latest_build."""
logging.info('Not uploading latest build because no ClusterFuzz '
'deployment.')
- def upload_corpus(self, target_name, corpus_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_corpus(self, target_name): # pylint: disable=no-self-use,unused-argument
"""Noop Impelementation of upload_corpus."""
logging.info('Not uploading corpus because no ClusterFuzz deployment.')
- def upload_crashes(self, crashes_dir): # pylint: disable=no-self-use,unused-argument
+ def upload_crashes(self): # pylint: disable=no-self-use
"""Noop Impelementation of upload_crashes."""
logging.info('Not uploading crashes because no ClusterFuzz deployment.')
- def download_corpus(self, target_name, parent_dir): # pylint: disable=no-self-use,unused-argument
+ def download_corpus(self, target_name): # pylint: disable=no-self-use,unused-argument
"""Noop Impelementation of download_corpus."""
logging.info('Not downloading corpus because no ClusterFuzz deployment.')
- return self.make_empty_corpus_dir(target_name, parent_dir)
+ return self.make_empty_corpus_dir(target_name)
- def download_latest_build(self, parent_dir): # pylint: disable=no-self-use,unused-argument
+ def download_latest_build(self): # pylint: disable=no-self-use
"""Noop Impelementation of download_latest_build."""
logging.info(
'Not downloading latest build because no ClusterFuzz deployment.')
-def get_clusterfuzz_deployment(config):
+def get_clusterfuzz_deployment(config, workspace):
"""Returns object reprsenting deployment of ClusterFuzz used by |config|."""
if (config.platform == config.Platform.INTERNAL_GENERIC_CI or
config.platform == config.Platform.INTERNAL_GITHUB):
logging.info('Using OSS-Fuzz as ClusterFuzz deployment.')
- return OSSFuzz(config)
+ return OSSFuzz(config, workspace)
if config.platform == config.Platform.EXTERNAL_GENERIC_CI:
logging.info('Not using a ClusterFuzz deployment.')
- return NoClusterFuzzDeployment(config)
+ return NoClusterFuzzDeployment(config, workspace)
logging.info('Using ClusterFuzzLite as ClusterFuzz deployment.')
- return ClusterFuzzLite(config)
+ return ClusterFuzzLite(config, workspace)
diff --git a/infra/cifuzz/clusterfuzz_deployment_test.py b/infra/cifuzz/clusterfuzz_deployment_test.py
index 3c7e27598..c03cd1e51 100644
--- a/infra/cifuzz/clusterfuzz_deployment_test.py
+++ b/infra/cifuzz/clusterfuzz_deployment_test.py
@@ -22,6 +22,7 @@ from pyfakefs import fake_filesystem_unittest
import clusterfuzz_deployment
import config_utils
+import docker
import test_helpers
# NOTE: This integration test relies on
@@ -31,15 +32,19 @@ EXAMPLE_PROJECT = 'example'
# An example fuzzer that triggers an error.
EXAMPLE_FUZZER = 'example_crash_fuzzer'
-OUT_DIR = '/out'
-EXPECTED_LATEST_BUILD_PATH = os.path.join(OUT_DIR, 'cifuzz-latest-build')
+WORKSPACE = '/workspace'
+EXPECTED_LATEST_BUILD_PATH = os.path.join(WORKSPACE, 'cifuzz-prev-build')
def _create_config(**kwargs):
"""Creates a config object and then sets every attribute that is a key in
|kwargs| to the corresponding value. Asserts that each key in |kwargs| is an
attribute of Config."""
- defaults = {'is_github': True, 'project_name': EXAMPLE_PROJECT}
+ defaults = {
+ 'is_github': True,
+ 'project_name': EXAMPLE_PROJECT,
+ 'workspace': WORKSPACE,
+ }
for default_key, default_value in defaults.items():
if default_key not in kwargs:
kwargs[default_key] = default_value
@@ -49,7 +54,8 @@ def _create_config(**kwargs):
def _create_deployment(**kwargs):
config = _create_config(**kwargs)
- return clusterfuzz_deployment.get_clusterfuzz_deployment(config)
+ workspace = docker.Workspace(config)
+ return clusterfuzz_deployment.get_clusterfuzz_deployment(config, workspace)
class OSSFuzzTest(fake_filesystem_unittest.TestCase):
@@ -61,10 +67,11 @@ class OSSFuzzTest(fake_filesystem_unittest.TestCase):
@mock.patch('http_utils.download_and_unpack_zip', return_value=True)
def test_download_corpus(self, mocked_download_and_unpack_zip):
- """Tests that download_corpus works for a valid project."""
- result = self.deployment.download_corpus(EXAMPLE_FUZZER, OUT_DIR)
+ """Tests that we can download a corpus for a valid project."""
+ result = self.deployment.download_corpus(EXAMPLE_FUZZER)
self.assertIsNotNone(result)
- expected_corpus_dir = os.path.join(OUT_DIR, 'cifuzz-corpus', EXAMPLE_FUZZER)
+ expected_corpus_dir = os.path.join(self.deployment.workspace.corpora,
+ EXAMPLE_FUZZER)
expected_url = ('https://storage.googleapis.com/example-backup.'
'clusterfuzz-external.appspot.com/corpus/libFuzzer/'
'example_crash_fuzzer/public.zip')
@@ -75,8 +82,9 @@ class OSSFuzzTest(fake_filesystem_unittest.TestCase):
def test_download_corpus_fail(self, _):
"""Tests that when downloading fails, an empty corpus directory is still
returned."""
- corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER, OUT_DIR)
- self.assertEqual(corpus_path, '/out/cifuzz-corpus/example_crash_fuzzer')
+ corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER)
+ self.assertEqual(corpus_path,
+ '/workspace/cifuzz-corpus/example_crash_fuzzer')
self.assertEqual(os.listdir(corpus_path), [])
def test_get_latest_build_name(self):
@@ -86,12 +94,11 @@ class OSSFuzzTest(fake_filesystem_unittest.TestCase):
self.assertTrue('address' in latest_build_name)
@parameterized.parameterized.expand([
- ('upload_latest_build', ('build',),
+ ('upload_latest_build', tuple(),
'Not uploading latest build because on OSS-Fuzz.'),
- ('upload_corpus', ('target', 'corpus_dir'),
+ ('upload_corpus', ('target',),
'Not uploading corpus because on OSS-Fuzz.'),
- ('upload_crashes', ('crashes_dir',),
- 'Not uploading crashes because on OSS-Fuzz.'),
+ ('upload_crashes', tuple(), 'Not uploading crashes because on OSS-Fuzz.'),
])
def test_noop_methods(self, method, method_args, expected_message):
"""Tests that certain methods are noops for OSS-Fuzz."""
@@ -104,7 +111,7 @@ class OSSFuzzTest(fake_filesystem_unittest.TestCase):
def test_download_latest_build(self, mocked_download_and_unpack_zip):
"""Tests that downloading the latest build works as intended under normal
circumstances."""
- self.assertEqual(self.deployment.download_latest_build(OUT_DIR),
+ self.assertEqual(self.deployment.download_latest_build(),
EXPECTED_LATEST_BUILD_PATH)
expected_url = ('https://storage.googleapis.com/clusterfuzz-builds/example/'
'example-address-202008030600.zip')
@@ -115,7 +122,7 @@ class OSSFuzzTest(fake_filesystem_unittest.TestCase):
def test_download_latest_build_fail(self, _):
"""Tests that download_latest_build returns None when it fails to download a
build."""
- self.assertIsNone(self.deployment.download_latest_build(OUT_DIR))
+ self.assertIsNone(self.deployment.download_latest_build())
class ClusterFuzzLiteTest(fake_filesystem_unittest.TestCase):
@@ -130,8 +137,9 @@ class ClusterFuzzLiteTest(fake_filesystem_unittest.TestCase):
return_value=True)
def test_download_corpus(self, mocked_download_corpus):
"""Tests that download_corpus works for a valid project."""
- result = self.deployment.download_corpus(EXAMPLE_FUZZER, OUT_DIR)
- expected_corpus_dir = os.path.join(OUT_DIR, 'cifuzz-corpus', EXAMPLE_FUZZER)
+ result = self.deployment.download_corpus(EXAMPLE_FUZZER)
+ expected_corpus_dir = os.path.join(WORKSPACE, 'cifuzz-corpus',
+ EXAMPLE_FUZZER)
self.assertEqual(result, expected_corpus_dir)
mocked_download_corpus.assert_called_with('corpus-example_crash_fuzzer',
expected_corpus_dir)
@@ -141,8 +149,9 @@ class ClusterFuzzLiteTest(fake_filesystem_unittest.TestCase):
def test_download_corpus_fail(self, _):
"""Tests that when downloading fails, an empty corpus directory is still
returned."""
- corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER, OUT_DIR)
- self.assertEqual(corpus_path, '/out/cifuzz-corpus/example_crash_fuzzer')
+ corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER)
+ self.assertEqual(corpus_path,
+ '/workspace/cifuzz-corpus/example_crash_fuzzer')
self.assertEqual(os.listdir(corpus_path), [])
@mock.patch(
@@ -151,7 +160,7 @@ class ClusterFuzzLiteTest(fake_filesystem_unittest.TestCase):
def test_download_latest_build(self, mocked_download_latest_build):
"""Tests that downloading the latest build works as intended under normal
circumstances."""
- self.assertEqual(self.deployment.download_latest_build(OUT_DIR),
+ self.assertEqual(self.deployment.download_latest_build(),
EXPECTED_LATEST_BUILD_PATH)
expected_artifact_name = 'cifuzz-build-address'
mocked_download_latest_build.assert_called_with(expected_artifact_name,
@@ -163,7 +172,7 @@ class ClusterFuzzLiteTest(fake_filesystem_unittest.TestCase):
def test_download_latest_build_fail(self, _):
"""Tests that download_latest_build returns None when it fails to download a
build."""
- self.assertIsNone(self.deployment.download_latest_build(OUT_DIR))
+ self.assertIsNone(self.deployment.download_latest_build())
class NoClusterFuzzDeploymentTest(fake_filesystem_unittest.TestCase):
@@ -173,26 +182,30 @@ class NoClusterFuzzDeploymentTest(fake_filesystem_unittest.TestCase):
self.setUpPyfakefs()
config = test_helpers.create_run_config(project_name=EXAMPLE_PROJECT,
build_integration_path='/',
+ workspace=WORKSPACE,
is_github=False)
- self.deployment = clusterfuzz_deployment.get_clusterfuzz_deployment(config)
+ workspace = docker.Workspace(config)
+ self.deployment = clusterfuzz_deployment.get_clusterfuzz_deployment(
+ config, workspace)
@mock.patch('logging.info')
def test_download_corpus(self, mocked_info):
"""Tests that download corpus returns the path to the empty corpus
directory."""
- corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER, OUT_DIR)
- self.assertEqual(corpus_path, '/out/cifuzz-corpus/example_crash_fuzzer')
+ corpus_path = self.deployment.download_corpus(EXAMPLE_FUZZER)
+ self.assertEqual(corpus_path,
+ '/workspace/cifuzz-corpus/example_crash_fuzzer')
mocked_info.assert_called_with(
'Not downloading corpus because no ClusterFuzz deployment.')
@parameterized.parameterized.expand([
- ('upload_latest_build', ('build',),
+ ('upload_latest_build', tuple(),
'Not uploading latest build because no ClusterFuzz deployment.'),
- ('upload_corpus', ('target', 'corpus_dir'),
+ ('upload_corpus', ('target',),
'Not uploading corpus because no ClusterFuzz deployment.'),
- ('upload_crashes', ('crashes_dir',),
+ ('upload_crashes', tuple(),
'Not uploading crashes because no ClusterFuzz deployment.'),
- ('download_latest_build', ('parent_dir',),
+ ('download_latest_build', tuple(),
'Not downloading latest build because no ClusterFuzz deployment.')
])
def test_noop_methods(self, method, method_args, expected_message):
@@ -222,10 +235,12 @@ class GetClusterFuzzDeploymentTest(unittest.TestCase):
return_value=platform,
new_callable=mock.PropertyMock):
with mock.patch('filestore_utils.get_filestore', return_value=None):
- config = test_helpers.create_run_config()
+ config = _create_config()
+ workspace = docker.Workspace(config)
+
self.assertIsInstance(
- clusterfuzz_deployment.get_clusterfuzz_deployment(config),
- expected_deployment_cls)
+ clusterfuzz_deployment.get_clusterfuzz_deployment(
+ config, workspace), expected_deployment_cls)
if __name__ == '__main__':
diff --git a/infra/cifuzz/docker.py b/infra/cifuzz/docker.py
index 233e67dfb..c14ecbcc6 100644
--- a/infra/cifuzz/docker.py
+++ b/infra/cifuzz/docker.py
@@ -53,36 +53,77 @@ def delete_images(images):
utils.execute(['docker', 'builder', 'prune', '-f'])
-def get_base_docker_run_args(out_dir, sanitizer='address', language='c++'):
+def get_base_docker_run_args(workspace, sanitizer='address', language='c++'):
"""Returns arguments that should be passed to every invocation of 'docker
run'."""
docker_args = _DEFAULT_DOCKER_RUN_ARGS.copy()
docker_args += [
- '-e',
- f'SANITIZER={sanitizer}',
- '-e',
- f'FUZZING_LANGUAGE={language}',
+ '-e', f'SANITIZER={sanitizer}', '-e', f'FUZZING_LANGUAGE={language}',
+ '-e', 'OUT=' + workspace.out
]
docker_container = utils.get_container_name()
if docker_container:
- docker_args += ['--volumes-from', docker_container, '-e', 'OUT=' + out_dir]
+ # Don't map specific volumes if in a docker container, it breaks when
+ # running a sibling container.
+ docker_args += ['--volumes-from', docker_container]
else:
- docker_args += get_args_mapping_host_path_to_container(out_dir, '/out')
+ docker_args += _get_args_mapping_host_path_to_container(workspace.workspace)
return docker_args, docker_container
-def get_base_docker_run_command(out_dir, sanitizer='address', language='c++'):
+def get_base_docker_run_command(workspace, sanitizer='address', language='c++'):
"""Returns part of the command that should be used everytime 'docker run' is
invoked."""
docker_args, docker_container = get_base_docker_run_args(
- out_dir, sanitizer, language)
+ workspace, sanitizer, language)
command = _DEFAULT_DOCKER_RUN_COMMAND.copy() + docker_args
return command, docker_container
-def get_args_mapping_host_path_to_container(host_path, container_path=None):
+def _get_args_mapping_host_path_to_container(host_path, container_path=None):
"""Get arguments to docker run that will map |host_path| a path on the host to
a path in the container. If |container_path| is specified, that path is mapped
to. If not, then |host_path| is mapped to itself in the container."""
+ # WARNING: Do not use this function when running in production (and
+ # --volumes-from) is used for mapping volumes. It will break production.
container_path = host_path if container_path is None else container_path
return ['-v', f'{host_path}:{container_path}']
+
+
+class Workspace:
+ """Class representing the workspace directory."""
+
+ def __init__(self, config):
+ self.workspace = config.workspace
+
+ def initialize_dir(self, directory): # pylint: disable=no-self-use
+ """Creates directory if it doesn't already exist, otherwise does nothing."""
+ os.makedirs(directory, exist_ok=True)
+
+ @property
+ def out(self):
+ """The out directory used for storing the fuzzer build built by
+ build_fuzzers."""
+ # Don't use 'out' because it needs to be used by artifacts.
+ return os.path.join(self.workspace, 'build-out')
+
+ @property
+ def work(self):
+ """The directory used as the work directory for the fuzzer build/run."""
+ return os.path.join(self.workspace, 'work')
+
+ @property
+ def artifacts(self):
+ """The directory used to store artifacts for download by CI-system users."""
+ # This is hardcoded by a lot of clients, so we need to use this.
+ return os.path.join(self.workspace, 'out', 'artifacts')
+
+ @property
+ def clusterfuzz_build(self):
+ """The directory where builds from ClusterFuzz are stored."""
+ return os.path.join(self.workspace, 'cifuzz-prev-build')
+
+ @property
+ def corpora(self):
+ """The directory where corpora from ClusterFuzz are stored."""
+ return os.path.join(self.workspace, 'cifuzz-corpus')
diff --git a/infra/cifuzz/docker_test.py b/infra/cifuzz/docker_test.py
index 74fcd47ab..7146c8c8c 100644
--- a/infra/cifuzz/docker_test.py
+++ b/infra/cifuzz/docker_test.py
@@ -15,10 +15,13 @@
import unittest
from unittest import mock
+import config_utils
import docker
CONTAINER_NAME = 'example-container'
-OUT_DIR = '/example-out'
+config = config_utils.RunFuzzersConfig()
+config.workspace = '/workspace'
+WORKSPACE = docker.Workspace(config)
SANITIZER = 'example-sanitizer'
LANGUAGE = 'example-language'
@@ -57,14 +60,26 @@ class GetBaseDockerRunArgsTest(unittest.TestCase):
"""Tests that get_base_docker_run_args works as intended when inside a
container."""
docker_args, docker_container = docker.get_base_docker_run_args(
- OUT_DIR, SANITIZER, LANGUAGE)
+ WORKSPACE, SANITIZER, LANGUAGE)
self.assertEqual(docker_container, CONTAINER_NAME)
expected_docker_args = []
expected_docker_args = [
- '--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
- 'ARCHITECTURE=x86_64', '-e', 'CIFUZZ=True', '-e',
- f'SANITIZER={SANITIZER}', '-e', f'FUZZING_LANGUAGE={LANGUAGE}',
- '--volumes-from', CONTAINER_NAME, '-e', f'OUT={OUT_DIR}'
+ '--cap-add',
+ 'SYS_PTRACE',
+ '-e',
+ 'FUZZING_ENGINE=libfuzzer',
+ '-e',
+ 'ARCHITECTURE=x86_64',
+ '-e',
+ 'CIFUZZ=True',
+ '-e',
+ f'SANITIZER={SANITIZER}',
+ '-e',
+ f'FUZZING_LANGUAGE={LANGUAGE}',
+ '-e',
+ f'OUT={WORKSPACE.out}',
+ '--volumes-from',
+ CONTAINER_NAME,
]
self.assertEqual(docker_args, expected_docker_args)
@@ -73,13 +88,14 @@ class GetBaseDockerRunArgsTest(unittest.TestCase):
"""Tests that get_base_docker_run_args works as intended when not inside a
container."""
docker_args, docker_container = docker.get_base_docker_run_args(
- OUT_DIR, SANITIZER, LANGUAGE)
+ WORKSPACE, SANITIZER, LANGUAGE)
self.assertEqual(docker_container, None)
expected_docker_args = [
'--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
'ARCHITECTURE=x86_64', '-e', 'CIFUZZ=True', '-e',
- f'SANITIZER={SANITIZER}', '-e', f'FUZZING_LANGUAGE={LANGUAGE}', '-v',
- f'{OUT_DIR}:/out'
+ f'SANITIZER={SANITIZER}', '-e', f'FUZZING_LANGUAGE={LANGUAGE}', '-e',
+ f'OUT={WORKSPACE.out}', '-v',
+ f'{WORKSPACE.workspace}:{WORKSPACE.workspace}'
]
self.assertEqual(docker_args, expected_docker_args)
@@ -92,12 +108,13 @@ class GetBaseDockerRunCommandTest(unittest.TestCase):
"""Tests that get_base_docker_run_args works as intended when not inside a
container."""
docker_args, docker_container = docker.get_base_docker_run_command(
- OUT_DIR, SANITIZER, LANGUAGE)
+ WORKSPACE, SANITIZER, LANGUAGE)
self.assertEqual(docker_container, None)
expected_docker_command = [
'docker', 'run', '--rm', '--privileged', '--cap-add', 'SYS_PTRACE',
'-e', 'FUZZING_ENGINE=libfuzzer', '-e', 'ARCHITECTURE=x86_64', '-e',
'CIFUZZ=True', '-e', f'SANITIZER={SANITIZER}', '-e',
- f'FUZZING_LANGUAGE={LANGUAGE}', '-v', f'{OUT_DIR}:/out'
+ f'FUZZING_LANGUAGE={LANGUAGE}', '-e', f'OUT={WORKSPACE.out}', '-v',
+ f'{WORKSPACE.workspace}:{WORKSPACE.workspace}'
]
self.assertEqual(docker_args, expected_docker_command)
diff --git a/infra/cifuzz/fuzz_target.py b/infra/cifuzz/fuzz_target.py
index c8d1c6861..178868feb 100644
--- a/infra/cifuzz/fuzz_target.py
+++ b/infra/cifuzz/fuzz_target.py
@@ -33,7 +33,7 @@ logging.basicConfig(
# Use a fixed seed for determinism. Use len_control=0 since we don't have enough
# time fuzzing for len_control to make sense (probably).
-LIBFUZZER_OPTIONS = '-seed=1337 -len_control=0'
+LIBFUZZER_OPTIONS = ['-seed=1337', '-len_control=0']
# The number of reproduce attempts for a crash.
REPRODUCE_ATTEMPTS = 10
@@ -54,25 +54,25 @@ class ReproduceError(Exception):
"""Error for when we can't attempt to reproduce a crash."""
-class FuzzTarget:
+class FuzzTarget: # pylint: disable=too-many-instance-attributes
"""A class to manage a single fuzz target.
Attributes:
target_name: The name of the fuzz target.
duration: The length of time in seconds that the target should run.
target_path: The location of the fuzz target binary.
- out_dir: The location of where output artifacts are stored.
+ workspace: The workspace for storing things related to fuzzing.
"""
# pylint: disable=too-many-arguments
- def __init__(self, target_path, duration, out_dir, clusterfuzz_deployment,
+ def __init__(self, target_path, duration, workspace, clusterfuzz_deployment,
config):
"""Represents a single fuzz target.
Args:
target_path: The location of the fuzz target binary.
duration: The length of time in seconds the target should run.
- out_dir: The location of where the output from crashes should be stored.
+ workspace: The path used for storing things needed for fuzzing.
clusterfuzz_deployment: The object representing the ClusterFuzz
deployment.
config: The config of this project.
@@ -80,7 +80,7 @@ class FuzzTarget:
self.target_path = target_path
self.target_name = os.path.basename(self.target_path)
self.duration = int(duration)
- self.out_dir = out_dir
+ self.workspace = workspace
self.clusterfuzz_deployment = clusterfuzz_deployment
self.config = config
self.latest_corpus_path = None
@@ -92,17 +92,13 @@ class FuzzTarget:
FuzzResult namedtuple with stacktrace and testcase if applicable.
"""
logging.info('Running fuzzer: %s.', self.target_name)
- command, _ = docker.get_base_docker_run_command(self.out_dir,
+ command, _ = docker.get_base_docker_run_command(self.workspace,
self.config.sanitizer,
self.config.language)
- # TODO(metzman): Stop using /out for artifacts and corpus. Use another
- # directory.
# If corpus can be downloaded use it for fuzzing.
self.latest_corpus_path = self.clusterfuzz_deployment.download_corpus(
- self.target_name, self.out_dir)
- command += docker.get_args_mapping_host_path_to_container(
- self.latest_corpus_path)
+ self.target_name)
command += ['-e', 'CORPUS_DIR=' + self.latest_corpus_path]
command += [
@@ -110,9 +106,13 @@ class FuzzTarget:
'-c'
]
- options = LIBFUZZER_OPTIONS + ' -max_total_time=' + str(self.duration)
+ options = LIBFUZZER_OPTIONS.copy() + [
+ f'-max_total_time={self.duration}',
+ # Make sure libFuzzer artifact files don't pollute $OUT.
+ f'-artifact_prefix={self.workspace.artifacts}/'
+ ]
+ options = ' '.join(options)
run_fuzzer_command = f'run_fuzzer {self.target_name} {options}'
-
command.append(run_fuzzer_command)
logging.info('Running command: %s', ' '.join(command))
@@ -134,7 +134,7 @@ class FuzzTarget:
# Crash was discovered.
logging.info('Fuzzer %s, ended before timeout.', self.target_name)
- testcase = self.get_testcase(stderr)
+ testcase = get_testcase(stderr)
if not testcase:
logging.error(b'No testcase found in stacktrace: %s.', stderr)
return FuzzResult(None, None, self.latest_corpus_path)
@@ -186,14 +186,13 @@ class FuzzTarget:
Raises:
ReproduceError if we can't attempt to reproduce the crash.
"""
-
if not os.path.exists(target_path):
raise ReproduceError(f'Target {target_path} not found.')
os.chmod(target_path, stat.S_IRWXO)
command, container = docker.get_base_docker_run_command(
- self.out_dir, self.config.sanitizer, self.config.language)
+ self.workspace, self.config.sanitizer, self.config.language)
if container:
command += ['-e', f'TESTCASE={testcase}']
else:
@@ -256,8 +255,7 @@ class FuzzTarget:
can't be reproduced on an older ClusterFuzz build of the target."""
if not os.path.exists(testcase):
raise ReproduceError('Testcase %s not found.' % testcase)
- clusterfuzz_build_dir = self.clusterfuzz_deployment.download_latest_build(
- self.out_dir)
+ clusterfuzz_build_dir = self.clusterfuzz_deployment.download_latest_build()
if not clusterfuzz_build_dir:
# Crash is reproducible on PR build and we can't test on a recent
# ClusterFuzz/OSS-Fuzz build.
@@ -284,18 +282,17 @@ class FuzzTarget:
'Code change (pr/commit) introduced crash.')
return True
- def get_testcase(self, error_bytes):
- """Gets the file from a fuzzer run stacktrace.
- Args:
- error_bytes: The bytes containing the output from the fuzzer.
+def get_testcase(stderr_bytes):
+ """Gets the file from a fuzzer run stacktrace.
- Returns:
- The path to the testcase or None if not found.
- """
- # TODO(metzman): Stop parsing and use libFuzzers' artifact_prefix option
- # instead.
- match = re.search(rb'\bTest unit written to \.\/([^\s]+)', error_bytes)
- if match:
- return os.path.join(self.out_dir, match.group(1).decode('utf-8'))
- return None
+ Args:
+ stderr_bytes: The bytes containing the output from the fuzzer.
+
+ Returns:
+ The path to the testcase or None if not found.
+ """
+ match = re.search(rb'\bTest unit written to (.+)', stderr_bytes)
+ if match:
+ return match.group(1).decode('utf-8')
+ return None
diff --git a/infra/cifuzz/fuzz_target_test.py b/infra/cifuzz/fuzz_target_test.py
index b8a9d142a..c760f9fc1 100644
--- a/infra/cifuzz/fuzz_target_test.py
+++ b/infra/cifuzz/fuzz_target_test.py
@@ -23,6 +23,7 @@ import parameterized
from pyfakefs import fake_filesystem_unittest
import clusterfuzz_deployment
+import docker
import fuzz_target
import test_helpers
@@ -44,7 +45,11 @@ def _create_config(**kwargs):
"""Creates a config object and then sets every attribute that is a key in
|kwargs| to the corresponding value. Asserts that each key in |kwargs| is an
attribute of Config."""
- defaults = {'is_github': True, 'project_name': EXAMPLE_PROJECT}
+ defaults = {
+ 'is_github': True,
+ 'project_name': EXAMPLE_PROJECT,
+ 'workspace': '/workspace'
+ }
for default_key, default_value in defaults.items():
if default_key not in kwargs:
kwargs[default_key] = default_value
@@ -54,7 +59,8 @@ def _create_config(**kwargs):
def _create_deployment(**kwargs):
config = _create_config(**kwargs)
- return clusterfuzz_deployment.get_clusterfuzz_deployment(config)
+ workspace = docker.Workspace(config)
+ return clusterfuzz_deployment.get_clusterfuzz_deployment(config, workspace)
# TODO(metzman): Use patch from test_libs/helpers.py in clusterfuzz so that we
@@ -66,13 +72,14 @@ class IsReproducibleTest(fake_filesystem_unittest.TestCase):
def setUp(self):
"""Sets up example fuzz target to test is_reproducible method."""
self.fuzz_target_name = 'fuzz-target'
- self.out_dir = '/example/outdir'
- self.fuzz_target_path = os.path.join(self.out_dir, self.fuzz_target_name)
- self.testcase_path = '/testcase'
deployment = _create_deployment()
+ self.workspace = deployment.workspace
+ self.fuzz_target_path = os.path.join(self.workspace.out,
+ self.fuzz_target_name)
+ self.testcase_path = '/testcase'
self.test_target = fuzz_target.FuzzTarget(self.fuzz_target_path,
fuzz_target.REPRODUCE_ATTEMPTS,
- self.out_dir, deployment,
+ self.workspace, deployment,
deployment.config)
def test_reproducible(self, _):
@@ -87,8 +94,8 @@ class IsReproducibleTest(fake_filesystem_unittest.TestCase):
'docker', 'run', '--rm', '--privileged', '--cap-add', 'SYS_PTRACE',
'-e', 'FUZZING_ENGINE=libfuzzer', '-e', 'ARCHITECTURE=x86_64', '-e',
'CIFUZZ=True', '-e', 'SANITIZER=' + self.test_target.config.sanitizer,
- '-e', 'FUZZING_LANGUAGE=' + self.test_target.config.language,
- '--volumes-from', 'container', '-e', 'OUT=' + self.out_dir, '-e',
+ '-e', 'FUZZING_LANGUAGE=' + self.test_target.config.language, '-e',
+ 'OUT=' + self.workspace.out, '--volumes-from', 'container', '-e',
'TESTCASE=' + self.testcase_path, '-t',
'gcr.io/oss-fuzz-base/base-runner', 'reproduce',
self.fuzz_target_name, '-runs=100'
@@ -136,32 +143,24 @@ class IsReproducibleTest(fake_filesystem_unittest.TestCase):
class GetTestCaseTest(unittest.TestCase):
"""Tests get_testcase."""
- def setUp(self):
- """Sets up example fuzz target to test get_testcase method."""
- deployment = _create_deployment()
- self.test_target = fuzz_target.FuzzTarget('/example/path', 10,
- '/example/outdir', deployment,
- deployment.config)
-
def test_valid_error_string(self):
"""Tests that get_testcase returns the correct testcase give an error."""
testcase_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'test_data', 'example_crash_fuzzer_output.txt')
with open(testcase_path, 'rb') as test_fuzz_output:
- parsed_testcase = self.test_target.get_testcase(test_fuzz_output.read())
- self.assertEqual(
- parsed_testcase,
- '/example/outdir/crash-ad6700613693ef977ff3a8c8f4dae239c3dde6f5')
+ parsed_testcase = fuzz_target.get_testcase(test_fuzz_output.read())
+ self.assertEqual(parsed_testcase,
+ './crash-ad6700613693ef977ff3a8c8f4dae239c3dde6f5')
def test_invalid_error_string(self):
"""Tests that get_testcase returns None with a bad error string."""
- self.assertIsNone(self.test_target.get_testcase(b''))
- self.assertIsNone(self.test_target.get_testcase(b' Example crash string.'))
+ self.assertIsNone(fuzz_target.get_testcase(b''))
+ self.assertIsNone(fuzz_target.get_testcase(b' Example crash string.'))
def test_encoding(self):
"""Tests that get_testcase accepts bytes and returns a string."""
fuzzer_output = b'\x8fTest unit written to ./crash-1'
- result = self.test_target.get_testcase(fuzzer_output)
+ result = fuzz_target.get_testcase(fuzzer_output)
self.assertTrue(isinstance(result, str))
diff --git a/infra/cifuzz/generate_coverage_report.py b/infra/cifuzz/generate_coverage_report.py
index 1916131bd..9a5b8e7a8 100644
--- a/infra/cifuzz/generate_coverage_report.py
+++ b/infra/cifuzz/generate_coverage_report.py
@@ -18,9 +18,9 @@ import helper
import docker
-def run_coverage_command(out_dir, config):
+def run_coverage_command(workspace, config):
"""Runs the coverage command in base-runner to generate a coverage report."""
- docker_args, _ = docker.get_base_docker_run_args(out_dir, config.sanitizer,
+ docker_args, _ = docker.get_base_docker_run_args(workspace, config.sanitizer,
config.language)
docker_args += [
'-e', 'COVERAGE_EXTRA_ARGS=', '-e', 'HTTP_PORT=', '-t',
@@ -29,18 +29,18 @@ def run_coverage_command(out_dir, config):
return helper.docker_run(docker_args)
-def download_corpora(out_dir, fuzz_target_paths, clusterfuzz_deployment):
- """Downloads corpora to |out_dir| for the fuzz targets in |fuzz_target_paths|
- using clusterfuzz_deployment| to download corpora from ClusterFuzz/OSS-Fuzz"""
+def download_corpora(fuzz_target_paths, clusterfuzz_deployment):
+ """Downloads corpora for fuzz targets in |fuzz_target_paths| using
+ clusterfuzz_deployment| to download corpora from ClusterFuzz/OSS-Fuzz."""
# TODO(metzman): Download to /corpus dir.
for target_path in fuzz_target_paths:
target = os.path.basename(target_path)
- clusterfuzz_deployment.download_corpus(target, out_dir)
+ clusterfuzz_deployment.download_corpus(target)
-def generate_coverage_report(fuzz_target_paths, out_dir, clusterfuzz_deployment,
- config):
+def generate_coverage_report(fuzz_target_paths, workspace,
+ clusterfuzz_deployment, config):
"""Generates a coverage report using Clang's source based coverage."""
- download_corpora(out_dir, fuzz_target_paths, clusterfuzz_deployment)
- run_coverage_command(out_dir, config)
+ download_corpora(fuzz_target_paths, clusterfuzz_deployment)
+ run_coverage_command(workspace, config)
# TODO(metzman): Upload this build to the filestore.
diff --git a/infra/cifuzz/generate_coverage_report_test.py b/infra/cifuzz/generate_coverage_report_test.py
index 461cd7ca3..250d95946 100644
--- a/infra/cifuzz/generate_coverage_report_test.py
+++ b/infra/cifuzz/generate_coverage_report_test.py
@@ -30,17 +30,20 @@ class TestRunCoverageCommand(unittest.TestCase):
@mock.patch('helper.docker_run')
def test_run_coverage_command(self, mocked_docker_run): # pylint: disable=no-self-use
"""Tests that run_coverage_command works as intended."""
+ config = test_helpers.create_run_config(project_name=PROJECT,
+ sanitizer=SANITIZER)
+ workspace = test_helpers.create_workspace()
expected_docker_args = [
'--cap-add', 'SYS_PTRACE', '-e', 'FUZZING_ENGINE=libfuzzer', '-e',
'ARCHITECTURE=x86_64', '-e', 'CIFUZZ=True', '-e',
- f'SANITIZER={SANITIZER}', '-e', 'FUZZING_LANGUAGE=c++', '-v',
- f'{OUT_DIR}:/out', '-e', 'COVERAGE_EXTRA_ARGS=', '-e', 'HTTP_PORT=',
- '-t', 'gcr.io/oss-fuzz-base/base-runner', 'coverage'
+ f'SANITIZER={SANITIZER}', '-e', 'FUZZING_LANGUAGE=c++', '-e',
+ 'OUT=/workspace/build-out', '-v',
+ f'{workspace.workspace}:{workspace.workspace}', '-e',
+ 'COVERAGE_EXTRA_ARGS=', '-e', 'HTTP_PORT=', '-t',
+ 'gcr.io/oss-fuzz-base/base-runner', 'coverage'
]
- config = test_helpers.create_run_config(project_name=PROJECT,
- sanitizer=SANITIZER)
- generate_coverage_report.run_coverage_command(OUT_DIR, config)
+ generate_coverage_report.run_coverage_command(workspace, config)
mocked_docker_run.assert_called_with(expected_docker_args)
@@ -51,10 +54,7 @@ class DownloadCorporaTest(unittest.TestCase):
"""Tests that download_corpora works as intended."""
clusterfuzz_deployment = mock.Mock()
fuzz_target_paths = ['/path/to/fuzzer1', '/path/to/fuzzer2']
- expected_calls = [
- mock.call('fuzzer1', OUT_DIR),
- mock.call('fuzzer2', OUT_DIR)
- ]
- generate_coverage_report.download_corpora(OUT_DIR, fuzz_target_paths,
+ expected_calls = [mock.call('fuzzer1'), mock.call('fuzzer2')]
+ generate_coverage_report.download_corpora(fuzz_target_paths,
clusterfuzz_deployment)
clusterfuzz_deployment.download_corpus.assert_has_calls(expected_calls)
diff --git a/infra/cifuzz/run_fuzzers.py b/infra/cifuzz/run_fuzzers.py
index 6b787fcc7..a50e43a4c 100644
--- a/infra/cifuzz/run_fuzzers.py
+++ b/infra/cifuzz/run_fuzzers.py
@@ -20,6 +20,7 @@ import sys
import time
import clusterfuzz_deployment
+import docker
import fuzz_target
import generate_coverage_report
import stack_parser
@@ -42,16 +43,17 @@ class BaseFuzzTargetRunner:
def __init__(self, config):
self.config = config
+ self.workspace = docker.Workspace(config)
self.clusterfuzz_deployment = (
- clusterfuzz_deployment.get_clusterfuzz_deployment(self.config))
+ clusterfuzz_deployment.get_clusterfuzz_deployment(
+ self.config, self.workspace))
+
# Set by the initialize method.
- self.out_dir = None
self.fuzz_target_paths = None
- self.crashes_dir = None
def get_fuzz_targets(self):
- """Returns fuzz targets in out_dir."""
- return utils.get_fuzz_targets(self.out_dir)
+ """Returns fuzz targets in out directory."""
+ return utils.get_fuzz_targets(self.workspace.out)
def initialize(self):
"""Initialization method. Must be called before calling run_fuzz_targets.
@@ -69,24 +71,23 @@ class BaseFuzzTargetRunner:
self.config.fuzz_seconds)
return False
- self.out_dir = os.path.join(self.config.workspace, 'out')
- if not os.path.exists(self.out_dir):
- logging.error('Out directory: %s does not exist.', self.out_dir)
+ if not os.path.exists(self.workspace.out):
+ logging.error('Out directory: %s does not exist.', self.workspace.out)
return False
- self.crashes_dir = os.path.join(self.out_dir, 'artifacts')
- if not os.path.exists(self.crashes_dir):
- os.mkdir(self.crashes_dir)
- elif (not os.path.isdir(self.crashes_dir) or os.listdir(self.crashes_dir)):
+ if not os.path.exists(self.workspace.artifacts):
+ os.makedirs(self.workspace.artifacts)
+ elif (not os.path.isdir(self.workspace.artifacts) or
+ os.listdir(self.workspace.artifacts)):
logging.error('Artifacts path: %s exists and is not an empty directory.',
- self.crashes_dir)
+ self.workspace.artifacts)
return False
self.fuzz_target_paths = self.get_fuzz_targets()
logging.info('Fuzz targets: %s', self.fuzz_target_paths)
if not self.fuzz_target_paths:
logging.error('No fuzz targets were found in out directory: %s.',
- self.out_dir)
+ self.workspace.out)
return False
return True
@@ -110,11 +111,11 @@ class BaseFuzzTargetRunner:
|fuzz_target|."""
artifact_name = (f'{target.target_name}-{self.config.sanitizer}-'
f'{artifact_name}')
- return os.path.join(self.crashes_dir, artifact_name)
+ return os.path.join(self.workspace.artifacts, artifact_name)
def create_fuzz_target_obj(self, target_path, run_seconds):
"""Returns a fuzz target object."""
- return fuzz_target.FuzzTarget(target_path, run_seconds, self.out_dir,
+ return fuzz_target.FuzzTarget(target_path, run_seconds, self.workspace,
self.clusterfuzz_deployment, self.config)
def run_fuzz_targets(self):
@@ -173,19 +174,19 @@ class CoverageTargetRunner(BaseFuzzTargetRunner):
raise NotImplementedError('Not implemented for CoverageTargetRunner.')
def get_fuzz_targets(self):
- """Returns fuzz targets in out_dir."""
+ """Returns fuzz targets in out directory."""
# We only want fuzz targets from the root because during the coverage build,
# a lot of the image's filesystem is copied into /out for the purpose of
# generating coverage reports.
# TOOD(metzman): Figure out if top_level_only should be the only behavior
# for this function.
- return utils.get_fuzz_targets(self.out_dir, top_level_only=True)
+ return utils.get_fuzz_targets(self.workspace.out, top_level_only=True)
def run_fuzz_targets(self):
"""Generates a coverage report. Always returns False since it never finds
any bugs."""
generate_coverage_report.generate_coverage_report(
- self.fuzz_target_paths, self.out_dir, self.clusterfuzz_deployment,
+ self.fuzz_target_paths, self.workspace, self.clusterfuzz_deployment,
self.config)
return False
@@ -238,38 +239,20 @@ class BatchFuzzTargetRunner(BaseFuzzTargetRunner):
def run_fuzz_targets(self):
result = super().run_fuzz_targets()
- self.clusterfuzz_deployment.upload_crashes(self.crashes_dir)
+ self.clusterfuzz_deployment.upload_crashes()
# We want to upload the build to the filestore after we do batch fuzzing.
- # There are some problems with this. First, we don't want to upload the
- # build before fuzzing, because if we download the latest build, we will
- # consider the build we just uploaded to be the latest even though it
- # shouldn't be (we really intend to download the build before the curent
- # one.
- # Second, the out directory is mounted into the runnner container and is
- # used to pass the runner corpora, old builds and for the runner to pass the
- # host testcases. Thus, we will upload the build after fuzzing. But before
- # we zip the out directory we will remove these extra things that are now in
- # out.
+ # There are some is a problem with this. We don't want to upload the build
+ # before fuzzing, because if we download the latest build, we will consider
+ # the build we just uploaded to be the latest even though it shouldn't be
+ # (we really intend to download the build before the curent one.
# TODO(metzman): We should really be uploading latest build in build_fuzzers
# before we remove unaffected fuzzers. Otherwise, we can lose fuzzers. This
# is probably more of a theoretical concern since in batch fuzzing, there is
# no code change and thus no fuzzers that are removed, but it's inelegant to
# put this here.
- # TODO(metzman): Don't pollute self.out_dir like this.
-
- for directory in [
- self.clusterfuzz_deployment.get_corpus_dir(self.out_dir),
- # This is the directory of the ClusterFuzz build, not the build we just
- # did.
- self.clusterfuzz_deployment.get_build_dir(self.out_dir),
- self.crashes_dir,
- ]:
- if os.path.exists(directory):
- shutil.rmtree(directory)
-
- self.clusterfuzz_deployment.upload_latest_build(self.out_dir)
+ self.clusterfuzz_deployment.upload_latest_build()
return result
diff --git a/infra/cifuzz/run_fuzzers_test.py b/infra/cifuzz/run_fuzzers_test.py
index 83f18d517..9e6ddf8c5 100644
--- a/infra/cifuzz/run_fuzzers_test.py
+++ b/infra/cifuzz/run_fuzzers_test.py
@@ -121,7 +121,7 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
expected_error_args = ('Fuzz_seconds argument must be greater than 1, '
'but was: %s.', fuzz_seconds)
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
+ out_path = os.path.join(tmp_dir, 'build-out')
os.mkdir(out_path)
with mock.patch('utils.get_fuzz_targets') as mocked_get_fuzz_targets:
mocked_get_fuzz_targets.return_value = [
@@ -134,16 +134,17 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
def test_initialize_no_out_dir(self):
"""Tests initialize fails with no out dir."""
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
+ out_path = os.path.join(tmp_dir, 'build-out')
expected_error_args = ('Out directory: %s does not exist.', out_path)
self._test_initialize_fail(expected_error_args, workspace=tmp_dir)
def test_initialize_nonempty_artifacts(self):
"""Tests initialize with a file artifacts path."""
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
+ out_path = os.path.join(tmp_dir, 'build-out')
os.mkdir(out_path)
- artifacts_path = os.path.join(out_path, 'artifacts')
+ os.makedirs(os.path.join(tmp_dir, 'out'))
+ artifacts_path = os.path.join(tmp_dir, 'out', 'artifacts')
with open(artifacts_path, 'w') as artifacts_handle:
artifacts_handle.write('fake')
expected_error_args = (
@@ -154,8 +155,9 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
def test_initialize_bad_artifacts(self):
"""Tests initialize with a non-empty artifacts path."""
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
- artifacts_path = os.path.join(out_path, 'artifacts')
+ out_path = os.path.join(tmp_dir, 'build-out')
+ os.mkdir(out_path)
+ artifacts_path = os.path.join(tmp_dir, 'out', 'artifacts')
os.makedirs(artifacts_path)
artifact_path = os.path.join(artifacts_path, 'artifact')
with open(artifact_path, 'w') as artifact_handle:
@@ -172,8 +174,9 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
"""Tests initialize with an empty artifacts dir."""
mocked_get_fuzz_targets.return_value = ['fuzz-target']
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
- artifacts_path = os.path.join(out_path, 'artifacts')
+ out_path = os.path.join(tmp_dir, 'build-out')
+ os.mkdir(out_path)
+ artifacts_path = os.path.join(tmp_dir, 'out', 'artifacts')
os.makedirs(artifacts_path)
runner = self._create_runner(workspace=tmp_dir)
self.assertTrue(runner.initialize())
@@ -187,17 +190,17 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
"""Tests initialize with no artifacts dir (the expected setting)."""
mocked_get_fuzz_targets.return_value = ['fuzz-target']
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
- os.makedirs(out_path)
+ out_path = os.path.join(tmp_dir, 'build-out')
+ os.mkdir(out_path)
runner = self._create_runner(workspace=tmp_dir)
self.assertTrue(runner.initialize())
mocked_log_error.assert_not_called()
- self.assertTrue(os.path.isdir(os.path.join(out_path, 'artifacts')))
+ self.assertTrue(os.path.isdir(os.path.join(tmp_dir, 'out', 'artifacts')))
def test_initialize_no_fuzz_targets(self):
"""Tests initialize with no fuzz targets."""
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
+ out_path = os.path.join(tmp_dir, 'build-out')
os.makedirs(out_path)
expected_error_args = ('No fuzz targets were found in out directory: %s.',
out_path)
@@ -205,18 +208,21 @@ class BaseFuzzTargetRunnerTest(unittest.TestCase):
def test_get_fuzz_target_artifact(self):
"""Tests that get_fuzz_target_artifact works as intended."""
- runner = self._create_runner()
- crashes_dir = 'crashes-dir'
- runner.crashes_dir = crashes_dir
- artifact_name = 'artifact-name'
- target = mock.MagicMock()
- target_name = 'target_name'
- target.target_name = target_name
- fuzz_target_artifact = runner.get_fuzz_target_artifact(
- target, artifact_name)
- expected_fuzz_target_artifact = (
- 'crashes-dir/target_name-address-artifact-name')
- self.assertEqual(fuzz_target_artifact, expected_fuzz_target_artifact)
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ runner = self._create_runner(workspace=tmp_dir)
+ crashes_dir = 'crashes-dir'
+ runner.crashes_dir = crashes_dir
+ artifact_name = 'artifact-name'
+ target = mock.MagicMock()
+ target_name = 'target_name'
+ target.target_name = target_name
+
+ fuzz_target_artifact = runner.get_fuzz_target_artifact(
+ target, artifact_name)
+ expected_fuzz_target_artifact = os.path.join(
+ tmp_dir, 'out', 'artifacts', 'target_name-address-artifact-name')
+
+ self.assertEqual(fuzz_target_artifact, expected_fuzz_target_artifact)
class CiFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
@@ -233,7 +239,7 @@ class CiFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
mocked_get_fuzz_targets):
"""Tests that run_fuzz_targets quits on the first crash it finds."""
workspace = 'workspace'
- out_path = os.path.join(workspace, 'out')
+ out_path = os.path.join(workspace, 'build-out')
self.fs.create_dir(out_path)
config = test_helpers.create_run_config(fuzz_seconds=FUZZ_SECONDS,
workspace=workspace,
@@ -253,7 +259,8 @@ class CiFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
magic_mock.target_name = 'target1'
mocked_create_fuzz_target_obj.return_value = magic_mock
self.assertTrue(runner.run_fuzz_targets())
- self.assertIn('target1-address-testcase', os.listdir(runner.crashes_dir))
+ self.assertIn('target1-address-testcase',
+ os.listdir(runner.workspace.artifacts))
self.assertEqual(mocked_run_fuzz_target.call_count, 1)
@@ -265,11 +272,11 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
def setUp(self):
self.setUpPyfakefs()
- self.out_dir = os.path.join(self.WORKSPACE, 'out')
- self.fs.create_dir(self.out_dir)
- self.testcase1 = os.path.join(self.WORKSPACE, 'testcase-aaa')
+ out_dir = os.path.join(self.WORKSPACE, 'build-out')
+ self.fs.create_dir(out_dir)
+ self.testcase1 = os.path.join(out_dir, 'testcase-aaa')
self.fs.create_file(self.testcase1)
- self.testcase2 = os.path.join(self.WORKSPACE, 'testcase-bbb')
+ self.testcase2 = os.path.join(out_dir, 'testcase-bbb')
self.fs.create_file(self.testcase2)
self.config = test_helpers.create_run_config(fuzz_seconds=FUZZ_SECONDS,
workspace=self.WORKSPACE,
@@ -317,32 +324,9 @@ class BatchFuzzTargetRunnerTest(fake_filesystem_unittest.TestCase):
self, mocked_upload_crashes, mocked_upload_latest_build, _):
"""Tests that run_fuzz_targets uploads crashes and builds correctly."""
runner = run_fuzzers.BatchFuzzTargetRunner(self.config)
+ # TODO(metzman): Don't rely on this failing gracefully.
runner.initialize()
- expected_crashes_dir = 'workspace/out/artifacts'
-
- def mock_upload_crashes(crashes_dir):
- self.assertEqual(crashes_dir, expected_crashes_dir)
- # Ensure it wasn't deleted first.
- self.assertTrue(os.path.exists(crashes_dir))
-
- mocked_upload_crashes.side_effect = mock_upload_crashes
-
- expected_out_dir = 'workspace/out'
- expected_build_dir = 'workspace/out/cifuzz-latest-build'
- expected_corpus_dir = 'workspace/out/cifuzz-corpus'
- self.fs.create_dir(expected_build_dir)
- self.fs.create_dir(expected_corpus_dir)
-
- def mock_upload_latest_build(out_dir):
- self.assertEqual(out_dir, expected_out_dir)
- # Ensure these were deleted before this function call.
- self.assertFalse(os.path.exists(expected_crashes_dir))
- self.assertFalse(os.path.exists(expected_build_dir))
- self.assertFalse(os.path.exists(expected_corpus_dir))
-
- mocked_upload_latest_build.side_effect = mock_upload_latest_build
-
self.assertFalse(runner.run_fuzz_targets())
self.assertEqual(mocked_upload_crashes.call_count, 1)
self.assertEqual(mocked_upload_latest_build.call_count, 1)
@@ -362,7 +346,6 @@ class CoverageReportIntegrationTest(unittest.TestCase):
generation."""
with tempfile.TemporaryDirectory() as workspace:
- out_dir = os.path.join(workspace, 'out')
try:
# Do coverage build.
build_config = test_helpers.create_build_config(
@@ -388,8 +371,8 @@ class CoverageReportIntegrationTest(unittest.TestCase):
TEST_DATA_PATH, 'example_coverage_report_summary.json')
with open(expected_summary_path) as file_handle:
expected_summary = json.loads(file_handle.read())
- actual_summary_path = os.path.join(out_dir, 'report', 'linux',
- 'summary.json')
+ actual_summary_path = os.path.join(workspace, 'build-out', 'report',
+ 'linux', 'summary.json')
with open(actual_summary_path) as file_handle:
actual_summary = json.loads(file_handle.read())
self.assertEqual(expected_summary, actual_summary)
@@ -427,8 +410,6 @@ class RunAddressFuzzersIntegrationTest(RunFuzzerIntegrationTestMixin,
project_name=EXAMPLE_PROJECT)
result = run_fuzzers.run_fuzzers(config)
self.assertEqual(result, run_fuzzers.RunFuzzersResult.BUG_FOUND)
- build_dir = os.path.join(workspace, 'out', self.BUILD_DIR_NAME)
- self.assertNotEqual(0, len(os.listdir(build_dir)))
@mock.patch('fuzz_target.FuzzTarget.is_reproducible',
side_effect=[True, True])
@@ -438,18 +419,15 @@ class RunAddressFuzzersIntegrationTest(RunFuzzerIntegrationTestMixin,
workspace = os.path.join(tmp_dir, 'workspace')
shutil.copytree(TEST_DATA_PATH, workspace)
config = test_helpers.create_run_config(fuzz_seconds=FUZZ_SECONDS,
- workspace=TEST_DATA_PATH,
+ workspace=workspace,
project_name=EXAMPLE_PROJECT)
result = run_fuzzers.run_fuzzers(config)
self.assertEqual(result, run_fuzzers.RunFuzzersResult.NO_BUG_FOUND)
- build_dir = os.path.join(TEST_DATA_PATH, 'out', self.BUILD_DIR_NAME)
- self.assertTrue(os.path.exists(build_dir))
- self.assertNotEqual(0, len(os.listdir(build_dir)))
def test_invalid_build(self):
"""Tests run_fuzzers with an invalid ASAN build."""
with tempfile.TemporaryDirectory() as tmp_dir:
- out_path = os.path.join(tmp_dir, 'out')
+ out_path = os.path.join(tmp_dir, 'build-out')
os.mkdir(out_path)
config = test_helpers.create_run_config(fuzz_seconds=FUZZ_SECONDS,
workspace=tmp_dir,
diff --git a/infra/cifuzz/test_data/out/example_crash_fuzzer b/infra/cifuzz/test_data/build-out/example_crash_fuzzer
index 704800dda..704800dda 100755
--- a/infra/cifuzz/test_data/out/example_crash_fuzzer
+++ b/infra/cifuzz/test_data/build-out/example_crash_fuzzer
Binary files differ
diff --git a/infra/cifuzz/test_data/out/example_nocrash_fuzzer b/infra/cifuzz/test_data/build-out/example_nocrash_fuzzer
index e4ff86042..e4ff86042 100755
--- a/infra/cifuzz/test_data/out/example_nocrash_fuzzer
+++ b/infra/cifuzz/test_data/build-out/example_nocrash_fuzzer
Binary files differ
diff --git a/infra/cifuzz/test_data/memory/out/curl_fuzzer_memory b/infra/cifuzz/test_data/memory/build-out/curl_fuzzer_memory
index c602ce970..c602ce970 100755
--- a/infra/cifuzz/test_data/memory/out/curl_fuzzer_memory
+++ b/infra/cifuzz/test_data/memory/build-out/curl_fuzzer_memory
Binary files differ
diff --git a/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined b/infra/cifuzz/test_data/undefined/build-out/curl_fuzzer_undefined
index 504cab108..504cab108 100755
--- a/infra/cifuzz/test_data/undefined/out/curl_fuzzer_undefined
+++ b/infra/cifuzz/test_data/undefined/build-out/curl_fuzzer_undefined
Binary files differ
diff --git a/infra/cifuzz/test_helpers.py b/infra/cifuzz/test_helpers.py
index 3d20bf468..c5eea2de9 100644
--- a/infra/cifuzz/test_helpers.py
+++ b/infra/cifuzz/test_helpers.py
@@ -20,6 +20,7 @@ import tempfile
from unittest import mock
import config_utils
+import docker
def _create_config(config_cls, **kwargs):
@@ -48,6 +49,13 @@ def create_run_config(**kwargs):
return _create_config(config_utils.RunFuzzersConfig, **kwargs)
+def create_workspace(workspace_path='/workspace'):
+ """Returns a workspace located at |workspace_path| ('/workspace' by
+ default)."""
+ config = create_run_config(workspace=workspace_path)
+ return docker.Workspace(config)
+
+
def patch_environ(testcase_obj, env=None):
"""Patch environment."""
if env is None:
diff --git a/infra/utils_test.py b/infra/utils_test.py
index aa6ec7ba7..3ce405523 100644
--- a/infra/utils_test.py
+++ b/infra/utils_test.py
@@ -24,7 +24,7 @@ import helper
EXAMPLE_PROJECT = 'example'
TEST_OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- 'cifuzz', 'test_data', 'out')
+ 'cifuzz', 'test_data', 'build-out')
class IsFuzzTargetLocalTest(unittest.TestCase):