Viewing files at commit b00752e49ee2Commit detailsView latest

Browse files
tests/test_large_binary_lfs.py 422 lines · 18.7 KB · python
1
from __future__ import annotations
2
3
import os
4
import shutil
5
import stat
6
import sys
7
import tempfile
8
import unittest
9
from pathlib import Path
10
from unittest import mock
11
12
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
13
14
import bench  # noqa: E402
15
from oakbench.execution import TimedCapture  # noqa: E402
16
from oakbench.rows import validate_rows  # noqa: E402
17
from oakbench.subjects import Subject  # noqa: E402
18
19
GIT = shutil.which("git")
20
GIT_LFS = shutil.which("git-lfs")
21
22
23
def make_binary_fixture(root: Path, file_names: list[str], size: int = 256) -> Path:
24
    """A minimal fixture tree shaped like ensure_fixture output (group-*/...)."""
25
    fixture = root / "fixture"
26
    for name in file_names:
27
        bench.write_pattern_file(fixture / "group-000" / name, size, f"test:{name}", True)
28
    return fixture
29
30
31
def write_git_lfs_shim(root: Path) -> Path:
32
    """A fake git-lfs on PATH: git dispatches `git lfs ...` to it, so the
33
    subprocess layer is faked without git-lfs being installed."""
34
    shim_dir = root / "shim-bin"
35
    shim_dir.mkdir()
36
    shim = shim_dir / "git-lfs"
37
    shim.write_text("#!/bin/sh\nexit 0\n")
