muscleotter commited on
Commit
62b19d5
·
verified ·
1 Parent(s): 22fc39b

Add test scripts and raw results

Browse files
tests/README.md CHANGED
@@ -10,6 +10,7 @@ These are the scripts and raw results that produced the numbers in the model car
10
  | `rounds.py` | The 12 hard problems, with the failing test shown to the model for up to 3 tries. |
11
  | `extra_tests.py` | Recall in a 32,000-token prompt, and the four-step tool loop. |
12
  | `image_tests.py` | 11 image questions. The images are drawn by the script, so every answer is known. |
 
13
 
14
  ## Running them
15
 
 
10
  | `rounds.py` | The 12 hard problems, with the failing test shown to the model for up to 3 tries. |
11
  | `extra_tests.py` | Recall in a 32,000-token prompt, and the four-step tool loop. |
12
  | `image_tests.py` | 11 image questions. The images are drawn by the script, so every answer is known. |
13
+ | `harness_trials.py` | MiniMax Code fixes three small failing projects, run headless against a local server. Needs MiniMax Code installed. |
14
 
15
  ## Running them
16
 
tests/harness_trials.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Harness test: MiniMax Code fixes three small failing projects, driven headless against the model on port 8001.
2
+
3
+ Usage: python3 trials.py <label> [--passes 2]
4
+ For each task it makes a fresh copy of the project, runs `mcode-qwen exec` in it with full permissions, then runs the
5
+ project's tests itself. A trial passes only if the tests pass and the test files are unchanged.
6
+ Writes results/harness-<label>.json next to the other results.
7
+ """
8
+ import hashlib
9
+ import json
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import time
14
+ from pathlib import Path
15
+
16
+ HERE = Path(__file__).parent
17
+ RES = HERE.parent / "results"
18
+ MCODE = Path.home() / ".local/bin/mcode-qwen"
19
+ PROMPT = ("The tests in this project are failing. Find the cause and fix the code so that "
20
+ "`python3 -m unittest discover -s tests` passes. Do not change anything in the tests folder. "
21
+ "When the tests pass, stop and reply DONE.")
22
+
23
+ TASKS = {
24
+ "intervals": { # one file, two small bugs
25
+ "intervals.py": '''"""Interval helpers."""
26
+
27
+
28
+ def merge_intervals(intervals):
29
+ """Merge overlapping closed intervals. Touching intervals such as [1, 2] and [2, 3] also merge."""
30
+ merged = []
31
+ for start, end in intervals:
32
+ if merged and start < merged[-1][1]:
33
+ merged[-1][1] = max(merged[-1][1], end)
34
+ else:
35
+ merged.append([start, end])
36
+ return merged
37
+ ''',
38
+ "tests/test_intervals.py": '''import unittest
39
+ from intervals import merge_intervals
40
+
41
+
42
+ class TestMerge(unittest.TestCase):
43
+ def test_overlap(self):
44
+ self.assertEqual(merge_intervals([[1, 3], [2, 6], [8, 10]]), [[1, 6], [8, 10]])
45
+
46
+ def test_touching(self):
47
+ self.assertEqual(merge_intervals([[1, 2], [2, 3]]), [[1, 3]])
48
+
49
+ def test_unsorted(self):
50
+ self.assertEqual(merge_intervals([[5, 7], [1, 10], [2, 3]]), [[1, 10]])
51
+
52
+ def test_empty(self):
53
+ self.assertEqual(merge_intervals([]), [])
54
+
55
+
56
+ if __name__ == "__main__":
57
+ unittest.main()
58
+ ''',
59
+ },
60
+ "durations": { # a function to write from a written spec
61
+ "README.md": '''# durations
62
+
63
+ `parse_duration(text)` turns a duration such as `1h30m15s` into a number of seconds.
64
+
65
+ - Units: `d` (days), `h` (hours), `m` (minutes), `s` (seconds). Any subset, in that order.
66
+ - Spaces between parts are allowed: `2h 5m`.
67
+ - A bare number with no unit is seconds: `90`.
68
+ - Empty text, unknown units, repeated units, or units out of order raise `ValueError`.
69
+ ''',
70
+ "durations.py": '''"""Duration parsing. See README.md for the rules."""
71
+
72
+
73
+ def parse_duration(text):
74
+ raise NotImplementedError
75
+ ''',
76
+ "tests/test_durations.py": '''import unittest
77
+ from durations import parse_duration
78
+
79
+
80
+ class TestParse(unittest.TestCase):
81
+ def test_full(self):
82
+ self.assertEqual(parse_duration("1h30m15s"), 5415)
83
+
84
+ def test_days_and_spaces(self):
85
+ self.assertEqual(parse_duration("2d 3h"), 183600)
86
+ self.assertEqual(parse_duration("2h 5m"), 7500)
87
+
88
+ def test_bare_number(self):
89
+ self.assertEqual(parse_duration("90"), 90)
90
+
91
+ def test_single_units(self):
92
+ self.assertEqual(parse_duration("45m"), 2700)
93
+ self.assertEqual(parse_duration("7s"), 7)
94
+
95
+ def test_errors(self):
96
+ for bad in ("", "5x", "1h1h", "30m2h", "h", "1.5h"):
97
+ with self.assertRaises(ValueError, msg=bad):
98
+ parse_duration(bad)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ unittest.main()
103
+ ''',
104
+ },
105
+ "inventory": { # two bugs in two different files
106
+ "inventory/__init__.py": "",
107
+ "inventory/loader.py": '''"""Read stock rows from CSV text."""
108
+ import csv
109
+ import io
110
+
111
+
112
+ def load_rows(text):
113
+ """Return a list of dicts with category (str), quantity (int) and price (float)."""
114
+ rows = []
115
+ for row in csv.DictReader(io.StringIO(text)):
116
+ rows.append({"category": row["category"], "quantity": int(row["quantity"]), "price": float(row["price"])})
117
+ return rows
118
+ ''',
119
+ "inventory/report.py": '''"""Summaries over stock rows."""
120
+ from collections import defaultdict
121
+
122
+
123
+ def total_by_category(rows):
124
+ """Total stock value (quantity times price) per category, rounded to 2 places."""
125
+ totals = defaultdict(float)
126
+ for row in rows:
127
+ totals[row["category"]] += row["price"]
128
+ return {k: round(v, 2) for k, v in totals.items()}
129
+
130
+
131
+ def most_valuable(rows):
132
+ """Name of the category with the highest total value, or None when there are no rows."""
133
+ totals = total_by_category(rows)
134
+ return max(totals, key=totals.get) if totals else None
135
+ ''',
136
+ "tests/test_inventory.py": '''import unittest
137
+ from inventory.loader import load_rows
138
+ from inventory.report import most_valuable, total_by_category
139
+
140
+ CSV = """category,quantity,price
141
+ tools,3,10.00
142
+ tools ,2,5.50
143
+ paint,10,2.25
144
+ Paint,1,100.00
145
+ """
146
+
147
+
148
+ class TestInventory(unittest.TestCase):
149
+ def test_totals(self):
150
+ self.assertEqual(total_by_category(load_rows(CSV)), {"tools": 41.0, "paint": 122.5})
151
+
152
+ def test_most_valuable(self):
153
+ self.assertEqual(most_valuable(load_rows(CSV)), "paint")
154
+
155
+ def test_empty(self):
156
+ self.assertIsNone(most_valuable(load_rows("category,quantity,price\\n")))
157
+
158
+
159
+ if __name__ == "__main__":
160
+ unittest.main()
161
+ ''',
162
+ },
163
+ }
164
+
165
+
166
+ def make(task, dest):
167
+ if dest.exists():
168
+ shutil.rmtree(dest)
169
+ for rel, body in TASKS[task].items():
170
+ p = dest / rel
171
+ p.parent.mkdir(parents=True, exist_ok=True)
172
+ p.write_text(body)
173
+
174
+
175
+ def tests_hash(repo):
176
+ h = hashlib.sha256()
177
+ for p in sorted((repo / "tests").rglob("*.py")):
178
+ h.update(p.read_bytes())
179
+ return h.hexdigest()
180
+
181
+
182
+ def run_tests(repo):
183
+ p = subprocess.run([sys.executable, "-m", "unittest", "discover", "-s", "tests"], cwd=repo, capture_output=True, text=True, timeout=120)
184
+ return p.returncode == 0, (p.stderr or p.stdout)[-300:]
185
+
186
+
187
+ def main():
188
+ label = sys.argv[1]
189
+ passes = int(sys.argv[sys.argv.index("--passes") + 1]) if "--passes" in sys.argv else 2
190
+ out_path = RES / f"harness-{label}.json"
191
+ res = json.loads(out_path.read_text()) if out_path.exists() else {"label": label, "trials": []}
192
+ done = {(t["task"], t["pass_no"]) for t in res["trials"]}
193
+ for n in range(1, passes + 1):
194
+ for task in TASKS:
195
+ if (task, n) in done:
196
+ continue
197
+ repo = HERE / "work" / f"{label}-{task}-{n}"
198
+ make(task, repo)
199
+ before_ok, _ = run_tests(repo)
200
+ before = tests_hash(repo)
201
+ log = HERE / "work" / f"{label}-{task}-{n}.log"
202
+ t0 = time.time()
203
+ try:
204
+ p = subprocess.run([str(MCODE), "exec", "--cwd", str(repo), "--permission", "full", "--timeout", "8m",
205
+ "--max-steps", "40", "--prompt-mode", "coding", PROMPT],
206
+ capture_output=True, text=True, timeout=600)
207
+ code, tail = p.returncode, (p.stdout + "\n" + p.stderr)
208
+ except subprocess.TimeoutExpired:
209
+ code, tail = -1, "timed out after 600 s"
210
+ secs = time.time() - t0
211
+ log.write_text(tail)
212
+ ok, test_tail = run_tests(repo)
213
+ untouched = tests_hash(repo) == before
214
+ res["trials"].append({"task": task, "pass_no": n, "failing_before": not before_ok, "tests_pass": ok,
215
+ "tests_untouched": untouched, "passed": ok and untouched, "seconds": round(secs, 1),
216
+ "exit_code": code, "said_done": "DONE" in tail[-400:], "test_output": test_tail if not ok else ""})
217
+ out_path.write_text(json.dumps(res, indent=2))
218
+ print(f"{label} {task} #{n}: {'PASS' if ok and untouched else 'FAIL'} in {secs:.0f}s "
219
+ f"(tests pass {ok}, tests untouched {untouched}, exit {code})", flush=True)
220
+ t = res["trials"]
221
+ print(f"SUMMARY {label}: {sum(x['passed'] for x in t)} of {len(t)} trials passed, "
222
+ f"total {sum(x['seconds'] for x in t) / 60:.1f} min", flush=True)
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()
tests/results/harness-AREX-2-8bit.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "AREX-2-8bit",
3
+ "trials": [
4
+ {
5
+ "task": "intervals",
6
+ "pass_no": 1,
7
+ "failing_before": true,
8
+ "tests_pass": true,
9
+ "tests_untouched": true,
10
+ "passed": true,
11
+ "seconds": 244.2,
12
+ "exit_code": 0,
13
+ "said_done": true,
14
+ "test_output": ""
15
+ },
16
+ {
17
+ "task": "durations",
18
+ "pass_no": 1,
19
+ "failing_before": true,
20
+ "tests_pass": true,
21
+ "tests_untouched": true,
22
+ "passed": true,
23
+ "seconds": 192.6,
24
+ "exit_code": 0,
25
+ "said_done": true,
26
+ "test_output": ""
27
+ },
28
+ {
29
+ "task": "inventory",
30
+ "pass_no": 1,
31
+ "failing_before": true,
32
+ "tests_pass": true,
33
+ "tests_untouched": true,
34
+ "passed": true,
35
+ "seconds": 117.7,
36
+ "exit_code": 0,
37
+ "said_done": true,
38
+ "test_output": ""
39
+ },
40
+ {
41
+ "task": "intervals",
42
+ "pass_no": 2,
43
+ "failing_before": true,
44
+ "tests_pass": true,
45
+ "tests_untouched": true,
46
+ "passed": true,
47
+ "seconds": 101.3,
48
+ "exit_code": 0,
49
+ "said_done": true,
50
+ "test_output": ""
51
+ },
52
+ {
53
+ "task": "durations",
54
+ "pass_no": 2,
55
+ "failing_before": true,
56
+ "tests_pass": true,
57
+ "tests_untouched": true,
58
+ "passed": true,
59
+ "seconds": 222.0,
60
+ "exit_code": 0,
61
+ "said_done": true,
62
+ "test_output": ""
63
+ },
64
+ {
65
+ "task": "inventory",
66
+ "pass_no": 2,
67
+ "failing_before": true,
68
+ "tests_pass": true,
69
+ "tests_untouched": true,
70
+ "passed": true,
71
+ "seconds": 129.8,
72
+ "exit_code": 0,
73
+ "said_done": true,
74
+ "test_output": ""
75
+ }
76
+ ]
77
+ }
tests/results/harness-Qwen3.8-27B-8bit.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "Qwen3.8-27B-8bit",
3
+ "trials": [
4
+ {
5
+ "task": "intervals",
6
+ "pass_no": 1,
7
+ "failing_before": true,
8
+ "tests_pass": true,
9
+ "tests_untouched": true,
10
+ "passed": true,
11
+ "seconds": 226.6,
12
+ "exit_code": 0,
13
+ "said_done": true,
14
+ "test_output": ""
15
+ },
16
+ {
17
+ "task": "durations",
18
+ "pass_no": 1,
19
+ "failing_before": true,
20
+ "tests_pass": true,
21
+ "tests_untouched": true,
22
+ "passed": true,
23
+ "seconds": 322.8,
24
+ "exit_code": 0,
25
+ "said_done": true,
26
+ "test_output": ""
27
+ },
28
+ {
29
+ "task": "inventory",
30
+ "pass_no": 1,
31
+ "failing_before": true,
32
+ "tests_pass": true,
33
+ "tests_untouched": true,
34
+ "passed": true,
35
+ "seconds": 119.2,
36
+ "exit_code": 0,
37
+ "said_done": true,
38
+ "test_output": ""
39
+ },
40
+ {
41
+ "task": "intervals",
42
+ "pass_no": 2,
43
+ "failing_before": true,
44
+ "tests_pass": true,
45
+ "tests_untouched": true,
46
+ "passed": true,
47
+ "seconds": 115.3,
48
+ "exit_code": 0,
49
+ "said_done": true,
50
+ "test_output": ""
51
+ },
52
+ {
53
+ "task": "durations",
54
+ "pass_no": 2,
55
+ "failing_before": true,
56
+ "tests_pass": true,
57
+ "tests_untouched": true,
58
+ "passed": true,
59
+ "seconds": 149.7,
60
+ "exit_code": 0,
61
+ "said_done": true,
62
+ "test_output": ""
63
+ },
64
+ {
65
+ "task": "inventory",
66
+ "pass_no": 2,
67
+ "failing_before": true,
68
+ "tests_pass": true,
69
+ "tests_untouched": true,
70
+ "passed": true,
71
+ "seconds": 134.4,
72
+ "exit_code": 0,
73
+ "said_done": true,
74
+ "test_output": ""
75
+ }
76
+ ]
77
+ }