# 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. """Tests the functionality of the cifuzz module.""" import os import sys import tempfile import unittest from unittest import mock # pylint: disable=wrong-import-position INFRA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(INFRA_DIR) OSS_FUZZ_DIR = os.path.dirname(INFRA_DIR) import cifuzz import test_helpers # NOTE: This integration test relies on # https://github.com/google/oss-fuzz/tree/master/projects/example project. EXAMPLE_PROJECT = 'example' # Location of files used for testing. TEST_FILES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files') # An example fuzzer that triggers an crash. # Binary is a copy of the example project's do_stuff_fuzzer and can be # generated by running "python3 infra/helper.py build_fuzzers example". EXAMPLE_CRASH_FUZZER = 'example_crash_fuzzer' # An example fuzzer that does not trigger a crash. # Binary is a modified version of example project's do_stuff_fuzzer. It is # created by removing the bug in my_api.cpp. EXAMPLE_NOCRASH_FUZZER = 'example_nocrash_fuzzer' # A fuzzer to be built in build_fuzzers integration tests. EXAMPLE_BUILD_FUZZER = 'do_stuff_fuzzer' # pylint: disable=no-self-use class BuildFuzzersTest(unittest.TestCase): """Unit tests for build_fuzzers.""" @mock.patch('build_specified_commit.detect_main_repo', return_value=('example.com', '/path')) @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, _, __, ___): """Tests that the CIFUZZ env var is set.""" with tempfile.TemporaryDirectory() as tmp_dir: cifuzz.build_fuzzers(EXAMPLE_PROJECT, EXAMPLE_PROJECT, tmp_dir, pr_ref='refs/pull/1757/merge') docker_run_command = mocked_docker_run.call_args_list[0][0][0] def command_has_env_var_arg(command, env_var_arg): for idx, element in enumerate(command): if idx == 0: continue if element == env_var_arg and command[idx - 1] == '-e': return True return False self.assertTrue(command_has_env_var_arg(docker_run_command, 'CIFUZZ=True')) class InternalGithubBuilderTest(unittest.TestCase): """Tests for building OSS-Fuzz projects on GitHub actions.""" PROJECT_NAME = 'myproject' PROJECT_REPO_NAME = 'myproject' SANITIZER = 'address' COMMIT_SHA = 'fake' PR_REF = 'fake' def _create_builder(self, tmp_dir): """Creates an InternalGithubBuilder and returns it.""" return cifuzz.InternalGithubBuilder(self.PROJECT_NAME, self.PROJECT_REPO_NAME, tmp_dir, self.SANITIZER, self.COMMIT_SHA, self.PR_REF) @mock.patch('repo_manager._clone', side_effect=None) @mock.patch('cifuzz.checkout_specified_commit', side_effect=None) def test_correct_host_repo_path(self, _, __): """Tests that the correct self.host_repo_path is set by build_image_and_checkout_src. Specifically, we want the name of the directory the repo is in to match the name used in the docker image/container, so that it will replace the host's copy properly.""" image_repo_path = '/src/repo_dir' with tempfile.TemporaryDirectory() as tmp_dir, mock.patch( 'build_specified_commit.detect_main_repo', return_value=('inferred_url', image_repo_path)): builder = self._create_builder(tmp_dir) builder.build_image_and_checkout_src() self.assertEqual(os.path.basename(builder.host_repo_path), os.path.basename(image_repo_path)) @unittest.skipIf(not os.getenv('INTEGRATION_TESTS'), 'INTEGRATION_TESTS=1 not set') 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: out_path = os.path.join(tmp_dir, 'out') os.mkdir(out_path) self.assertTrue( cifuzz.build_fuzzers( EXAMPLE_PROJECT, 'oss-fuzz', tmp_dir, commit_sha='0b95fe1039ed7c38fea1f97078316bfc1030c523')) self.assertTrue( os.path.exists(os.path.join(out_path, EXAMPLE_BUILD_FUZZER))) def test_valid_pull_request(self): """Tests building fuzzers with valid pull request.""" with tempfile.TemporaryDirectory() as tmp_dir: out_path = os.path.join(tmp_dir, 'out') os.mkdir(out_path) self.assertTrue( cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz', tmp_dir, pr_ref='refs/pull/1757/merge')) self.assertTrue( os.path.exists(os.path.join(out_path, EXAMPLE_BUILD_FUZZER))) def test_invalid_pull_request(self): """Tests building fuzzers with invalid pull request.""" with tempfile.TemporaryDirectory() as tmp_dir: out_path = os.path.join(tmp_dir, 'out') os.mkdir(out_path) self.assertTrue( cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz', tmp_dir, pr_ref='ref-1/merge')) def test_invalid_project_name(self): """Tests building fuzzers with invalid project name.""" with tempfile.TemporaryDirectory() as tmp_dir: self.assertFalse( cifuzz.build_fuzzers( 'not_a_valid_project', 'oss-fuzz', tmp_dir, commit_sha='0b95fe1039ed7c38fea1f97078316bfc1030c523')) def test_invalid_repo_name(self): """Tests building fuzzers with invalid repo name.""" with tempfile.TemporaryDirectory() as tmp_dir: self.assertFalse( cifuzz.build_fuzzers( EXAMPLE_PROJECT, 'not-real-repo', tmp_dir, commit_sha='0b95fe1039ed7c38fea1f97078316bfc1030c523')) def test_invalid_commit_sha(self): """Tests building fuzzers with invalid commit SHA.""" with tempfile.TemporaryDirectory() as tmp_dir: with self.assertRaises(AssertionError): cifuzz.build_fuzzers(EXAMPLE_PROJECT, 'oss-fuzz', tmp_dir, commit_sha='') def test_invalid_workspace(self): """Tests building fuzzers with invalid workspace.""" self.assertFalse( cifuzz.build_fuzzers( EXAMPLE_PROJECT, 'oss-fuzz', 'not/a/dir', commit_sha='0b95fe1039ed7c38fea1f97078316bfc1030c523', )) class CheckFuzzerBuildTest(unittest.TestCase): """Tests the check_fuzzer_build function in the cifuzz module.""" def test_correct_fuzzer_build(self): """Checks check_fuzzer_build function returns True for valid fuzzers.""" test_fuzzer_dir = os.path.join(TEST_FILES_PATH, 'out') self.assertTrue(cifuzz.check_fuzzer_build(test_fuzzer_dir)) def test_not_a_valid_fuzz_path(self): """Tests that False is returned when a bad path is given.""" self.assertFalse(cifuzz.check_fuzzer_build('not/a/valid/path')) def test_not_a_valid_fuzzer(self): """Checks a directory that exists but does not have fuzzers is False.""" self.assertFalse(cifuzz.check_fuzzer_build(TEST_FILES_PATH)) @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 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, allowed_broken_targets_percentage='0') self.assertIn('-e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0', ' '.join(mocked_docker_run.call_args[0][0])) @unittest.skip('Test is too long to be run with presubmit.') class BuildSantizerIntegrationTest(unittest.TestCase): """Integration tests for the build_fuzzers. Note: This test relies on "curl" being an OSS-Fuzz project.""" def test_valid_project_curl_memory(self): """Tests that MSAN can be detected from project.yaml""" with tempfile.TemporaryDirectory() as tmp_dir: self.assertTrue( cifuzz.build_fuzzers('curl', 'curl', tmp_dir, pr_ref='fake_pr', sanitizer='memory')) def test_valid_project_curl_undefined(self): """Test that UBSAN can be detected from project.yaml""" with tempfile.TemporaryDirectory() as tmp_dir: self.assertTrue( cifuzz.build_fuzzers('curl', 'curl', tmp_dir, pr_ref='fake_pr', sanitizer='undefined')) if __name__ == '__main__': unittest.main()