@@ -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