Skip to content

Commit 19ac3e9

Browse files
authored
docs/ci: fix CONTRIBUTING.md template and usage guide typos; add tests (#29)
- Fill in project name in CONTRIBUTING.md (was placeholder "__________") - Fix typos in docs/README.md ("followoing" -> "following", "Evaluation your" -> "Evaluating your") - Fix broken README link pointing to SWE-agent/ProgramBench#installation (now relative ../README.md#quickstart) - Add CLI smoke tests for info, blob, and top-level help - Add tests for count_testcases, EvaluationResult.summarize, _can_reprocess, and filter_instances Co-authored-by: yurekami <yurekami@users.noreply.github.com>
1 parent 2bb8e84 commit 19ac3e9

5 files changed

Lines changed: 210 additions & 5 deletions

File tree

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing to __________
1+
# Contributing to ProgramBench
22
We want to make contributing to this project as easy and transparent as
33
possible.
44

@@ -35,5 +35,5 @@ outlined on that page and do not file a public issue.
3535
* ...
3636

3737
## License
38-
By contributing to __________, you agree that your contributions will be licensed
38+
By contributing to ProgramBench, you agree that your contributions will be licensed
3939
under the LICENSE file in the root directory of this source tree.

‎docs/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
## Inference
99

1010
Please use the images with tag `task_cleanroom` from `https://hub.docker.com/orgs/programbench/repositories`.
11-
E.g., to solve the task `ffmpeg__ffmpeg.360a402`, use the followoing image:
11+
E.g., to solve the task `ffmpeg__ffmpeg.360a402`, use the following image:
1212

1313
```
1414
https://hub.docker.com/repository/docker/programbench/ffmpeg_1776_ffmpeg.360a402/tags/task_cleanroom/
@@ -41,9 +41,9 @@ We expect to release our baseline system in `mini-swe-agent` this week.
4141

4242
## Evaluation
4343

44-
Evaluation your agent run is the main function performed by the `ProgramBench` repository.
44+
Evaluating your agent run is the main function performed by the `ProgramBench` repository.
4545

46-
After following the installation instructions from the [README](https://github.com/SWE-agent/ProgramBench#installation), you can run the evaluation with:
46+
After following the installation instructions from the [README](../README.md#quickstart), you can run the evaluation with:
4747

4848
```
4949
uv run programbench eval /path/to/my-amazing-agent-run

‎tests/test_cli.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""Smoke tests for CLI subcommands."""
8+
9+
from typer.testing import CliRunner
10+
11+
from programbench.cli.main import app
12+
13+
runner = CliRunner()
14+
15+
16+
def test_top_level_help():
17+
result = runner.invoke(app, ["--help"])
18+
assert result.exit_code == 0
19+
assert "eval" in result.output
20+
assert "blob" in result.output
21+
assert "info" in result.output
22+
23+
24+
def test_info_help():
25+
result = runner.invoke(app, ["info", "--help"])
26+
assert result.exit_code == 0
27+
assert "run-dir" in result.output.lower() or "run_dir" in result.output.lower()
28+
29+
30+
def test_blob_help():
31+
result = runner.invoke(app, ["blob", "--help"])
32+
assert result.exit_code == 0
33+
assert "sync" in result.output
34+
35+
36+
def test_blob_sync_help():
37+
result = runner.invoke(app, ["blob", "sync", "--help"])
38+
assert result.exit_code == 0
39+
assert "instance" in result.output.lower()

‎tests/test_eval_extras.py‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""Tests for eval functions not covered by test_eval.py."""
8+
9+
import pytest
10+
11+
from programbench.eval.eval import (
12+
EvaluationResult,
13+
TestBranchError,
14+
TestResult,
15+
count_testcases,
16+
)
17+
from programbench.eval.eval_batch import _can_reprocess
18+
19+
20+
JUNIT_XML_THREE_CASES = """\
21+
<?xml version="1.0" encoding="utf-8"?>
22+
<testsuites>
23+
<testsuite name="pytest" tests="3">
24+
<testcase classname="t" name="a" time="0.01"/>
25+
<testcase classname="t" name="b" time="0.02"/>
26+
<testcase classname="t" name="c" time="0.03"/>
27+
</testsuite>
28+
</testsuites>
29+
"""
30+
31+
32+
class TestCountTestcases:
33+
@pytest.mark.parametrize(
34+
("xml", "expected"),
35+
[
36+
("", 0),
37+
(" \n ", 0),
38+
("<not valid xml>", 0),
39+
(JUNIT_XML_THREE_CASES, 3),
40+
],
41+
)
42+
def test_counts(self, xml, expected):
43+
assert count_testcases(xml) == expected
44+
45+
46+
class TestEvaluationResultSummarize:
47+
def test_clean_run(self):
48+
result = EvaluationResult(
49+
test_results=[
50+
TestResult(name="t1", branch="b1", status="passed", extra={}),
51+
TestResult(name="t2", branch="b1", status="passed", extra={}),
52+
],
53+
solution_branch="submission",
54+
)
55+
s = result.summarize()
56+
assert "100" in s
57+
assert "2/2" in s
58+
assert "submission" in s
59+
60+
def test_with_error_code(self):
61+
result = EvaluationResult(error_code="compile_failed", error_details="gcc not found")
62+
s = result.summarize()
63+
assert "compile_failed" in s
64+
assert "gcc not found" in s
65+
66+
def test_with_branch_errors(self):
67+
result = EvaluationResult(
68+
test_results=[TestResult(name="t1", branch="b1", status="passed", extra={})],
69+
test_branch_errors={"b2": [TestBranchError(error_code="timeout", error_details="")]},
70+
)
71+
assert "b2" in result.summarize()
72+
73+
def test_with_system_errors(self):
74+
result = EvaluationResult(
75+
test_results=[TestResult(name="t1", branch="b1", status="system_error", extra={})],
76+
)
77+
assert "system_errors=1" in result.summarize()
78+
79+
def test_with_warnings(self):
80+
result = EvaluationResult(warnings=["something unexpected"])
81+
assert "warnings=1" in result.summarize()
82+
83+
84+
class TestCanReprocess:
85+
def test_error_code_is_reprocessable(self):
86+
assert _can_reprocess(EvaluationResult(error_code="compile_failed"))
87+
88+
def test_all_branches_tagged_in_log(self):
89+
result = EvaluationResult(
90+
test_branches=["b1", "b2"],
91+
log=[
92+
{"step": "results_read", "branch": "b1", "returncode": 0, "output": "<xml/>"},
93+
{"step": "results_read", "branch": "b2", "returncode": 0, "output": "<xml/>"},
94+
],
95+
)
96+
assert _can_reprocess(result)
97+
98+
def test_missing_branch_in_log_not_reprocessable(self):
99+
result = EvaluationResult(
100+
test_branches=["b1", "b2"],
101+
log=[
102+
{"step": "results_read", "branch": "b1", "returncode": 0, "output": "<xml/>"},
103+
],
104+
)
105+
assert not _can_reprocess(result)
106+
107+
def test_branch_with_error_excluded_from_check(self):
108+
result = EvaluationResult(
109+
test_branches=["b1", "b2"],
110+
test_branch_errors={"b2": [TestBranchError(error_code="fail", error_details="")]},
111+
log=[
112+
{"step": "results_read", "branch": "b1", "returncode": 0, "output": "<xml/>"},
113+
],
114+
)
115+
assert _can_reprocess(result)

‎tests/test_instance_filters.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from programbench.utils.instance_filters import filter_instances
8+
9+
10+
def _inst(iid: str, branches: dict | None = None) -> dict:
11+
return {"instance_id": iid, "branches": branches or {}}
12+
13+
14+
INSTANCES = [
15+
_inst("alpha__foo.abc", {"b1": {"tests": ["t1"]}}),
16+
_inst("beta__bar.def", {"b2": {"tests": ["t2"]}}),
17+
_inst("gamma__baz.ghi"),
18+
]
19+
20+
21+
class TestFilterInstances:
22+
def test_no_filters_returns_all(self):
23+
assert filter_instances(INSTANCES) == INSTANCES
24+
25+
def test_regex_filter(self):
26+
assert [i["instance_id"] for i in filter_instances(INSTANCES, filter_spec="alpha.*")] == ["alpha__foo.abc"]
27+
28+
def test_regex_filter_no_match(self):
29+
assert filter_instances(INSTANCES, filter_spec="nonexistent") == []
30+
31+
def test_slice_spec(self):
32+
assert [i["instance_id"] for i in filter_instances(INSTANCES, slice_spec="0:2")] == [
33+
"alpha__foo.abc",
34+
"beta__bar.def",
35+
]
36+
37+
def test_slice_from_end(self):
38+
assert [i["instance_id"] for i in filter_instances(INSTANCES, slice_spec="-1:")] == ["gamma__baz.ghi"]
39+
40+
def test_has_test_branch(self):
41+
result = filter_instances(INSTANCES, has_test_branch=True)
42+
assert [i["instance_id"] for i in result] == ["alpha__foo.abc", "beta__bar.def"]
43+
44+
def test_filter_and_slice_combined(self):
45+
result = filter_instances(INSTANCES, filter_spec="(alpha|beta).*", slice_spec="0:1")
46+
assert [i["instance_id"] for i in result] == ["alpha__foo.abc"]
47+
48+
def test_shuffle_is_deterministic(self):
49+
r1 = [i["instance_id"] for i in filter_instances(INSTANCES, shuffle=True)]
50+
r2 = [i["instance_id"] for i in filter_instances(INSTANCES, shuffle=True)]
51+
assert r1 == r2

0 commit comments

Comments
 (0)