|
1 | 1 | # ABOUTME: AC-3 — task-dir shape for one (dataset, query) emission. |
2 | 2 | # ABOUTME: Asserts forbidden files (ground_truth.csv, validate.py) never reach workdir. |
3 | 3 |
|
| 4 | +import importlib.util |
4 | 5 | from pathlib import Path |
5 | 6 |
|
6 | 7 | import pytest |
@@ -74,6 +75,65 @@ def test_task_dir_layout(tmp_path: Path): |
74 | 75 | assert (task_dir / "steps" / "main" / "workdir" / "README.md").exists() |
75 | 76 |
|
76 | 77 |
|
| 78 | +def _build_common_scaffold_data_root(root: Path) -> Path: |
| 79 | + """Single-query data root whose validator imports common_scaffold.""" |
| 80 | + data_root = root / "data" |
| 81 | + scaffold_validate = data_root / "common_scaffold" / "validate" |
| 82 | + scaffold_validate.mkdir(parents=True) |
| 83 | + (scaffold_validate / "levenshtein.py").write_text( |
| 84 | + "def levenshtein(left, right):\n" |
| 85 | + " return 0 if left == right else 1\n" |
| 86 | + ) |
| 87 | + (scaffold_validate / "__pycache__").mkdir() |
| 88 | + (scaffold_validate / "__pycache__" / "ignored.pyc").write_bytes(b"cached") |
| 89 | + |
| 90 | + qdir = data_root / "query_PATENTS" |
| 91 | + qdir.mkdir(parents=True) |
| 92 | + (qdir / "db_description.txt").write_text("Synthetic affected DAB dataset.") |
| 93 | + q1 = qdir / "query1" |
| 94 | + q1.mkdir() |
| 95 | + (q1 / "query.json").write_text('{"question": "Return abc."}') |
| 96 | + (q1 / "validate.py").write_text( |
| 97 | + "from common_scaffold.validate.levenshtein import levenshtein\n\n" |
| 98 | + "def validate(answer):\n" |
| 99 | + " distance = levenshtein(answer, 'abc')\n" |
| 100 | + " return (distance == 0, f'distance={distance}')\n" |
| 101 | + ) |
| 102 | + return data_root |
| 103 | + |
| 104 | + |
| 105 | +def test_per_query_materializes_common_scaffold_for_upstream_validators( |
| 106 | + tmp_path: Path, monkeypatch |
| 107 | +) -> None: |
| 108 | + # Regression: 9 of 54 DAB validators do `from common_scaffold.validate |
| 109 | + # .levenshtein import levenshtein`. verify.py exec_module's validate.py in |
| 110 | + # the dab-agent container (no common_scaffold installed), so without the |
| 111 | + # vendored package the import raises, no reward.json is written, and harbor |
| 112 | + # reports RewardFileNotFoundError (the verifier appears never to run). The |
| 113 | + # batch path already vendored it; the per-query path must too. |
| 114 | + data_root = _build_common_scaffold_data_root(tmp_path) |
| 115 | + manifest = prepare_dataset_tasks( |
| 116 | + data_root=data_root, |
| 117 | + dataset="PATENTS", |
| 118 | + tasks_root=tmp_path / "tasks", |
| 119 | + ) |
| 120 | + tests_dir = manifest[0]["task_dir"] / "tests" |
| 121 | + |
| 122 | + assert (tests_dir / "common_scaffold" / "validate" / "levenshtein.py").exists() |
| 123 | + assert not (tests_dir / "common_scaffold" / "validate" / "__pycache__").exists() |
| 124 | + |
| 125 | + # The copied validator must import and run with /tests on sys.path |
| 126 | + # (verify.py runs as `python /tests/verify.py`, so /tests is sys.path[0]). |
| 127 | + monkeypatch.syspath_prepend(str(tests_dir)) |
| 128 | + spec = importlib.util.spec_from_file_location( |
| 129 | + "_generated_validate_per_query", tests_dir / "validate.py" |
| 130 | + ) |
| 131 | + assert spec is not None and spec.loader is not None |
| 132 | + module = importlib.util.module_from_spec(spec) |
| 133 | + spec.loader.exec_module(module) |
| 134 | + assert module.validate("abc") == (True, "distance=0") |
| 135 | + |
| 136 | + |
77 | 137 | def test_compose_bind_mount_sources_resolve_to_real_files(tmp_path: Path): |
78 | 138 | """PKG-13 T1 / AC-4 + PKG-14 AC-1: bind-mount sources in the generated |
79 | 139 | compose must point at existing files. Under PKG-14 bind mode (the default) |
|
0 commit comments