aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--infra/bisector.py55
-rw-r--r--infra/build_specified_commit.py58
-rw-r--r--infra/repo_manager.py149
3 files changed, 170 insertions, 92 deletions
diff --git a/infra/bisector.py b/infra/bisector.py
index 1b06997d..ee13be3d 100644
--- a/infra/bisector.py
+++ b/infra/bisector.py
@@ -94,35 +94,23 @@ def main():
return 0
-def bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data): # pylint: disable=too-many-locals
- """From a commit range, this function caluclates which introduced a
- specific error from a fuzz test_case_path.
-
- Args:
- old_commit: The oldest commit in the error regression range.
- new_commit: The newest commit in the error regression range.
- test_case_path: The file path of the test case that triggers the error
- fuzz_target: The name of the fuzzer to be tested.
- build_data: a class holding all of the input parameters for bisection.
-
- Returns:
- A Result.
-
- Raises:
- ValueError: when a repo url can't be determine from the project.
- """
+def _bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data): # pylint: disable=too-many-locals
+ """Perform the bisect."""
with tempfile.TemporaryDirectory() as tmp_dir:
repo_url, repo_path = build_specified_commit.detect_main_repo(
build_data.project_name, commit=new_commit)
if not repo_url or not repo_path:
raise ValueError('Main git repo can not be determined.')
+ # Copy /src from the built Docker container to ensure all dependencies
+ # exist. This will be mounted when running them.
host_src_dir = build_specified_commit.copy_src_from_docker(
build_data.project_name, tmp_dir)
- bisect_repo_manager = repo_manager.RepoManager(
- repo_url, host_src_dir, repo_name=os.path.basename(repo_path))
+ bisect_repo_manager = repo_manager.BaseRepoManager(
+ os.path.join(host_src_dir, os.path.basename(repo_path)))
commit_list = bisect_repo_manager.get_commit_list(new_commit, old_commit)
+
old_idx = len(commit_list) - 1
new_idx = 0
logging.info('Testing against new_commit (%s)', commit_list[new_idx])
@@ -166,5 +154,34 @@ def bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data): #
return Result(repo_url, commit_list[new_idx])
+def bisect(old_commit, new_commit, test_case_path, fuzz_target, build_data): # pylint: disable=too-many-locals
+ """From a commit range, this function caluclates which introduced a
+ specific error from a fuzz test_case_path.
+
+ Args:
+ old_commit: The oldest commit in the error regression range.
+ new_commit: The newest commit in the error regression range.
+ test_case_path: The file path of the test case that triggers the error
+ fuzz_target: The name of the fuzzer to be tested.
+ build_data: a class holding all of the input parameters for bisection.
+
+ Returns:
+ The commit SHA that introduced the error or None.
+
+ Raises:
+ ValueError: when a repo url can't be determine from the project.
+ """
+ result = _bisect(old_commit, new_commit, test_case_path, fuzz_target,
+ build_data)
+
+ # Clean up projects/ as _bisect may have modified it.
+ oss_fuzz_repo_manager = repo_manager.BaseRepoManager(helper.OSS_FUZZ_DIR)
+ oss_fuzz_repo_manager.git(['reset', 'projects'])
+ oss_fuzz_repo_manager.git(['checkout', 'projects'])
+ oss_fuzz_repo_manager.git(['clean', '-fxd', 'projects'])
+
+ return result
+
+
if __name__ == '__main__':
main()
diff --git a/infra/build_specified_commit.py b/infra/build_specified_commit.py
index 5a012344..23b47eaa 100644
--- a/infra/build_specified_commit.py
+++ b/infra/build_specified_commit.py
@@ -21,8 +21,10 @@ import os
import collections
import logging
import re
+import shutil
import helper
+import repo_manager
import utils
BuildData = collections.namedtuple(
@@ -49,7 +51,7 @@ def copy_src_from_docker(project_name, host_dir):
def build_fuzzers_from_commit(commit, build_repo_manager, host_src_path,
build_data):
- """Builds a OSS-Fuzz fuzzer at a specific commit SHA.
+ """Builds a OSS-Fuzz fuzzer at a specific commit SHA.
Args:
commit: The commit SHA to build the fuzzers at.
@@ -58,15 +60,51 @@ def build_fuzzers_from_commit(commit, build_repo_manager, host_src_path,
Returns:
0 on successful build or error code on failure.
"""
- build_repo_manager.checkout_commit(commit, clean=False)
- result = helper.build_fuzzers_impl(project_name=build_data.project_name,
- clean=True,
- engine=build_data.engine,
- sanitizer=build_data.sanitizer,
- architecture=build_data.architecture,
- env_to_add=None,
- source_path=host_src_path,
- mount_location=os.path.join('/src'))
+ oss_fuzz_repo_manager = repo_manager.BaseRepoManager(helper.OSS_FUZZ_DIR)
+ num_retry = 1
+
+ for i in range(num_retry + 1):
+ build_repo_manager.checkout_commit(commit, clean=False)
+ result = helper.build_fuzzers_impl(project_name=build_data.project_name,
+ clean=True,
+ engine=build_data.engine,
+ sanitizer=build_data.sanitizer,
+ architecture=build_data.architecture,
+ env_to_add=None,
+ source_path=host_src_path,
+ mount_location='/src')
+ if result == 0 or i == num_retry:
+ break
+
+ # Retry with an OSS-Fuzz builder container that's closer to the project
+ # commit date.
+ commit_date = build_repo_manager.commit_date(commit)
+ projects_dir = os.path.join('projects', build_data.project_name)
+
+ # Find first change in the projects/<PROJECT> directory before the project
+ # commit date.
+ oss_fuzz_commit, _, _ = oss_fuzz_repo_manager.git([
+ 'log', '--before=' + commit_date.isoformat(), '-n1', '--format=%H',
+ projects_dir
+ ],
+ check_result=True)
+ oss_fuzz_commit = oss_fuzz_commit.strip()
+
+ logging.info('Build failed. Retrying on earlier OSS-Fuzz commit %s.',
+ oss_fuzz_commit)
+
+ # Check out projects/<PROJECT> dir to the commit that was found.
+ oss_fuzz_repo_manager.git(['checkout', oss_fuzz_commit, projects_dir],
+ check_result=True)
+
+ # Rebuild image and re-copy src dir since things in /src could have changed.
+ if not helper.build_image_impl(build_data.project_name):
+ raise RuntimeError('Failed to rebuild image.')
+
+ shutil.rmtree(host_src_path, ignore_errors=True)
+ copy_src_from_docker(build_data.project_name,
+ os.path.dirname(host_src_path))
+
return result == 0
diff --git a/infra/repo_manager.py b/infra/repo_manager.py
index 71c5ba00..238cb3e7 100644
--- a/infra/repo_manager.py
+++ b/infra/repo_manager.py
@@ -21,6 +21,7 @@ a python API and manage the current state of the git repo.
r_man = RepoManager('https://github.com/google/oss-fuzz.git')
r_man.checkout('5668cc422c2c92d38a370545d3591039fb5bb8d4')
"""
+import datetime
import logging
import os
import shutil
@@ -28,48 +29,11 @@ import shutil
import utils
-class RepoManager:
- """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.
+class BaseRepoManager:
+ """Base repo manager."""
- 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', '')
- self.repo_dir = os.path.join(self.base_dir, self.repo_name)
-
- if not os.path.exists(self.repo_dir):
- self._clone()
-
- def _clone(self):
- """Creates a clone of the repo in the specified directory.
-
- 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 __init__(self, repo_dir):
+ self.repo_dir = repo_dir
def _is_git_repo(self):
"""Test if the current repo dir is a git repo or not.
@@ -80,6 +44,20 @@ class RepoManager:
git_path = os.path.join(self.repo_dir, '.git')
return os.path.isdir(git_path)
+ def git(self, cmd, check_result=False):
+ """Run a git command.
+
+ Args:
+ command: The git command as a list to be run.
+ check_result: Should an exception be thrown on failed command.
+
+ Returns:
+ stdout, stderr, error code.
+ """
+ return utils.execute(['git'] + cmd,
+ location=self.repo_dir,
+ check_result=check_result)
+
def commit_exists(self, commit):
"""Checks to see if a commit exists in the project repo.
@@ -92,10 +70,22 @@ class RepoManager:
if not commit.rstrip():
return False
- _, _, err_code = utils.execute(['git', 'cat-file', '-e', commit],
- self.repo_dir)
+ _, _, err_code = self.git(['cat-file', '-e', commit])
return not err_code
+ def commit_date(self, commit):
+ """Get the date of a commit.
+
+ Args:
+ commit: The commit hash.
+
+ Returns:
+ A datetime representing the date of the commit.
+ """
+ out, _, _ = self.git(['show', '-s', '--format=%ct', commit],
+ check_result=True)
+ return datetime.datetime.fromtimestamp(int(out))
+
def get_git_diff(self):
"""Gets a list of files that have changed from the repo head.
@@ -103,8 +93,7 @@ class RepoManager:
A list of changed file paths or None on Error.
"""
self.fetch_unshallow()
- out, err_msg, err_code = utils.execute(
- ['git', 'diff', '--name-only', 'origin...'], self.repo_dir)
+ out, err_msg, err_code = self.git(['diff', '--name-only', 'origin...'])
if err_code:
logging.error('Git diff failed with error message %s.', err_msg)
return None
@@ -119,9 +108,7 @@ class RepoManager:
Returns:
The current active commit SHA.
"""
- out, _, _ = utils.execute(['git', 'rev-parse', 'HEAD'],
- self.repo_dir,
- check_result=True)
+ out, _, _ = self.git(['rev-parse', 'HEAD'], check_result=True)
return out.strip('\n')
def get_commit_list(self, newest_commit, oldest_commit=None):
@@ -151,8 +138,7 @@ class RepoManager:
else:
commit_range = newest_commit
- out, _, err_code = utils.execute(['git', 'rev-list', commit_range],
- self.repo_dir)
+ out, _, err_code = self.git(['rev-list', commit_range])
commits = out.split('\n')
commits = [commit for commit in commits if commit]
if err_code or not commits:
@@ -168,9 +154,7 @@ class RepoManager:
"""Gets the current git repository history."""
shallow_file = os.path.join(self.repo_dir, '.git', 'shallow')
if os.path.exists(shallow_file):
- utils.execute(['git', 'fetch', '--unshallow'],
- self.repo_dir,
- check_result=True)
+ self.git(['fetch', '--unshallow'], check_result=True)
def checkout_pr(self, pr_ref):
"""Checks out a remote pull request.
@@ -179,12 +163,8 @@ class RepoManager:
pr_ref: The pull request reference to be checked out.
"""
self.fetch_unshallow()
- utils.execute(['git', 'fetch', 'origin', pr_ref],
- self.repo_dir,
- check_result=True)
- utils.execute(['git', 'checkout', '-f', 'FETCH_HEAD'],
- self.repo_dir,
- check_result=True)
+ self.git(['fetch', 'origin', pr_ref], check_result=True)
+ self.git(['checkout', '-f', 'FETCH_HEAD'], check_result=True)
def checkout_commit(self, commit, clean=True):
"""Checks out a specific commit from the repo.
@@ -199,11 +179,9 @@ class RepoManager:
self.fetch_unshallow()
if not self.commit_exists(commit):
raise ValueError('Commit %s does not exist in current branch' % commit)
- utils.execute(['git', 'checkout', '-f', commit],
- self.repo_dir,
- check_result=True)
+ self.git(['checkout', '-f', commit], check_result=True)
if clean:
- utils.execute(['git', 'clean', '-fxd'], self.repo_dir, check_result=True)
+ self.git(['clean', '-fxd'], check_result=True)
if self.get_current_commit() != commit:
raise RuntimeError('Error checking out commit %s' % commit)
@@ -211,3 +189,48 @@ class RepoManager:
"""Attempts to remove the git repo. """
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.
+
+ 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 not os.path.exists(self.repo_dir):
+ self._clone()
+
+ def _clone(self):
+ """Creates a clone of the repo in the specified directory.
+
+ 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)