Skip to content

Commit e826b53

Browse files
reytchisonhbrodin
andauthored
Fix CoverageBot.run_task and add test cases (#458)
* Use openlit <1.36.6 Openlit >=1.36.6 has a bug with langgraph instrumentation. Also add a test to seed-gen for when openlit is enabled * Link to issue * Fix CoverageBot.run_task and add test cases --------- Co-authored-by: Henrik Brodin <90325907+hbrodin@users.noreply.github.com>
1 parent a31b670 commit e826b53

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

fuzzer/src/buttercup/fuzzing_infra/coverage_bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,13 @@ def _sample_corpus(self, corpus: Corpus) -> Generator[tuple[str, list[str]], Non
109109

110110
yield (tmp_dir.path, remaining_files)
111111

112-
def run_task(self, task: WeightedHarness, builds: dict[BuildTypeHint, BuildOutput]) -> None: # type: ignore[invalid-method-override]
112+
def run_task(self, task: WeightedHarness, builds: dict[BuildTypeHint, list[BuildOutput]]) -> None: # type: ignore[invalid-method-override]
113113
coverage_builds = builds[BuildType.COVERAGE]
114114
if not coverage_builds:
115115
logger.error(f"No coverage build found for {task.task_id}")
116116
return
117117

118-
coverage_build = coverage_builds # builds dict has wrong type
118+
coverage_build = coverage_builds[0]
119119

120120
logger.info(f"Coverage build: {coverage_build}")
121121

fuzzer/tests/test_coverage_bot.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,142 @@ def test_submit_function_coverage_multiple_functions(coverage_bot, redis_client)
273273
# Create a set of function names for easier verification
274274
function_names = {coverage.function_name for coverage in stored_coverages}
275275
assert function_names == {"function1", "function2"}
276+
277+
278+
def test_run_task_with_coverage_build(coverage_bot):
279+
"""Test that run_task correctly handles builds dict with list[BuildOutput] values."""
280+
from buttercup.common.datastructures.msg_pb2 import BuildOutput, BuildType, WeightedHarness
281+
282+
# Create a mock WeightedHarness task
283+
task = WeightedHarness()
284+
task.task_id = "test_task_id"
285+
task.harness_name = "test_harness"
286+
task.package_name = "test_package"
287+
task.weight = 1.0
288+
289+
# Create a mock BuildOutput with the required task_dir attribute
290+
build_output = BuildOutput()
291+
build_output.task_id = "test_task_id"
292+
build_output.task_dir = "/tmp/test_task_dir"
293+
build_output.build_type = BuildType.COVERAGE
294+
295+
# The builds dict should have list[BuildOutput] as values, as per the base class signature
296+
# dict[BuildType, list[BuildOutput]]
297+
builds = {BuildType.COVERAGE: [build_output]}
298+
299+
# Mock all the dependencies that run_task uses
300+
with (
301+
patch("buttercup.fuzzing_infra.coverage_bot.ChallengeTask") as mock_challenge_task,
302+
patch("buttercup.fuzzing_infra.coverage_bot.Corpus") as mock_corpus_class,
303+
patch("buttercup.fuzzing_infra.coverage_bot.CoverageRunner") as mock_runner_class,
304+
patch("buttercup.fuzzing_infra.coverage_bot.trace"),
305+
):
306+
# Setup mock ChallengeTask
307+
mock_task_instance = MagicMock()
308+
mock_task_instance.task_meta.metadata = {}
309+
mock_task_instance.project_name = "test_project"
310+
mock_challenge_task.return_value = mock_task_instance
311+
312+
# Setup mock local task from get_rw_copy
313+
mock_local_task = MagicMock()
314+
mock_local_task.task_meta.metadata = {}
315+
mock_local_task.project_name = "test_project"
316+
mock_task_instance.get_rw_copy.return_value.__enter__.return_value = mock_local_task
317+
318+
# Setup mock Corpus
319+
mock_corpus = MagicMock()
320+
mock_corpus.path = "/tmp/corpus"
321+
mock_corpus.local_corpus_size.return_value = 10
322+
mock_corpus_class.return_value = mock_corpus
323+
324+
# Setup mock sample_corpus to return test files
325+
with patch.object(coverage_bot, "_sample_corpus") as mock_sample_corpus:
326+
mock_sample_corpus.return_value.__enter__.return_value = ("/tmp/sampled", ["file1", "file2"])
327+
328+
# Setup mock CoverageRunner
329+
mock_runner = MagicMock()
330+
mock_runner.run.return_value = [
331+
CoveredFunction(names="test_func", total_lines=100, covered_lines=50, function_paths=["path1"]),
332+
]
333+
mock_runner_class.return_value = mock_runner
334+
335+
coverage_bot.run_task(task, builds)
336+
337+
# Verify ChallengeTask was called with the correct task_dir from the build_output
338+
mock_challenge_task.assert_called_once_with(read_only_task_dir="/tmp/test_task_dir")
339+
340+
341+
def test_run_task_with_empty_coverage_builds(coverage_bot):
342+
"""Test that run_task handles empty coverage builds list gracefully."""
343+
from buttercup.common.datastructures.msg_pb2 import BuildType, WeightedHarness
344+
345+
task = WeightedHarness()
346+
task.task_id = "test_task_id"
347+
task.harness_name = "test_harness"
348+
task.package_name = "test_package"
349+
350+
# Empty list of builds
351+
builds = {BuildType.COVERAGE: []}
352+
353+
# This should return early without raising an error
354+
coverage_bot.run_task(task, builds)
355+
356+
357+
def test_run_task_with_multiple_coverage_builds(coverage_bot):
358+
"""Test that run_task uses the first build when multiple coverage builds exist."""
359+
from buttercup.common.datastructures.msg_pb2 import BuildOutput, BuildType, WeightedHarness
360+
361+
task = WeightedHarness()
362+
task.task_id = "test_task_id"
363+
task.harness_name = "test_harness"
364+
task.package_name = "test_package"
365+
task.weight = 1.0
366+
367+
# Create multiple BuildOutput objects
368+
build_output_1 = BuildOutput()
369+
build_output_1.task_id = "test_task_id"
370+
build_output_1.task_dir = "/tmp/first_task_dir"
371+
build_output_1.build_type = BuildType.COVERAGE
372+
373+
build_output_2 = BuildOutput()
374+
build_output_2.task_id = "test_task_id"
375+
build_output_2.task_dir = "/tmp/second_task_dir"
376+
build_output_2.build_type = BuildType.COVERAGE
377+
378+
# Multiple builds in the list
379+
builds = {BuildType.COVERAGE: [build_output_1, build_output_2]}
380+
381+
with (
382+
patch("buttercup.fuzzing_infra.coverage_bot.ChallengeTask") as mock_challenge_task,
383+
patch("buttercup.fuzzing_infra.coverage_bot.Corpus") as mock_corpus_class,
384+
patch("buttercup.fuzzing_infra.coverage_bot.CoverageRunner") as mock_runner_class,
385+
patch("buttercup.fuzzing_infra.coverage_bot.trace"),
386+
):
387+
mock_task_instance = MagicMock()
388+
mock_task_instance.task_meta.metadata = {}
389+
mock_task_instance.project_name = "test_project"
390+
mock_challenge_task.return_value = mock_task_instance
391+
392+
mock_local_task = MagicMock()
393+
mock_local_task.task_meta.metadata = {}
394+
mock_local_task.project_name = "test_project"
395+
mock_task_instance.get_rw_copy.return_value.__enter__.return_value = mock_local_task
396+
397+
mock_corpus = MagicMock()
398+
mock_corpus.path = "/tmp/corpus"
399+
mock_corpus.local_corpus_size.return_value = 10
400+
mock_corpus_class.return_value = mock_corpus
401+
402+
with patch.object(coverage_bot, "_sample_corpus") as mock_sample_corpus:
403+
mock_sample_corpus.return_value.__enter__.return_value = ("/tmp/sampled", ["file1"])
404+
405+
mock_runner = MagicMock()
406+
mock_runner.run.return_value = [
407+
CoveredFunction(names="test_func", total_lines=100, covered_lines=50, function_paths=["path1"]),
408+
]
409+
mock_runner_class.return_value = mock_runner
410+
411+
coverage_bot.run_task(task, builds)
412+
413+
# Verify it used the FIRST build's task_dir
414+
mock_challenge_task.assert_called_once_with(read_only_task_dir="/tmp/first_task_dir")

0 commit comments

Comments
 (0)