diff --git a/fuzzer_runner/pyproject.toml b/fuzzer_runner/pyproject.toml index 242af9f1..efc21d0d 100644 --- a/fuzzer_runner/pyproject.toml +++ b/fuzzer_runner/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "common", "clusterfuzz ==2.6.0", "pydantic ~=2.11.7", + "setuptools", # Required for pkg_resources used by google-cloud-ndb (clusterfuzz dep) ] [project.urls] diff --git a/fuzzer_runner/tests/test_fuzzer.py b/fuzzer_runner/tests/test_fuzzer.py deleted file mode 100644 index 4b9095b6..00000000 --- a/fuzzer_runner/tests/test_fuzzer.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_empty(): - pass diff --git a/fuzzer_runner/tests/test_runner.py b/fuzzer_runner/tests/test_runner.py new file mode 100644 index 00000000..8b1bc4e8 --- /dev/null +++ b/fuzzer_runner/tests/test_runner.py @@ -0,0 +1,83 @@ +"""Tests for the fuzzer runner module.""" + +from unittest.mock import MagicMock, patch + +from buttercup.fuzzer_runner.runner import Conf, Runner + + +class TestRunner: + """Tests for the Runner class.""" + + @patch("buttercup.fuzzer_runner.runner.patched_temp_dir") + @patch("buttercup.fuzzer_runner.runner.scratch_cwd") + @patch("os.makedirs") + def test_run_fuzzer_initializes_engine(self, mock_makedirs, mock_scratch_cwd, mock_patched_temp_dir): + """Test run_fuzzer""" + from buttercup.common.types import FuzzConfiguration + from clusterfuzz._internal.bot.fuzzers.libFuzzer import engine as libfuzzer_engine + + mock_patched_temp_dir.return_value.__enter__ = MagicMock() + mock_patched_temp_dir.return_value.__exit__ = MagicMock(return_value=False) + mock_scratch_cwd.return_value.__enter__ = MagicMock() + mock_scratch_cwd.return_value.__exit__ = MagicMock(return_value=False) + + # Mock the engine methods that would fail without a real fuzzer binary + mock_opts = MagicMock() + mock_opts.corpus_dir = "/corpus" + mock_opts.arguments = [] + mock_opts.strategies = [] + + mock_result = MagicMock() + mock_result.crashes = [] + mock_result.logs = "test" + mock_result.stats = {} + mock_result.time_executed = 1.0 + mock_result.timed_out = False + + with ( + patch.object(libfuzzer_engine.Engine, "prepare", return_value=mock_opts), + patch.object(libfuzzer_engine.Engine, "fuzz", return_value=mock_result), + ): + conf = Conf(timeout=60) + runner = Runner(conf) + fuzzconf = FuzzConfiguration( + corpus_dir="/corpus", + target_path="/target", + engine="libfuzzer", + sanitizer="address", + ) + + result = runner.run_fuzzer(fuzzconf) + + assert result.crashes == [] + assert result.timed_out is False + + @patch("buttercup.fuzzer_runner.runner.scratch_dir") + @patch("buttercup.fuzzer_runner.runner.patched_temp_dir") + @patch("buttercup.fuzzer_runner.runner.scratch_cwd") + def test_merge_corpus_initializes_engine(self, mock_scratch_cwd, mock_patched_temp_dir, mock_scratch_dir): + """Test merge_corpus.""" + from buttercup.common.types import FuzzConfiguration + from clusterfuzz._internal.bot.fuzzers.libFuzzer import engine as libfuzzer_engine + + mock_patched_temp_dir.return_value.__enter__ = MagicMock() + mock_patched_temp_dir.return_value.__exit__ = MagicMock(return_value=False) + mock_scratch_cwd.return_value.__enter__ = MagicMock() + mock_scratch_cwd.return_value.__exit__ = MagicMock(return_value=False) + + mock_td = MagicMock() + mock_td.path = "/tmp/scratch" + mock_scratch_dir.return_value.__enter__ = MagicMock(return_value=mock_td) + mock_scratch_dir.return_value.__exit__ = MagicMock(return_value=False) + + with patch.object(libfuzzer_engine.Engine, "minimize_corpus"): + conf = Conf(timeout=60) + runner = Runner(conf) + fuzzconf = FuzzConfiguration( + corpus_dir="/corpus", + target_path="/target", + engine="libfuzzer", + sanitizer="address", + ) + + runner.merge_corpus(fuzzconf, "/output") diff --git a/fuzzer_runner/uv.lock b/fuzzer_runner/uv.lock index 4ba3cc5b..3167ebf4 100644 --- a/fuzzer_runner/uv.lock +++ b/fuzzer_runner/uv.lock @@ -264,6 +264,7 @@ dependencies = [ { name = "clusterfuzz" }, { name = "common" }, { name = "pydantic" }, + { name = "setuptools" }, ] [package.dev-dependencies] @@ -281,6 +282,7 @@ requires-dist = [ { name = "clusterfuzz", specifier = "==2.6.0" }, { name = "common", editable = "../common" }, { name = "pydantic", specifier = "~=2.11.7" }, + { name = "setuptools" }, ] [package.metadata.requires-dev] @@ -1252,6 +1254,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] +[[package]] +name = "setuptools" +version = "80.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, +] + [[package]] name = "six" version = "1.17.0"