38
    shim.chmod(shim.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
39
    return shim_dir
40
41
42
@unittest.skipIf(GIT is None, "git binary not installed")
43
class HugeBinaryGitDiffLimitTest(unittest.TestCase):
44
    def metadata(self) -> dict:
45
        return {
46
            "bench_id": "test",
47
            "profile": "large",
48
            "benchmark_track": "agent-default",
49
            "command_semantics_version": bench.COMMAND_SEMANTICS_VERSION,
50
            "timestamp_utc": "test",
51
        }
52
53
    def test_stock_git_huge_binary_diff_rows_are_explicit_skips(self) -> None:
54
        scenario = bench.Scenario("single_huge_binary_1gb", 1, 1024 * 1024 * 1024, 1, True, 1)
55
        with tempfile.TemporaryDirectory() as tmp:
56
            root = Path(tmp)
57
            fixture = make_binary_fixture(root, ["file-0000.bin"], size=256)
58
            rows = bench.run_subject_scenario(
59
                Subject(name="git", kind="git", label="Git", bin=Path(GIT)),
60
                scenario,
61
                fixture,
62
                0,
63
                root / "runs",
64
                self.metadata(),
65
                skip_diff=False,
66
                admitted_output_chars=20_000,
67
                track="agent-default",
68
                skip_determinism_probe=False,
69
                skip_remote=True,
70
            )
71
72
        by_op = {row["operation"]: row for row in rows}
73
        for op in (
74
            "diff.dirty",
75
            "diff.dirty.determinism",
76
            "diff.dirty.inforecall",
77
            "diff.full.inforecall",
78
        ):
79
            self.assertEqual(by_op[op]["returncode"], 77, op)
80
            self.assertTrue(by_op[op]["skipped"], op)
81
            self.assertIn("git_huge_binary_diff_fixture_limit", by_op[op]["skip_reason"])
82
            self.assertTrue(by_op[op]["command"])
83
        self.assertEqual(validate_rows(rows, "core"), [])
84
85
    def test_git_lfs_huge_binary_diff_is_not_preflight_skipped(self) -> None:
86
        scenario = bench.Scenario("single_huge_binary_1gb", 1, 1024 * 1024 * 1024, 1, True, 1)
87
        subject = Subject(name="git_lfs", kind="git", label="Git (lfs)", bin=Path(GIT), git_mode="lfs")
88
        self.assertIsNone(bench.git_huge_binary_diff_limit_reason(subject, scenario, "diff.dirty"))
89
90
    def test_diagnostic_probe_rows_keep_failure_stderr(self) -> None:
91
        scenario = bench.Scenario("tiny_text", 1, 1, 1, False, 1)
92
        subject = Subject(name="git", kind="git", label="Git", bin=Path(GIT))
93
        with tempfile.TemporaryDirectory() as tmp:
94
            row = bench.run_determinism_probe(
95
                subject,
96
                scenario,
97
                "diff.dirty",
98
                Path(tmp),
99
                ["/bin/sh", "-c", "echo probe-boom >&2; exit 2"],
100
                0,
101
                self.metadata(),
102
            )
103
        self.assertEqual(row["returncode"], 2)
104
        self.assertIn("probe-boom", row["stderr"])
105
106
    def test_information_probe_rows_keep_failure_stderr(self) -> None:
107
        scenario = bench.Scenario("tiny_text", 1, 1, 1, False, 1)
108
        subject = Subject(name="git", kind="git", label="Git", bin=Path(GIT))
109
110
        def fake_run_timed(*_args, **_kwargs) -> TimedCapture:
111
            return TimedCapture(
112
                elapsed_ms=1.0,
113
                returncode=2,
114
                stdout_text="",
115
                stdout_bytes=0,
116
                stdout_truncated=False,
117
                stderr_text="info-boom\n",
118
                stderr_bytes=10,
119
                stderr_truncated=False,
120
            )
121
122
        with tempfile.TemporaryDirectory() as tmp, mock.patch.object(bench, "run_timed", fake_run_timed):
123
            row = bench.run_information_probe(
124
                subject,
125
                scenario,
126
                "diff.dirty",
127
                Path(tmp),
128
                ["/bin/sh", "-c", "exit 2"],
129
                0,
130
                self.metadata(),
131
                ["group-000/file-0000.txt"],
132
            )
133
        self.assertEqual(row["returncode"], 2)
134
        self.assertIn("info-boom", row["stderr"])
135
136
137
class LargeBinaryLfsModeTest(unittest.TestCase):
138
    def subject(self, mode: str = "lfs") -> Subject:
139
        return Subject(name=f"git_{mode}", kind="git", label=f"Git ({mode})", bin=Path("git"), git_mode=mode)
140
141
    def metadata(self) -> dict:
142
        return {
143
            "bench_id": "test",
144
            "profile": "micro",
145
            "benchmark_track": "agent-default",
146
            "command_semantics_version": bench.COMMAND_SEMANTICS_VERSION,
147
            "timestamp_utc": "test",
148
        }
149
150
    def run_scenario(self, scenario: bench.Scenario, fixture: Path, run_root: Path) -> list[dict]:
151
        return bench.run_subject_scenario(
152
            self.subject(),
153
            scenario,
154
            fixture,
155
            0,
156
            run_root,
157
            self.metadata(),
158
            skip_diff=True,
159
            admitted_output_chars=20_000,
160
            track="agent-default",
161
            skip_determinism_probe=True,
162
            skip_remote=True,
163
        )
164
165
    def test_expand_git_modes_accepts_lfs(self) -> None:
166
        subjects = [Subject(name="git", kind="git", label="Git", bin=Path("git"))]
167
        expanded = bench.expand_git_modes(subjects, "lfs")
168
        self.assertEqual([subject.name for subject in expanded], ["git", "git_lfs"])
169
        self.assertEqual(expanded[1].git_mode, "lfs")
170
171
    def test_lfs_mode_skips_non_binary_scenarios(self) -> None:
172
        with tempfile.TemporaryDirectory() as tmp:
173
            rows = self.run_scenario(
174
                bench.Scenario("tiny_text", 2, 64, 1, False, 1),
175
                Path(tmp) / "unused-fixture",
176
                Path(tmp) / "runs",
177
            )
178
        self.assertEqual(len(rows), 1)
179
        row = rows[0]
180
        self.assertEqual(row["operation"], "scenario.mode_skipped")
181
        self.assertEqual(row["returncode"], 77)
182
        self.assertTrue(row["skipped"])
183
        self.assertIn("git_lfs_not_applicable", row["skip_reason"])
184
        self.assertEqual(validate_rows(rows, "core"), [])
185
186
    def test_applies_to_list_in_config_is_the_source_of_truth(self) -> None:
187
        """config/git_modes.json git_lfs.applies_to names concrete scenarios;
188
        membership decides applicability, not the binary flag alone."""
189
        listed = bench.Scenario("single_large_binary", 1, 64, 1, True, 1)
190
        applies, reason = bench.git_mode_applies_to_scenario("lfs", listed)
191
        self.assertTrue(applies)
192
        self.assertIsNone(reason)
193
194
        # Binary, but not in the applies_to list (smoke's few_large_binaries).
195
        unlisted = bench.Scenario("few_large_binaries", 4, 64, 2, True, 1)
196
        applies, reason = bench.git_mode_applies_to_scenario("lfs", unlisted)
197
        self.assertFalse(applies)
198
        self.assertIn("git_lfs_not_applicable", reason)
199
        self.assertIn("applies_to", reason)
200
201
        # Modes whose applies_to entries are fixture-class tags (no scenario
202
        # named "large-tree" exists) keep the legacy applies-everywhere
203
        # behavior until the tags are mapped to scenario names.
204
        tiny = bench.Scenario("tiny_text", 2, 64, 1, False, 1)
205
        self.assertEqual(bench.git_mode_applies_to_scenario("untracked_cache", tiny), (True, None))
206
        self.assertEqual(bench.git_mode_applies_to_scenario(None, tiny), (True, None))
207
208
    def test_lfs_applies_to_pins_large_and_multigb_binary_scenarios(self) -> None:
209
        """The git_lfs comparator is explicit scenario coverage, not a generic
210
        binary flag. This pins the active runner's large binary tiers without
211
        generating their multi-GB fixtures in unit tests."""
212
        scenarios = {scenario.name: scenario for profile in bench.PROFILES.values() for scenario in profile}
213
        expected = {
214
            "single_large_binary": 128 * 1024 * 1024,
215
            "many_large_binaries": 32 * 8 * 1024 * 1024,
216
            "single_huge_binary_1gb": 1024 * 1024 * 1024,
217
            "many_large_binaries_multigb": 128 * 32 * 1024 * 1024,
218
        }
219
220
        lfs_applies_to = set(bench.git_modes_config()["git_lfs"]["applies_to"])
221
        self.assertEqual(lfs_applies_to, set(expected))
222
        for name, total_bytes in expected.items():
223
            scenario = scenarios[name]
224
            self.assertTrue(scenario.binary, name)
225
            self.assertEqual(scenario.file_count * scenario.file_size, total_bytes)
226
            self.assertEqual(bench.git_mode_applies_to_scenario("lfs", scenario), (True, None))
227
228
        self.assertGreaterEqual(
229
            scenarios["single_huge_binary_1gb"].file_size,
230
            1024 * 1024 * 1024,
231
        )
232
        self.assertGreaterEqual(
233
            scenarios["many_large_binaries_multigb"].file_count
234
            * scenarios["many_large_binaries_multigb"].file_size,
235
            4 * 1024 * 1024 * 1024,
236
        )
237
238
    def test_binary_scenario_outside_applies_to_emits_mode_skipped_row(self) -> None:
239
        with tempfile.TemporaryDirectory() as tmp:
240
            rows = self.run_scenario(
241
                bench.Scenario("single_medium_binary", 1, 64, 1, True, 1),
242
                Path(tmp) / "unused-fixture",
243
                Path(tmp) / "runs",
244
            )
245
        self.assertEqual(len(rows), 1)
246
        row = rows[0]
247
        self.assertEqual(row["operation"], "scenario.mode_skipped")
248
        self.assertEqual(row["returncode"], 77)
249
        self.assertTrue(row["skipped"])
250
        self.assertIn("git_lfs_not_applicable", row["skip_reason"])
251
        self.assertEqual(validate_rows(rows, "core"), [])
252
253
    def test_lfs_mode_skips_binary_scenario_when_git_lfs_is_unavailable(self) -> None:
254
        with tempfile.TemporaryDirectory() as tmp, mock.patch.object(bench, "git_lfs_available", return_value=False):
255
            rows = self.run_scenario(
256
                bench.Scenario("single_huge_binary_1gb", 1, 64, 1, True, 1),
257
                Path(tmp) / "unused-fixture",
258
                Path(tmp) / "runs",
259
            )
260
        self.assertEqual(len(rows), 1)
261
        row = rows[0]
262
        self.assertEqual(row["operation"], "scenario.mode_skipped")
263
        self.assertEqual(row["returncode"], 77)
264
        self.assertTrue(row["skipped"])
265
        self.assertIn("git_lfs_unavailable", row["skip_reason"])
266
        self.assertEqual(validate_rows(rows, "core"), [])
267
268
    def test_lfs_setup_commands_track_generated_binary_files(self) -> None:
269
        self.assertEqual(
270
            bench.WIRED_GIT_MODES["lfs"],
271
            [
272
                ("lfs_install", ["lfs", "install", "--local"]),
273
                ("lfs_track", ["lfs", "track", "*.bin"]),
274
            ],
275
        )
276
277
    def test_lfs_untracked_extensions_flags_files_the_patterns_miss(self) -> None:
278
        with tempfile.TemporaryDirectory() as tmp:
279
            repo = Path(tmp) / "repo"
280
            (repo / "group-000").mkdir(parents=True)
281
            (repo / "group-000" / "file-0000.bin").write_bytes(b"covered")
282
            (repo / ".gitattributes").write_text("*.bin filter=lfs\n")
283
            (repo / ".git").mkdir()
284
            (repo / ".git" / "config").write_text("")  # never scanned
285
            self.assertEqual(bench.lfs_untracked_extensions(repo), [])
286
            (repo / "group-000" / "file-0001.dat").write_bytes(b"missed")
287
            self.assertEqual(bench.lfs_untracked_extensions(repo), ["dat"])
288
289
290
@unittest.skipIf(GIT is None, "git binary not installed")
291
class MockedLfsSuccessPathTest(unittest.TestCase):
292
    """Success path with the git subprocess layer's lfs leg faked: a git-lfs
293
    shim on PATH answers `git lfs install/track`, so the full operation
294
    sequence and row stream are observable without git-lfs installed."""
295
296
    def subject(self) -> Subject:
297
        return Subject(name="git_lfs", kind="git", label="Git (lfs)", bin=Path(GIT), git_mode="lfs")
298
299
    def metadata(self) -> dict:
300
        return {
301
            "bench_id": "test",
302
            "profile": "micro",
303
            "benchmark_track": "agent-default",
304
            "command_semantics_version": bench.COMMAND_SEMANTICS_VERSION,
305
            "timestamp_utc": "test",
306
        }
307
308
    def run_listed_scenario(self, fixture_files: list[str]) -> list[dict]:
309
        scenario = bench.Scenario("single_large_binary", len(fixture_files), 256, 1, True, 1)
310
        with tempfile.TemporaryDirectory() as tmp:
311
            root = Path(tmp)
312
            fixture = make_binary_fixture(root, fixture_files)
313
            shim_dir = write_git_lfs_shim(root)
314
            shim_path = f"{shim_dir}{os.pathsep}{os.environ.get('PATH', '')}"
315
            with mock.patch.object(bench, "git_lfs_available", return_value=True), \
316
                    mock.patch.dict(os.environ, {"PATH": shim_path}):
317
                return bench.run_subject_scenario(
318
                    self.subject(),
319
                    scenario,
320
                    fixture,
321
                    0,
322
                    root / "runs",
323
                    self.metadata(),
324
                    skip_diff=True,
325
                    admitted_output_chars=20_000,
326
                    track="agent-default",
327
                    skip_determinism_probe=True,
328
                    skip_remote=True,
329
                )
330
331
    def test_listed_scenario_runs_install_then_track_then_initial_snapshot(self) -> None:
332
        rows = self.run_listed_scenario(["file-0000.bin", "file-0001.bin"])
333
        ops = [row["operation"] for row in rows]
334
335
        # Full setup sequence in order, between repo.init and snapshot.initial.
336
        self.assertIn("repo.init", ops)
337
        self.assertIn("mode.setup.lfs_install", ops)
338
        self.assertIn("mode.setup.lfs_track", ops)
339
        self.assertIn("snapshot.initial", ops)
340
        self.assertLess(ops.index("repo.init"), ops.index("mode.setup.lfs_install"))
341
        self.assertLess(ops.index("mode.setup.lfs_install"), ops.index("mode.setup.lfs_track"))
342
        self.assertLess(ops.index("mode.setup.lfs_track"), ops.index("snapshot.initial"))
343
344
        by_op = {row["operation"]: row for row in rows}
345
        self.assertEqual(by_op["mode.setup.lfs_install"]["command"], [GIT, "lfs", "install", "--local"])
346
        self.assertEqual(by_op["mode.setup.lfs_track"]["command"], [GIT, "lfs", "track", "*.bin"])
347
        self.assertEqual(by_op["mode.setup.lfs_install"]["returncode"], 0)
348
        self.assertEqual(by_op["mode.setup.lfs_track"]["returncode"], 0)
349
350
        # Measured rows all carry the git_lfs subject; nothing was skipped.
351
        for row in rows:
352
            self.assertEqual(row["subject"], "git_lfs")
353
            self.assertEqual(row["git_mode"], "lfs")
354
            self.assertNotEqual(row["returncode"], 77, row["operation"])
355
        for op in ("status.clean", "status.dirty", "snapshot.dirty", "branch.create", "task.snapshot"):
356
            self.assertIn(op, ops)
357
        self.assertEqual(validate_rows(rows, "core"), [])
358
359
    def test_fixture_extension_outside_track_patterns_fails_setup_closed(self) -> None:
360
        """An untracked comparator is an unfair comparator (config/git_modes.json
361
        git_lfs fairness_note): a .dat fixture file no pattern covers must turn
362
        the scenario into an honest skip with the exact reason."""
363
        rows = self.run_listed_scenario(["file-0000.bin", "file-0001.dat"])
364
        ops = [row["operation"] for row in rows]
365
366
        self.assertIn("mode.setup.lfs_track", ops)  # track itself succeeded
367
        measured = [row["operation"] for row in rows if not row.get("skipped")]
368
        self.assertNotIn("snapshot.initial", measured)  # nothing measured under the unfair label
369
        abandoned = [row for row in rows if row["operation"] == "snapshot.initial"]
370
        self.assertTrue(abandoned and all(row["skip_reason"] == "aborted_after_failed_prerequisite:repo.init"
371
                                          for row in abandoned))
372
        coverage = next(row for row in rows if row["operation"] == "mode.setup.lfs_track_coverage")
373
        self.assertEqual(coverage["operation"], "mode.setup.lfs_track_coverage")
374
        self.assertEqual(coverage["returncode"], 77)
375
        self.assertTrue(coverage["skipped"])
376
        self.assertEqual(coverage["skip_reason"], "lfs_track_incomplete:dat")
377
        self.assertEqual(validate_rows(rows, "core"), [])
378
379
380
@unittest.skipUnless(GIT and GIT_LFS, "git-lfs not installed")
381
class LiveGitLfsSmokeTest(unittest.TestCase):
382
    """Real git-lfs smoke: tiny fixture, one run. Skips on hosts without
383
    git-lfs; runs the genuine install/track/snapshot path where it exists."""
384
385
    def test_real_lfs_run_emits_setup_rows_and_measured_rows(self) -> None:
386
        if not bench.git_lfs_available(Path(GIT)):
387
            self.skipTest("git lfs version failed despite git-lfs on PATH")
388
        scenario = bench.Scenario("single_large_binary", 1, 1024, 1, True, 1)
389
        with tempfile.TemporaryDirectory() as tmp:
390
            root = Path(tmp)
391
            fixture = make_binary_fixture(root, ["file-0000.bin"], size=1024)
392
            rows = bench.run_subject_scenario(
393
                Subject(name="git_lfs", kind="git", label="Git (lfs)", bin=Path(GIT), git_mode="lfs"),
394
                scenario,
395
                fixture,
396
                0,
397
                root / "runs",
398
                {
399
                    "bench_id": "test",
400
                    "profile": "micro",
401
                    "benchmark_track": "agent-default",
402
                    "command_semantics_version": bench.COMMAND_SEMANTICS_VERSION,
403
                    "timestamp_utc": "test",
404
                },
405
                skip_diff=True,
406
                admitted_output_chars=20_000,
407
                track="agent-default",
408
                skip_determinism_probe=True,
409
                skip_remote=True,
410
            )
411
        ops = [row["operation"] for row in rows]
412
        by_op = {row["operation"]: row for row in rows}
413
        self.assertEqual(by_op["mode.setup.lfs_install"]["returncode"], 0)
414
        self.assertEqual(by_op["mode.setup.lfs_track"]["returncode"], 0)
415
        self.assertIn("snapshot.initial", ops)
416
        self.assertEqual(by_op["snapshot.initial"]["returncode"], 0)
417
        self.assertTrue(all(row["subject"] == "git_lfs" for row in rows))
418
        self.assertEqual(validate_rows(rows, "core"), [])
419
420
421
if __name__ == "__main__":
422
    unittest.main()