aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--infra/base-images/base-builder/detect_repo_test.py17
-rw-r--r--infra/build_fuzzers.Dockerfile1
-rw-r--r--infra/build_specified_commit.py3
-rw-r--r--infra/cifuzz/actions/build_fuzzers/action.yml8
-rw-r--r--infra/cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py96
-rw-r--r--infra/cifuzz/cifuzz-base/Dockerfile2
-rw-r--r--infra/cifuzz/cifuzz.py387
-rw-r--r--infra/cifuzz/cifuzz_test.py29
-rw-r--r--infra/cifuzz/fuzz_target_test.py8
-rw-r--r--infra/cifuzz/test_files/external-project/Makefile44
-rw-r--r--infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp24
-rw-r--r--infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict6
-rw-r--r--infra/cifuzz/test_files/external-project/my_api.cpp36
-rw-r--r--infra/cifuzz/test_files/external-project/my_api.h19
-rw-r--r--infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile22
-rw-r--r--infra/cifuzz/test_files/external-project/oss-fuzz/build.sh24
-rw-r--r--infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp47
-rw-r--r--infra/repo_manager.py59
-rw-r--r--infra/repo_manager_test.py105
-rw-r--r--infra/retry.py3
-rw-r--r--infra/run_fuzzers.Dockerfile1
-rw-r--r--infra/test_helpers.py27
22 files changed, 717 insertions, 251 deletions
diff --git a/infra/base-images/base-builder/detect_repo_test.py b/infra/base-images/base-builder/detect_repo_test.py
index 4886522ac..96f4c9ec3 100644
--- a/infra/base-images/base-builder/detect_repo_test.py
+++ b/infra/base-images/base-builder/detect_repo_test.py
@@ -44,20 +44,19 @@ class DetectRepoIntegrationTest(unittest.TestCase):
with tempfile.TemporaryDirectory() as tmp_dir:
# Construct example repo's to check for commits.
- for example_repo in test_repos.TEST_REPOS:
- repo_manager.RepoManager(example_repo.git_url, tmp_dir)
- self.check_with_repo(example_repo.git_url,
- example_repo.git_repo_name,
+ for test_repo in test_repos.TEST_REPOS:
+ repo_manager.clone_repo_and_get_manager(test_repo.git_url, tmp_dir)
+ self.check_with_repo(test_repo.git_url,
+ test_repo.git_repo_name,
tmp_dir,
- commit=example_repo.old_commit)
+ commit=test_repo.old_commit)
def test_infer_main_repo_from_name(self):
"""Tests that the main project repo can be inferred from a repo name."""
-
with tempfile.TemporaryDirectory() as tmp_dir:
- for example_repo in test_repos.TEST_REPOS:
- repo_manager.RepoManager(example_repo.git_url, tmp_dir)
- self.check_with_repo(example_repo.git_url, example_repo.git_repo_name,
+ for test_repo in test_repos.TEST_REPOS:
+ repo_manager.clone_repo_and_get_manager(test_repo.git_url, tmp_dir)
+ self.check_with_repo(test_repo.git_url, test_repo.git_repo_name,
tmp_dir)
def check_with_repo(self, repo_origin, repo_name, tmp_dir, commit=None):
diff --git a/infra/build_fuzzers.Dockerfile b/infra/build_fuzzers.Dockerfile
index 49e438575..49c0d2c16 100644
--- a/infra/build_fuzzers.Dockerfile
+++ b/infra/build_fuzzers.Dockerfile
@@ -17,7 +17,6 @@
FROM gcr.io/oss-fuzz-base/cifuzz-base
-# Copies your code file from action repository to the container
COPY cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py /opt/build_fuzzers_entrypoint.py
# Python file to execute when the docker container starts up
diff --git a/infra/build_specified_commit.py b/infra/build_specified_commit.py
index 6c3e3cc23..a4ff70f7a 100644
--- a/infra/build_specified_commit.py
+++ b/infra/build_specified_commit.py
@@ -143,8 +143,7 @@ def copy_src_from_docker(project_name, host_dir):
return src_dir
-@retry.wrap(_IMAGE_BUILD_TRIES, 2,
- 'infra.build_specified_commit._build_image_with_retries')
+@retry.wrap(_IMAGE_BUILD_TRIES, 2)
def _build_image_with_retries(project_name):
"""Build image with retries."""
return helper.build_image_impl(project_name)
diff --git a/infra/cifuzz/actions/build_fuzzers/action.yml b/infra/cifuzz/actions/build_fuzzers/action.yml
index 20420eb37..2919db40e 100644
--- a/infra/cifuzz/actions/build_fuzzers/action.yml
+++ b/infra/cifuzz/actions/build_fuzzers/action.yml
@@ -14,6 +14,12 @@ inputs:
sanitizer:
description: 'The sanitizer to build the fuzzers with.'
default: 'address'
+ project-src-path:
+ description: "The path to the project's source code checkout."
+ required: false
+ build-integration-path:
+ description: "The path to the the project's build integration."
+ required: false
runs:
using: 'docker'
image: '../../../build_fuzzers.Dockerfile'
@@ -22,3 +28,5 @@ runs:
DRY_RUN: ${{ inputs.dry-run}}
ALLOWED_BROKEN_TARGETS_PERCENTAGE: ${{ inputs.allowed-broken-targets-percentage}}
SANITIZER: ${{ inputs.sanitizer }}
+ PROJECT_SRC_PATH: ${{ inputs.project-src-path }}
+ BUILD_INTEGRATION_PATH: ${{ inputs.build-integration-path }}
diff --git a/infra/cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py b/infra/cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py
index 689862c04..5d467e7b4 100644
--- a/infra/cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py
+++ b/infra/cifuzz/actions/build_fuzzers/build_fuzzers_entrypoint.py
@@ -27,6 +27,32 @@ logging.basicConfig(
level=logging.DEBUG)
+def get_pr_ref(event_path):
+ """Returns the PR ref from |event_path|."""
+ with open(event_path, encoding='utf-8') as file_handle:
+ event = json.load(file_handle)
+ return 'refs/pull/{0}/merge'.format(event['pull_request']['number'])
+
+
+def get_project_src_path(workspace):
+ """Returns the manually checked out path of the project's source if specified
+ or None."""
+ # TODO(metzman): Get rid of MANUAL_SRC_PATH when Skia switches to
+ # project_src_path.
+ path = os.getenv('PROJECT_SRC_PATH', os.getenv('MANUAL_SRC_PATH'))
+ if not path:
+ logging.debug('No PROJECT_SRC_PATH.')
+ return path
+
+ logging.debug('PROJECT_SRC_PATH set.')
+ if os.path.isabs(path):
+ return path
+
+ # If |src| is not absolute, assume we are running in GitHub actions.
+ # TODO(metzman): Don't make this assumption.
+ return os.path.join(workspace, path)
+
+
def main():
"""Build OSS-Fuzz project's fuzzers for CI tools.
This script is used to kick off the Github Actions CI tool. It is the
@@ -51,52 +77,54 @@ def main():
Returns:
0 on success or 1 on failure.
"""
- oss_fuzz_project_name = os.environ.get('OSS_FUZZ_PROJECT_NAME')
- github_repo_name = os.path.basename(os.environ.get('GITHUB_REPOSITORY'))
- commit_sha = os.environ.get('GITHUB_SHA')
- event = os.environ.get('GITHUB_EVENT_NAME')
- workspace = os.environ.get('GITHUB_WORKSPACE')
- sanitizer = os.environ.get('SANITIZER').lower()
+ oss_fuzz_project_name = os.getenv('OSS_FUZZ_PROJECT_NAME')
+ github_repo_name = os.path.basename(os.getenv('GITHUB_REPOSITORY'))
+ commit_sha = os.getenv('GITHUB_SHA')
+ event = os.getenv('GITHUB_EVENT_NAME')
+ workspace = os.getenv('GITHUB_WORKSPACE')
+ sanitizer = os.getenv('SANITIZER').lower()
+ project_src_path = get_project_src_path(workspace)
+ build_integration_path = os.getenv('BUILD_INTEGRATION_PATH')
+ allowed_broken_targets_percentage = os.getenv(
+ 'ALLOWED_BROKEN_TARGETS_PERCENTAGE')
# Check if failures should not be reported.
- dry_run = (os.environ.get('DRY_RUN').lower() == 'true')
-
- # The default return code when an error occurs.
- returncode = 1
+ dry_run = os.getenv('DRY_RUN').lower() == 'true'
if dry_run:
# Sets the default return code on error to success.
returncode = 0
+ else:
+ # The default return code when an error occurs.
+ returncode = 1
if not workspace:
- logging.error('This script needs to be run in the Github action context.')
- return returncode
-
- if event == 'push' and not cifuzz.build_fuzzers(oss_fuzz_project_name,
- github_repo_name,
- workspace,
- commit_sha=commit_sha,
- sanitizer=sanitizer):
- logging.error('Error building fuzzers for project %s with commit %s.',
- oss_fuzz_project_name, commit_sha)
+ logging.error('This script needs to be run within Github actions.')
return returncode
if event == 'pull_request':
- event_path = os.environ.get('GITHUB_EVENT_PATH')
- with open(event_path, encoding='utf-8') as file_handle:
- event = json.load(file_handle)
- pr_ref = 'refs/pull/{0}/merge'.format(event['pull_request']['number'])
- if not cifuzz.build_fuzzers(oss_fuzz_project_name,
- github_repo_name,
- workspace,
- pr_ref=pr_ref,
- sanitizer=sanitizer):
- logging.error(
- 'Error building fuzzers for project %s with pull request %s.',
- oss_fuzz_project_name, pr_ref)
- return returncode
+ event_path = os.getenv('GITHUB_EVENT_PATH')
+ pr_ref = get_pr_ref(event_path)
+ else:
+ pr_ref = None
+
+ if not cifuzz.build_fuzzers(oss_fuzz_project_name,
+ github_repo_name,
+ workspace,
+ commit_sha=commit_sha,
+ pr_ref=pr_ref,
+ sanitizer=sanitizer,
+ project_src_path=project_src_path,
+ build_integration_path=build_integration_path):
+ logging.error(
+ 'Error building fuzzers for project %s (commit: %s, pr_ref: %s).',
+ oss_fuzz_project_name, commit_sha, pr_ref)
+ return returncode
out_dir = os.path.join(workspace, 'out')
- if cifuzz.check_fuzzer_build(out_dir, sanitizer=sanitizer):
+ if cifuzz.check_fuzzer_build(
+ out_dir,
+ sanitizer=sanitizer,
+ allowed_broken_targets_percentage=allowed_broken_targets_percentage):
returncode = 0
return returncode
diff --git a/infra/cifuzz/cifuzz-base/Dockerfile b/infra/cifuzz/cifuzz-base/Dockerfile
index e006c2b48..0aee3b2cf 100644
--- a/infra/cifuzz/cifuzz-base/Dockerfile
+++ b/infra/cifuzz/cifuzz-base/Dockerfile
@@ -35,4 +35,4 @@ RUN apt-get update && apt-get install docker-ce docker-ce-cli containerd.io -y
ENV OSS_FUZZ_ROOT=/opt/oss-fuzz
ADD . ${OSS_FUZZ_ROOT}
-RUN rm -rf ${OSS_FUZZ_ROOT}/infra
+RUN rm -rf ${OSS_FUZZ_ROOT}/infra \ No newline at end of file
diff --git a/infra/cifuzz/cifuzz.py b/infra/cifuzz/cifuzz.py
index 479a4e07c..69e92a4c0 100644
--- a/infra/cifuzz/cifuzz.py
+++ b/infra/cifuzz/cifuzz.py
@@ -34,6 +34,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import build_specified_commit
import helper
import repo_manager
+import retry
import utils
# From clusterfuzz: src/python/crash_analysis/crash_analyzer.py
@@ -77,28 +78,273 @@ logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.DEBUG)
+_IMAGE_BUILD_TRIES = 3
+_IMAGE_BUILD_BACKOFF = 2
-def checkout_specified_commit(build_repo_manager, pr_ref, commit_sha):
+
+def checkout_specified_commit(repo_manager_obj, pr_ref, commit_sha):
"""Checks out the specified commit or pull request using
- build_repo_manager."""
+ |repo_manager_obj|."""
try:
if pr_ref:
- build_repo_manager.checkout_pr(pr_ref)
+ repo_manager_obj.checkout_pr(pr_ref)
else:
- build_repo_manager.checkout_commit(commit_sha)
+ repo_manager_obj.checkout_commit(commit_sha)
except (RuntimeError, ValueError):
logging.error(
'Can not check out requested state %s. '
'Using current repo state', pr_ref or commit_sha)
+@retry.wrap(_IMAGE_BUILD_TRIES, _IMAGE_BUILD_BACKOFF)
+def build_external_project_docker_image(project_name, project_src,
+ build_integration_path):
+ """Builds the project builder image for an external (non-OSS-Fuzz) project.
+ Returns True on success."""
+ dockerfile_path = os.path.join(build_integration_path, 'Dockerfile')
+ tag = 'gcr.io/oss-fuzz/{project_name}'.format(project_name=project_name)
+ command = ['-t', tag, '-f', dockerfile_path, project_src]
+ return helper.docker_build(command)
+
+
+def fix_git_repo_for_diff(repo_dir):
+ """Fixes git repos cloned by the "checkout" action so that diffing works on
+ them."""
+ command = [
+ 'git', 'symbolic-ref', 'refs/remotes/origin/HEAD',
+ 'refs/remotes/origin/master'
+ ]
+ return utils.execute(command, location=repo_dir)
+
+
+def check_project_src_path(project_src_path):
+ """Returns True if |project_src_path| exists."""
+ if not os.path.exists(project_src_path):
+ logging.error(
+ 'PROJECT_SRC_PATH: %s does not exist. '
+ 'Are you mounting it correctly?', project_src_path)
+ return False
+ return True
+
+
+# pylint: disable=too-many-arguments
+
+
+class BaseBuilder: # pylint: disable=too-many-instance-attributes
+ """Base class for fuzzer builders."""
+
+ def __init__(self,
+ project_name,
+ project_repo_name,
+ workspace,
+ sanitizer,
+ host_repo_path=None):
+ self.project_name = project_name
+ self.project_repo_name = project_repo_name
+ self.workspace = workspace
+ self.out_dir = os.path.join(workspace, 'out')
+ os.makedirs(self.out_dir, exist_ok=True)
+ self.sanitizer = sanitizer
+ self.host_repo_path = host_repo_path
+ self.image_repo_path = None
+ self.repo_manager = None
+
+ def build_image_and_checkout_src(self):
+ """Builds the project builder image and checkout source code for the patch
+ we want to fuzz (if necessary). Returns True on success.
+ Must be implemented by child classes."""
+ raise NotImplementedError('Child class must implement method')
+
+ def build_fuzzers(self):
+ """Moves the source code we want to fuzz into the project builder and builds
+ the fuzzers from that source code. Returns True on success."""
+ image_src_path = os.path.dirname(self.image_repo_path)
+ command = get_common_docker_args(self.sanitizer)
+ container = utils.get_container_name()
+
+ if container:
+ command.extend(['-e', 'OUT=' + self.out_dir, '--volumes-from', container])
+ rm_path = os.path.join(self.image_repo_path, '*')
+
+ bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
+ rm_path, self.host_repo_path, image_src_path)
+ else:
+ # TODO(metzman): Figure out if we can eliminate this branch.
+ command.extend([
+ '-e', 'OUT=' + '/out', '-v',
+ '%s:%s' % (self.host_repo_path, self.image_repo_path), '-v',
+ '%s:%s' % (self.out_dir, '/out')
+ ])
+ bash_command = 'compile'
+
+ command.extend([
+ 'gcr.io/oss-fuzz/' + self.project_name,
+ '/bin/bash',
+ '-c',
+ ])
+ command.append(bash_command)
+ logging.info('Building with %s sanitizer.', self.sanitizer)
+ if helper.docker_run(command):
+ # docker_run returns nonzero on failure.
+ logging.error('Building fuzzers failed.')
+ return False
+ return True
+
+ def build(self):
+ """Builds the image, checkouts the source (if needed), builds the fuzzers
+ and then removes the unaffectted fuzzers. Returns True on success."""
+ methods = [
+ self.build_image_and_checkout_src, self.build_fuzzers,
+ self.remove_unaffected_fuzzers
+ ]
+ for method in methods:
+ if not method():
+ return False
+ return True
+
+ def remove_unaffected_fuzzers(self):
+ """Removes the fuzzers unaffected by the patch."""
+ fix_git_repo_for_diff(self.host_repo_path)
+ remove_unaffected_fuzzers(self.project_name, self.out_dir,
+ self.repo_manager.get_git_diff(),
+ self.image_repo_path)
+ return True
+
+
+class ExternalGithubBuilder(BaseBuilder):
+ """Class for building non-OSS-Fuzz projects on GitHub Actions."""
+
+ def __init__(self, project_name, project_repo_name, workspace, sanitizer,
+ project_src_path, build_integration_path):
+
+ super().__init__(project_name,
+ project_repo_name,
+ workspace,
+ sanitizer,
+ host_repo_path=project_src_path)
+ self.build_integration_path = os.path.join(self.host_repo_path,
+ build_integration_path)
+ logging.info('build_integration_path %s, project_src_path %s.',
+ self.build_integration_path, self.host_repo_path)
+ self.image_repo_path = os.path.join('/src', project_repo_name)
+
+ def build_image_and_checkout_src(self):
+ """Builds the project builder image for a non-OSS-Fuzz project. Sets the
+ repo manager. Does not checkout source code since external projects are
+ expected to bring their own source code to CIFuzz. Returns True on
+ success."""
+ logging.info('Building external project.')
+ if not build_external_project_docker_image(
+ self.project_name, self.host_repo_path, self.build_integration_path):
+ logging.error('Failed to build external project.')
+ return False
+ self.repo_manager = repo_manager.RepoManager(self.host_repo_path)
+ return True
+
+
+class InternalGithubBuilder(BaseBuilder):
+ """Class for building OSS-Fuzz projects on GitHub actions."""
+
+ def __init__(self, project_name, project_repo_name, workspace, sanitizer,
+ commit_sha, pr_ref):
+ # Validate inputs.
+ assert pr_ref or commit_sha
+
+ super().__init__(project_name, project_repo_name, workspace, sanitizer)
+
+ self.commit_sha = commit_sha
+ self.pr_ref = pr_ref
+
+ def build_image_and_checkout_src(self):
+ """Builds the project builder image for a non-OSS-Fuzz project. Sets the
+ repo manager and host_repo_path. Checks out source code of project with
+ patch under test. Returns True on success."""
+ logging.info('Building OSS-Fuzz project on Github Actions.')
+ # detect_main_repo builds the image as a side effect.
+ inferred_url, self.image_repo_path = (
+ build_specified_commit.detect_main_repo(
+ self.project_name, repo_name=self.project_repo_name))
+
+ if not inferred_url or not self.image_repo_path:
+ logging.error('Could not detect repo from project %s.', self.project_name)
+ return False
+
+ git_workspace = os.path.join(self.workspace, 'storage')
+ os.makedirs(git_workspace, exist_ok=True)
+
+ # Checkout project's repo in the shared volume.
+ self.repo_manager = repo_manager.clone_repo_and_get_manager(
+ inferred_url, git_workspace, repo_name=self.project_repo_name)
+
+ self.host_repo_path = self.repo_manager.repo_dir
+
+ checkout_specified_commit(self.repo_manager, self.pr_ref, self.commit_sha)
+ return True
+
+
+class InternalGenericCiBuilder(BaseBuilder):
+ """Class for building fuzzers for an OSS-Fuzz project using on a platform
+ other than GitHub actions."""
+
+ def __init__(self, project_name, project_repo_name, workspace, sanitizer,
+ project_src_path):
+ super().__init__(project_name,
+ project_repo_name,
+ workspace,
+ sanitizer,
+ host_repo_path=project_src_path)
+
+ def build_image_and_checkout_src(self):
+ """Builds the project builder image for a non-OSS-Fuzz project. Sets the
+ repo manager. Does not checkout source code since external projects are
+ expected to bring their own source code to CIFuzz. Returns True on
+ success."""
+ logging.info('Building OSS-Fuzz project.')
+ # detect_main_repo builds the image as a side effect.
+ _, self.image_repo_path = (build_specified_commit.detect_main_repo(
+ self.project_name, repo_name=self.project_repo_name))
+
+ if not self.image_repo_path:
+ logging.error('Could not detect repo from project %s.', self.project_name)
+ return False
+
+ # Checkout project's repo in the shared volume.
+ self.repo_manager = repo_manager.RepoManager(self.host_repo_path)
+ return True
+
+
+def get_builder(project_name, project_repo_name, workspace, pr_ref, commit_sha,
+ sanitizer, project_src_path, build_integration_path):
+ """Determines what kind of build is being requested using the arguments
+ provided and instantiates and returns the correct builder object."""
+ if build_integration_path and project_src_path:
+ # Non-OSS-Fuzz projects must bring their own source and their own build
+ # integration (which is relative to that source).
+ return ExternalGithubBuilder(project_name, project_repo_name, workspace,
+ sanitizer, project_src_path,
+ build_integration_path)
+
+ if project_src_path:
+ # Builds of OSS-Fuzz projects not hosted on Github must bring their own
+ # source since the checkout logic CIFuzz implements is github-specific.
+ # TODO(metzman): Consider moving Github-actions builds of OSS-Fuzz projects
+ # to this system to reduce implementation complexity.
+ return InternalGenericCiBuilder(project_name, project_repo_name, workspace,
+ sanitizer, project_src_path)
+
+ return InternalGithubBuilder(project_name, project_repo_name, workspace,
+ sanitizer, commit_sha, pr_ref)
+
+
def build_fuzzers( # pylint: disable=too-many-arguments,too-many-locals
project_name,
project_repo_name,
workspace,
pr_ref=None,
commit_sha=None,
- sanitizer='address'):
+ sanitizer='address',
+ project_src_path=None,
+ build_integration_path=None):
"""Builds all of the fuzzers for a specific OSS-Fuzz project.
Args:
@@ -113,91 +359,15 @@ def build_fuzzers( # pylint: disable=too-many-arguments,too-many-locals
Returns:
True if build succeeded or False on failure.
"""
- # Validate inputs.
- assert pr_ref or commit_sha
- if not os.path.exists(workspace):
- logging.error('Invalid workspace: %s.', workspace)
+ # Do some quick validation.
+ if project_src_path and not check_project_src_path(project_src_path):
return False
- logging.info("Using %s sanitizer.", sanitizer)
-
- out_dir = os.path.join(workspace, 'out')
- os.makedirs(out_dir, exist_ok=True)
-
- # Build Fuzzers using docker run.
- inferred_url, project_builder_repo_path = (
- build_specified_commit.detect_main_repo(project_name,
- repo_name=project_repo_name))
- if not inferred_url or not project_builder_repo_path:
- logging.error('Could not detect repo from project %s.', project_name)
- return False
- project_repo_name = os.path.basename(project_builder_repo_path)
- src_in_project_builder = os.path.dirname(project_builder_repo_path)
-
- manual_src_path = os.getenv('MANUAL_SRC_PATH')
- if manual_src_path:
- if not os.path.exists(manual_src_path):
- logging.error(
- 'MANUAL_SRC_PATH: %s does not exist. '
- 'Are you mounting it correctly?', manual_src_path)
- return False
- # This is the path taken outside of GitHub actions.
- git_workspace = os.path.dirname(manual_src_path)
- else:
- git_workspace = os.path.join(workspace, 'storage')
- os.makedirs(git_workspace, exist_ok=True)
-
- # Checkout projects repo in the shared volume.
- build_repo_manager = repo_manager.RepoManager(inferred_url,
- git_workspace,
- repo_name=project_repo_name)
-
- if not manual_src_path:
- checkout_specified_commit(build_repo_manager, pr_ref, commit_sha)
-
- command = [
- '--cap-add',
- 'SYS_PTRACE',
- '-e',
- 'FUZZING_ENGINE=' + DEFAULT_ENGINE,
- '-e',
- 'SANITIZER=' + sanitizer,
- '-e',
- 'ARCHITECTURE=' + DEFAULT_ARCHITECTURE,
- '-e',
- 'CIFUZZ=True',
- '-e',
- 'FUZZING_LANGUAGE=c++', # FIXME: Add proper support.
- ]
- container = utils.get_container_name()
- if container:
- command += ['-e', 'OUT=' + out_dir, '--volumes-from', container]
- bash_command = 'rm -rf {0} && cp -r {1} {2} && compile'.format(
- os.path.join(src_in_project_builder, project_repo_name, '*'),
- os.path.join(git_workspace, project_repo_name), src_in_project_builder)
- else:
- command += [
- '-e', 'OUT=' + '/out', '-v',
- '%s:%s' % (os.path.join(git_workspace, project_repo_name),
- os.path.join(src_in_project_builder, project_repo_name)),
- '-v',
- '%s:%s' % (out_dir, '/out')
- ]
- bash_command = 'compile'
-
- command.extend([
- 'gcr.io/oss-fuzz/' + project_name,
- '/bin/bash',
- '-c',
- ])
- command.append(bash_command)
- if helper.docker_run(command):
- logging.error('Building fuzzers failed.')
- return False
- remove_unaffected_fuzzers(project_name, out_dir,
- build_repo_manager.get_git_diff(),
- project_builder_repo_path)
- return True
+ # Get the builder and then build the fuzzers.
+ builder = get_builder(project_name, project_repo_name, workspace, pr_ref,
+ commit_sha, sanitizer, project_src_path,
+ build_integration_path)
+ return builder.build()
def run_fuzzers( # pylint: disable=too-many-arguments,too-many-locals
@@ -267,7 +437,27 @@ def run_fuzzers( # pylint: disable=too-many-arguments,too-many-locals
return True, False
-def check_fuzzer_build(out_dir, sanitizer='address'):
+def get_common_docker_args(sanitizer):
+ """Returns a list of common docker arguments."""
+ return [
+ '--cap-add',
+ 'SYS_PTRACE',
+ '-e',
+ 'FUZZING_ENGINE=' + DEFAULT_ENGINE,
+ '-e',
+ 'SANITIZER=' + sanitizer,
+ '-e',
+ 'ARCHITECTURE=' + DEFAULT_ARCHITECTURE,
+ '-e',
+ 'CIFUZZ=True',
+ '-e',
+ 'FUZZING_LANGUAGE=c++', # FIXME: Add proper support.
+ ]
+
+
+def check_fuzzer_build(out_dir,
+ sanitizer='address',
+ allowed_broken_targets_percentage=None):
"""Checks the integrity of the built fuzzers.
Args:
@@ -284,24 +474,8 @@ def check_fuzzer_build(out_dir, sanitizer='address'):
logging.error('No fuzzers found in out directory: %s.', out_dir)
return False
- command = [
- '--cap-add',
- 'SYS_PTRACE',
- '-e',
- 'FUZZING_ENGINE=' + DEFAULT_ENGINE,
- '-e',
- 'SANITIZER=' + sanitizer,
- '-e',
- 'ARCHITECTURE=' + DEFAULT_ARCHITECTURE,
- '-e',
- 'CIFUZZ=True',
- '-e',
- 'FUZZING_LANGUAGE=c++', # FIXME: Add proper support.
- ]
+ command = get_common_docker_args(sanitizer)
- # Set ALLOWED_BROKEN_TARGETS_PERCENTAGE in docker if specified by user.
- allowed_broken_targets_percentage = os.getenv(
- 'ALLOWED_BROKEN_TARGETS_PERCENTAGE')
if allowed_broken_targets_percentage is not None:
command += [
'-e',
@@ -316,6 +490,7 @@ def check_fuzzer_build(out_dir, sanitizer='address'):
command += ['-v', '%s:/out' % out_dir]
command.extend(['-t', 'gcr.io/oss-fuzz-base/base-runner', 'test_all.py'])
exit_code = helper.docker_run(command)
+ logging.info('check fuzzer build exit code: %d', exit_code)
if exit_code:
logging.error('Check fuzzer build failed.')
return False
diff --git a/infra/cifuzz/cifuzz_test.py b/infra/cifuzz/cifuzz_test.py
index e7624f549..87ce8f236 100644
--- a/infra/cifuzz/cifuzz_test.py
+++ b/infra/cifuzz/cifuzz_test.py
@@ -33,6 +33,7 @@ OSS_FUZZ_DIR = os.path.dirname(INFRA_DIR)
import cifuzz
import fuzz_target
+import test_helpers
# NOTE: This integration test relies on
# https://github.com/google/oss-fuzz/tree/master/projects/example project.
@@ -69,7 +70,7 @@ class BuildFuzzersTest(unittest.TestCase):
@mock.patch('build_specified_commit.detect_main_repo',
return_value=('example.com', '/path'))
- @mock.patch('repo_manager.RepoManager', return_value=None)
+ @mock.patch('repo_manager._clone', return_value=None)
@mock.patch('cifuzz.checkout_specified_commit')
@mock.patch('helper.docker_run')
def test_cifuzz_env_var(self, mocked_docker_run, _, __, ___):
@@ -97,6 +98,26 @@ class BuildFuzzersTest(unittest.TestCase):
class BuildFuzzersIntegrationTest(unittest.TestCase):
"""Integration tests for build_fuzzers."""
+ def setUp(self):
+ test_helpers.patch_environ(self)
+
+ def test_external_project(self):
+ """Tests building fuzzers from an external project."""
+ project_name = 'external-project'
+ project_src_path = os.path.join(TEST_FILES_PATH, project_name)
+ build_integration_path = os.path.join(project_src_path, 'oss-fuzz')
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ out_path = os.path.join(tmp_dir, 'out')
+ os.mkdir(out_path)
+ self.assertTrue(
+ cifuzz.build_fuzzers(project_name,
+ project_name,
+ tmp_dir,
+ project_src_path=project_src_path,
+ build_integration_path=build_integration_path))
+ self.assertTrue(
+ os.path.exists(os.path.join(out_path, EXAMPLE_BUILD_FUZZER)))
+
def test_valid_commit(self):
"""Tests building fuzzers with valid inputs."""
with tempfile.TemporaryDirectory() as tmp_dir:
@@ -343,14 +364,14 @@ class CheckFuzzerBuildTest(unittest.TestCase):
"""Checks a directory that exists but does not have fuzzers is False."""
self.assertFalse(cifuzz.check_fuzzer_build(TEST_FILES_PATH))
- @mock.patch.dict(os.environ, {'ALLOWED_BROKEN_TARGETS_PERCENTAGE': '0'})
@mock.patch('helper.docker_run')
def test_allow_broken_fuzz_targets_percentage(self, mocked_docker_run):
"""Tests that ALLOWED_BROKEN_TARGETS_PERCENTAGE is set when running
- docker if it is set in the environment."""
+ docker if passed to check_fuzzer_build."""
mocked_docker_run.return_value = 0
test_fuzzer_dir = os.path.join(TEST_FILES_PATH, 'out')
- cifuzz.check_fuzzer_build(test_fuzzer_dir)
+ cifuzz.check_fuzzer_build(test_fuzzer_dir,
+ allowed_broken_targets_percentage='0')
self.assertIn('-e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0',
' '.join(mocked_docker_run.call_args[0][0]))
diff --git a/infra/cifuzz/fuzz_target_test.py b/infra/cifuzz/fuzz_target_test.py
index 3e8788813..8f72f32cb 100644
--- a/infra/cifuzz/fuzz_target_test.py
+++ b/infra/cifuzz/fuzz_target_test.py
@@ -320,9 +320,11 @@ class DownloadOSSFuzzBuildDirIntegrationTest(unittest.TestCase):
def test_invalid_build_dir(self):
"""Tests the download returns None when out_dir doesn't exist."""
- test_target = fuzz_target.FuzzTarget('/example/do_stuff_fuzzer', 10,
- 'not/a/dir', 'example')
- self.assertIsNone(test_target.download_oss_fuzz_build())
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ invalid_dir = os.path.join(tmp_dir, 'not/a/dir')
+ test_target = fuzz_target.FuzzTarget('/example/do_stuff_fuzzer', 10,
+ invalid_dir, 'example')
+ self.assertIsNone(test_target.download_oss_fuzz_build())
class DownloadUrlTest(unittest.TestCase):
diff --git a/infra/cifuzz/test_files/external-project/Makefile b/infra/cifuzz/test_files/external-project/Makefile
new file mode 100644
index 000000000..2c1773776
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/Makefile
@@ -0,0 +1,44 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+# Licensed under the Apache License, Version 2.0 (the "License");
+
+# Simple example of a build file that nicely integrates a fuzz target
+# with the rest of the project.
+#
+# We use 'make' as the build system, but these ideas are applicable
+# to any other build system
+
+# By default, use our own standalone_fuzz_target_runner.
+# This runner does no fuzzing, but simply executes the inputs
+# provided via parameters.
+# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a"
+# to link the fuzzer(s) against a real fuzzing engine.
+#
+# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
+LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
+
+# Values for CC, CFLAGS, CXX, CXXFLAGS are provided by OSS-Fuzz.
+# Outside of OSS-Fuzz use the ones you prefer or rely on the default values.
+# Do not use the -fsanitize=* flags by default.
+# OSS-Fuzz will use different -fsanitize=* flags for different builds (asan, ubsan, msan, ...)
+
+# You may add extra compiler flags like this:
+CXXFLAGS += -std=c++11
+
+all: do_stuff_fuzzer
+
+clean:
+ rm -fv *.a *.o *_fuzzer crash-* *.zip
+
+# Fuzz target, links against $LIB_FUZZING_ENGINE, so that
+# you may choose which fuzzing engine to use.
+do_stuff_fuzzer: do_stuff_fuzzer.cpp my_api.a standalone_fuzz_target_runner.o
+ ${CXX} ${CXXFLAGS} $< my_api.a ${LIB_FUZZING_ENGINE} -o $@
+
+
+# The library itself.
+my_api.a: my_api.cpp my_api.h
+ ${CXX} ${CXXFLAGS} $< -c
+ ar ruv my_api.a my_api.o
+
+# The standalone fuzz target runner.
+standalone_fuzz_target_runner.o: standalone_fuzz_target_runner.cpp
diff --git a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp b/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp
new file mode 100644
index 000000000..71fa8cae2
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.cpp
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "my_api.h"
+
+#include <string>
+
+// Simple fuzz target for DoStuff().
+// See http://libfuzzer.info for details.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ std::string str(reinterpret_cast<const char *>(data), size);
+ DoStuff(str); // Disregard the output.
+ return 0;
+}
diff --git a/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict b/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict
new file mode 100644
index 000000000..224679bf4
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/do_stuff_fuzzer.dict
@@ -0,0 +1,6 @@
+# A dictionary for more efficient fuzzing of DoStuff().
+# If the inputs contain multi-byte tokens, list them here.
+# See http://libfuzzer.info#dictionaries
+"foo"
+"bar"
+"ouch"
diff --git a/infra/cifuzz/test_files/external-project/my_api.cpp b/infra/cifuzz/test_files/external-project/my_api.cpp
new file mode 100644
index 000000000..9a2c1bc1c
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/my_api.cpp
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Implementation of "my_api".
+#include "my_api.h"
+
+#include <vector>
+
+// Do some computations with 'str', return the result.
+// This function contains a bug. Can you spot it?
+size_t DoStuff(const std::string &str) {
+ std::vector<int> Vec({0, 1, 2, 3, 4});
+ size_t Idx = 0;
+ if (str.size() > 5)
+ Idx++;
+ if (str.find("foo") != std::string::npos)
+ Idx++;
+ if (str.find("bar") != std::string::npos)
+ Idx++;
+ if (str.find("ouch") != std::string::npos)
+ Idx++;
+ if (str.find("omg") != std::string::npos)
+ Idx++;
+ return Vec[Idx];
+}
diff --git a/infra/cifuzz/test_files/external-project/my_api.h b/infra/cifuzz/test_files/external-project/my_api.h
new file mode 100644
index 000000000..325aa15cc
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/my_api.h
@@ -0,0 +1,19 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A library that does ... stuff.
+// Serves as an example of good fuzz testing and OSS-Fuzz integration.
+#include <string>
+
+size_t DoStuff(const std::string &str);
diff --git a/infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile b/infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile
new file mode 100644
index 000000000..e9dc33031
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/oss-fuzz/Dockerfile
@@ -0,0 +1,22 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+################################################################################
+
+FROM gcr.io/oss-fuzz-base/base-builder
+RUN apt-get update && apt-get install -y make
+
+COPY . $SRC/external-project
+WORKDIR external-project
+COPY oss-fuzz/build.sh $SRC/
diff --git a/infra/cifuzz/test_files/external-project/oss-fuzz/build.sh b/infra/cifuzz/test_files/external-project/oss-fuzz/build.sh
new file mode 100644
index 000000000..2c52ef90f
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/oss-fuzz/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash -eu
+# Copyright 2020 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+################################################################################
+
+make clean # Not strictly necessary, since we are building in a fresh dir.
+make -j$(nproc) all # Build the fuzz targets.
+
+# Copy the fuzzer executables, zip-ed corpora, option and dictionary files to $OUT
+find . -name '*_fuzzer' -exec cp -v '{}' $OUT ';'
+find . -name '*_fuzzer.dict' -exec cp -v '{}' $OUT ';' # If you have dictionaries.
+find . -name '*_fuzzer.options' -exec cp -v '{}' $OUT ';' # If you have custom options.
diff --git a/infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp b/infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp
new file mode 100644
index 000000000..38a0454f0
--- /dev/null
+++ b/infra/cifuzz/test_files/external-project/standalone_fuzz_target_runner.cpp
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Example of a standalone runner for "fuzz targets".
+// It reads all files passed as parameters and feeds their contents
+// one by one into the fuzz target (LLVMFuzzerTestOneInput).
+// This runner does not do any fuzzing, but allows us to run the fuzz target
+// on the test corpus (e.g. "do_stuff_test_data") or on a single file,
+// e.g. the one that comes from a bug report.
+
+#include <cassert>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+// Forward declare the "fuzz target" interface.
+// We deliberately keep this inteface simple and header-free.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int main(int argc, char **argv) {
+ for (int i = 1; i < argc; i++) {
+ std::ifstream in(argv[i]);
+ in.seekg(0, in.end);
+ size_t length = in.tellg();
+ in.seekg (0, in.beg);
+ std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl;
+ // Allocate exactly length bytes so that we reliably catch buffer overflows.
+ std::vector<char> bytes(length);
+ in.read(bytes.data(), bytes.size());
+ assert(in);
+ LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()),
+ bytes.size());
+ std::cout << "Execution successful" << std::endl;
+ }
+ return 0;
+}
diff --git a/infra/repo_manager.py b/infra/repo_manager.py
index 71f3e7821..1fa485bc1 100644
--- a/infra/repo_manager.py
+++ b/infra/repo_manager.py
@@ -29,8 +29,8 @@ import shutil
import utils
-class BaseRepoManager:
- """Base repo manager."""
+class RepoManager:
+ """Repo manager."""
def __init__(self, repo_dir):
self.repo_dir = repo_dir
@@ -200,51 +200,36 @@ class BaseRepoManager:
raise RuntimeError('Error checking out commit %s' % commit)
def remove_repo(self):
- """Attempts to remove the git repo. """
+ """Removes the git repo from disk."""
if os.path.isdir(self.repo_dir):
shutil.rmtree(self.repo_dir)
-class RepoManager(BaseRepoManager):
- """Class to manage git repos from python.
-
- Attributes:
- repo_url: The location of the git repo.
- base_dir: The location of where the repo clone is stored locally.
- repo_name: The name of the GitHub project.
- repo_dir: The location of the main repo.
- """
-
- def __init__(self, repo_url, base_dir, repo_name=None):
- """Constructs a repo manager class.
+def clone_repo_and_get_manager(repo_url, base_dir, repo_name=None):
+ """Clones a repo and constructs a repo manager class.
Args:
repo_url: The github url needed to clone.
base_dir: The full file-path where the git repo is located.
repo_name: The name of the directory the repo is cloned to.
"""
- self.repo_url = repo_url
- self.base_dir = base_dir
- if repo_name:
- self.repo_name = repo_name
- else:
- self.repo_name = os.path.basename(self.repo_url).replace('.git', '')
- repo_dir = os.path.join(self.base_dir, self.repo_name)
- super(RepoManager, self).__init__(repo_dir)
+ if repo_name is None:
+ repo_name = os.path.basename(repo_url).replace('.git', '')
+ repo_dir = os.path.join(base_dir, repo_name)
+ manager = RepoManager(repo_dir)
- if not os.path.exists(self.repo_dir):
- self._clone()
+ if not os.path.exists(repo_dir):
+ _clone(repo_url, base_dir, repo_name)
- def _clone(self):
- """Creates a clone of the repo in the specified directory.
+ return manager
- Raises:
- ValueError: when the repo is not able to be cloned.
- """
- if not os.path.exists(self.base_dir):
- os.makedirs(self.base_dir)
- self.remove_repo()
- out, _, _ = utils.execute(['git', 'clone', self.repo_url, self.repo_name],
- location=self.base_dir)
- if not self._is_git_repo():
- raise ValueError('%s is not a git repo' % self.repo_url)
+
+def _clone(repo_url, base_dir, repo_name):
+ """Creates a clone of the repo in the specified directory.
+
+ Raises:
+ ValueError: when the repo is not able to be cloned.
+ """
+ utils.execute(['git', 'clone', repo_url, repo_name],
+ location=base_dir,
+ check_result=True)
diff --git a/infra/repo_manager_test.py b/infra/repo_manager_test.py
index f3294bc2a..653a21718 100644
--- a/infra/repo_manager_test.py
+++ b/infra/repo_manager_test.py
@@ -13,6 +13,7 @@
# limitations under the License.
"""Test the functionality of the RepoManager class."""
+import contextlib
import os
import tempfile
import unittest
@@ -21,31 +22,36 @@ from unittest import mock
import repo_manager
import utils
-OSS_FUZZ_REPO = 'https://github.com/google/oss-fuzz'
+# pylint: disable=protected-access
+OSS_FUZZ_REPO_URL = 'https://github.com/google/oss-fuzz'
-class RepoManagerCloneTest(unittest.TestCase):
- """Tests the cloning functionality of RepoManager."""
+
+@contextlib.contextmanager
+def get_oss_fuzz_repo():
+ """Clones a temporary copy of the OSS-Fuzz repo. Returns the path to the
+ repo."""
+ repo_name = 'oss-fuzz'
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ repo_manager._clone(OSS_FUZZ_REPO_URL, tmp_dir, repo_name)
+ yield os.path.join(tmp_dir, repo_name)
+
+
+class CloneIntegrationTest(unittest.TestCase):
+ """Tests the _clone function."""
def test_clone_valid_repo(self):
"""Tests the correct location of the git repo."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
- git_path = os.path.join(test_repo_manager.base_dir,
- test_repo_manager.repo_name, '.git')
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ git_path = os.path.join(oss_fuzz_repo, '.git')
self.assertTrue(os.path.isdir(git_path))
- test_repo_manager.remove_repo()
def test_clone_invalid_repo(self):
- """Tests that constructing RepoManager with an invalid repo will fail."""
+ """Tests that cloning an invalid repo will fail."""
with tempfile.TemporaryDirectory() as tmp_dir:
- with self.assertRaises(ValueError):
- repo_manager.RepoManager(' ', tmp_dir)
- with self.assertRaises(ValueError):
- repo_manager.RepoManager('not_a_valid_repo', tmp_dir)
- with self.assertRaises(ValueError):
- repo_manager.RepoManager('https://github.com/oss-fuzz-not-real.git',
- tmp_dir)
+ with self.assertRaises(RuntimeError):
+ repo_manager._clone('https://github.com/oss-fuzz-not-real.git', tmp_dir,
+ 'oss-fuzz')
class RepoManagerCheckoutTest(unittest.TestCase):
@@ -53,23 +59,22 @@ class RepoManagerCheckoutTest(unittest.TestCase):
def test_checkout_valid_commit(self):
"""Tests that the git checkout command works."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
commit_to_test = '04ea24ee15bbe46a19e5da6c5f022a2ffdfbdb3b'
- test_repo_manager.checkout_commit(commit_to_test)
- self.assertEqual(commit_to_test, test_repo_manager.get_current_commit())
+ repo_man.checkout_commit(commit_to_test)
+ self.assertEqual(commit_to_test, repo_man.get_current_commit())
def test_checkout_invalid_commit(self):
"""Tests that the git checkout invalid commit fails."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
with self.assertRaises(ValueError):
- test_repo_manager.checkout_commit(' ')
+ repo_man.checkout_commit(' ')
with self.assertRaises(ValueError):
- test_repo_manager.checkout_commit(
- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+ repo_man.checkout_commit('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
with self.assertRaises(ValueError):
- test_repo_manager.checkout_commit('not-a-valid-commit')
+ repo_man.checkout_commit('not-a-valid-commit')
class RepoManagerGetCommitListTest(unittest.TestCase):
@@ -77,8 +82,8 @@ class RepoManagerGetCommitListTest(unittest.TestCase):
def test_get_valid_commit_list(self):
"""Tests an accurate commit list can be retrieved from the repo manager."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
old_commit = '04ea24ee15bbe46a19e5da6c5f022a2ffdfbdb3b'
new_commit = 'fa662173bfeb3ba08d2e84cefc363be11e6c8463'
commit_list = [
@@ -87,22 +92,22 @@ class RepoManagerGetCommitListTest(unittest.TestCase):
'97dee00a3c4ce95071c3e061592f5fd577dea886',
'04ea24ee15bbe46a19e5da6c5f022a2ffdfbdb3b'
]
- result_list = test_repo_manager.get_commit_list(new_commit, old_commit)
+ result_list = repo_man.get_commit_list(new_commit, old_commit)
self.assertListEqual(commit_list, result_list)
def test_get_invalid_commit_list(self):
"""Tests that the proper errors are thrown when invalid commits are
passed to get_commit_list."""
- with tempfile.TemporaryDirectory() as tmp_dir:
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
old_commit = '04ea24ee15bbe46a19e5da6c5f022a2ffdfbdb3b'
new_commit = 'fa662173bfeb3ba08d2e84cefc363be11e6c8463'
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
with self.assertRaises(ValueError):
- test_repo_manager.get_commit_list('fakecommit', new_commit)
+ repo_man.get_commit_list('fakecommit', new_commit)
with self.assertRaises(ValueError):
- test_repo_manager.get_commit_list(new_commit, 'fakecommit')
+ repo_man.get_commit_list(new_commit, 'fakecommit')
with self.assertRaises(RuntimeError):
- test_repo_manager.get_commit_list(old_commit, new_commit) # pylint: disable=arguments-out-of-order
+ repo_man.get_commit_list(old_commit, new_commit) # pylint: disable=arguments-out-of-order
class GitDiffTest(unittest.TestCase):
@@ -110,8 +115,8 @@ class GitDiffTest(unittest.TestCase):
def test_diff_exists(self):
"""Tests that a real diff is returned when a valid repo manager exists."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- repo_man = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
with mock.patch.object(utils,
'execute',
return_value=('test.py\ndiff.py', None, 0)):
@@ -120,16 +125,16 @@ class GitDiffTest(unittest.TestCase):
def test_diff_empty(self):
"""Tests that None is returned when there is no difference between repos."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- repo_man = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
with mock.patch.object(utils, 'execute', return_value=('', None, 0)):
diff = repo_man.get_git_diff()
self.assertIsNone(diff)
def test_error_on_command(self):
"""Tests that None is returned when the command errors out."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- repo_man = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
with mock.patch.object(utils,
'execute',
return_value=('', 'Test error.', 1)):
@@ -138,8 +143,8 @@ class GitDiffTest(unittest.TestCase):
def test_diff_no_change(self):
"""Tests that None is returned when there is no difference between repos."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- repo_man = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
diff = repo_man.get_git_diff()
self.assertIsNone(diff)
@@ -149,24 +154,22 @@ class CheckoutPrIntegrationTest(unittest.TestCase):
def test_pull_request_exists(self):
"""Tests that a diff is returned when a valid PR is checked out."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- repo_man = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
repo_man.checkout_pr('refs/pull/3415/merge')
diff = repo_man.get_git_diff()
- print(diff)
self.assertCountEqual(diff, ['README.md'])
def test_checkout_invalid_pull_request(self):
"""Tests that the git checkout invalid pull request fails."""
- with tempfile.TemporaryDirectory() as tmp_dir:
- test_repo_manager = repo_manager.RepoManager(OSS_FUZZ_REPO, tmp_dir)
+ with get_oss_fuzz_repo() as oss_fuzz_repo:
+ repo_man = repo_manager.RepoManager(oss_fuzz_repo)
with self.assertRaises(RuntimeError):
- test_repo_manager.checkout_pr(' ')
+ repo_man.checkout_pr(' ')
with self.assertRaises(RuntimeError):
- test_repo_manager.checkout_pr(
- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
+ repo_man.checkout_pr('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
with self.assertRaises(RuntimeError):
- test_repo_manager.checkout_pr('not/a/valid/pr')
+ repo_man.checkout_pr('not/a/valid/pr')
if __name__ == '__main__':
diff --git a/infra/retry.py b/infra/retry.py
index 4205319ea..293da998e 100644
--- a/infra/retry.py
+++ b/infra/retry.py
@@ -35,7 +35,6 @@ def get_delay(num_try, delay, backoff):
def wrap(retries,
delay,
- function,
backoff=2,
exception_type=Exception,
retry_on_false=False):
@@ -49,7 +48,7 @@ def wrap(retries,
"""Decorator for the given function."""
tries = retries + 1
is_generator = inspect.isgeneratorfunction(func)
- function_with_type = function
+ function_with_type = func.__qualname__
if is_generator:
function_with_type += ' (generator)'
diff --git a/infra/run_fuzzers.Dockerfile b/infra/run_fuzzers.Dockerfile
index 938cf0881..38a65259c 100644
--- a/infra/run_fuzzers.Dockerfile
+++ b/infra/run_fuzzers.Dockerfile
@@ -17,7 +17,6 @@
FROM gcr.io/oss-fuzz-base/cifuzz-base
-# Copies your code file from action repository to the container
COPY cifuzz/actions/run_fuzzers/run_fuzzers_entrypoint.py /opt/run_fuzzers_entrypoint.py
# Python file to execute when the docker container starts up
diff --git a/infra/test_helpers.py b/infra/test_helpers.py
new file mode 100644
index 000000000..5a9a4d582
--- /dev/null
+++ b/infra/test_helpers.py
@@ -0,0 +1,27 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Contains convenient helpers for writing tests."""
+
+import os
+from unittest import mock
+
+
+def patch_environ(testcase_obj, env=None):
+ """Patch environment."""
+ if env is None:
+ env = {}
+
+ patcher = mock.patch.dict(os.environ, env)
+ testcase_obj.addCleanup(patcher.stop)
+ patcher.start()