diff --git a/docs/design/track_b_holdout_registry_mutations.json b/docs/design/track_b_holdout_registry_mutations.json index 2adda697..d62cc152 100644 --- a/docs/design/track_b_holdout_registry_mutations.json +++ b/docs/design/track_b_holdout_registry_mutations.json @@ -4,7 +4,7 @@ "admitted_scope": "The held-out data registry's rules on invented registries, temporary git repositories and the committed registry; no population, behavioral or forecast claim.", "mutation_method": "One deliberate production-line change per check; both registry test modules run in full and are expected to fail; original bytes restored before the next check. Failed test ids are pytest's own report node ids, written by a reporting plugin, not parsed from summary lines. No mutated implementation is committed.", "restored_source_sha256": { - "src/populace_dynamics/track_b/holdout_registry.py": "9ca7fc52839d637f29596f369578ff255a528114fe8956c767e4fb3d84ddac5d" + "src/populace_dynamics/track_b/holdout_registry.py": "0b31ff1446e26562b29294bd1ef48c8be7f6d6587c1f8e99881bbacf4da240cf" }, "checks": [ { @@ -893,8 +893,8 @@ }, { "mutation": "an unset event path leaks TypeError", - "old": " except (TypeError, ValueError, OSError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", - "new": " except (ValueError, OSError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", + "old": " except (TypeError, ValueError, OSError, RecursionError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", + "new": " except (ValueError, OSError, RecursionError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", "command": "PYTHONPATH=$PWD/src OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 OMP_WAIT_POLICY=PASSIVE python -m pytest -q -p no:cacheprovider -rf tests/test_track_b_holdout_registry.py tests/test_track_b_holdout_registry_committed.py", "exit_code": 1, "failed_tests": [ @@ -909,10 +909,38 @@ "command": "PYTHONPATH=$PWD/src OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 OMP_WAIT_POLICY=PASSIVE python -m pytest -q -p no:cacheprovider -rf tests/test_track_b_holdout_registry.py tests/test_track_b_holdout_registry_committed.py", "exit_code": 1, "failed_tests": [ - "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[1]", - "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[False]", - "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[None]", - "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[true]" + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[1-40]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[1-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[False-40]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[False-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[None-40]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[None-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[true-40]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[true-64]" + ] + }, + { + "mutation": "a too deeply nested event leaks RecursionError", + "old": " except (TypeError, ValueError, OSError, RecursionError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", + "new": " except (TypeError, ValueError, OSError) as error:\n raise LookupError(f\"cannot read the GitHub event: {error}\") from None", + "command": "PYTHONPATH=$PWD/src OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 OMP_WAIT_POLICY=PASSIVE python -m pytest -q -p no:cacheprovider -rf tests/test_track_b_holdout_registry.py tests/test_track_b_holdout_registry_committed.py", + "exit_code": 1, + "failed_tests": [ + "tests/test_track_b_holdout_registry.py::test_a_too_deeply_nested_event_raises[pull_request]", + "tests/test_track_b_holdout_registry.py::test_a_too_deeply_nested_event_raises[push]" + ] + }, + { + "mutation": "a 64-digit zero push boundary falls back without created", + "old": " if event.get(\"created\") is not True:\n", + "new": " if len(before) == 40 and event.get(\"created\") is not True:\n", + "command": "PYTHONPATH=$PWD/src OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 OMP_WAIT_POLICY=PASSIVE python -m pytest -q -p no:cacheprovider -rf tests/test_track_b_holdout_registry.py tests/test_track_b_holdout_registry_committed.py", + "exit_code": 1, + "failed_tests": [ + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[1-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[False-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[None-64]", + "tests/test_track_b_holdout_registry.py::test_a_zero_push_boundary_needs_a_created_branch[true-64]" ] } ] diff --git a/src/populace_dynamics/track_b/holdout_registry.py b/src/populace_dynamics/track_b/holdout_registry.py index fc3256c3..019863a1 100644 --- a/src/populace_dynamics/track_b/holdout_registry.py +++ b/src/populace_dynamics/track_b/holdout_registry.py @@ -1362,11 +1362,12 @@ def _landed_union( def _event() -> Mapping[str, Any]: """The GitHub event payload at ``GITHUB_EVENT_PATH``. Raises - ``LookupError`` when it cannot be read or is not a JSON object.""" + ``LookupError`` when it cannot be read, is nested too deeply to parse, + or is not a JSON object.""" path = os.environ.get("GITHUB_EVENT_PATH") try: payload = json.loads(Path(path).read_text(encoding="utf-8")) - except (TypeError, ValueError, OSError) as error: + except (TypeError, ValueError, OSError, RecursionError) as error: raise LookupError(f"cannot read the GitHub event: {error}") from None if not isinstance(payload, Mapping): raise LookupError("the GitHub event is not a JSON object") @@ -1389,9 +1390,9 @@ def base_commit(repository: Path) -> str: Raises ``LookupError`` when none of these resolves, and on a push whose event cannot be read, whose ``before`` commit does not - resolve, or whose all-zero ``before`` comes without ``created``: a - push is compared with its own boundary, never with a nearer commit - that would check less history.""" + resolve, or whose all-zero ``before`` comes without ``created`` set + to true: a push is compared with its own boundary, never with a + nearer commit that would check less history.""" head = _commit(repository, "HEAD") if head is None: raise LookupError(f"no HEAD in {repository}") diff --git a/tests/README-tiers.md b/tests/README-tiers.md index 6daa58ab..bfe3bb66 100644 --- a/tests/README-tiers.md +++ b/tests/README-tiers.md @@ -39,9 +39,9 @@ pytest --collect-only -q -m oracle_policyengine | tail -1 | Tier | Tests at HEAD | |---|---:| -| `unit` | 18,789 | +| `unit` | 18,795 | | `artifact` | 4,271 | | `integration_psid` | 1,471 | | `reproduction_legacy` | 520 | | `oracle_policyengine` | 308 | -| **Total** | **25,359** | +| **Total** | **25,365** | diff --git a/tests/test_track_b_holdout_registry.py b/tests/test_track_b_holdout_registry.py index 3543dbf1..96cb384f 100644 --- a/tests/test_track_b_holdout_registry.py +++ b/tests/test_track_b_holdout_registry.py @@ -2718,18 +2718,20 @@ def test_a_push_compares_with_its_before_commit( assert registry.base_registry(repository) == middle +@pytest.mark.parametrize("digits", [40, 64]) @pytest.mark.parametrize("created", [None, False, "true", 1]) def test_a_zero_push_boundary_needs_a_created_branch( - tmp_path, no_ci_env, monkeypatch, created + tmp_path, no_ci_env, monkeypatch, created, digits ): """GitHub sends an all-zero ``before`` when a push creates its branch, and says so with ``created: true``. Without that the boundary is unknown, so the push raises rather than fall back to the merge base, - which would check less history.""" + which would check less history. A SHA-256 repository's zero has 64 + digits.""" repository = init_repository(tmp_path) first = registered(["candidate"]) commit_registry(repository, first, "registry") - payload = {"before": "0" * 40} + payload = {"before": "0" * digits} if created is not None: payload["created"] = created event = tmp_path / "event.json" @@ -2758,6 +2760,35 @@ def test_an_unset_event_path_raises( registry.pull_request_head() +@pytest.mark.parametrize("event_name", ["push", "pull_request"]) +def test_a_too_deeply_nested_event_raises( + tmp_path, no_ci_env, monkeypatch, event_name +): + """An event nested too deeply for the JSON parser cannot be read: a + push and a pull request each raise ``LookupError``, never + ``RecursionError``, though the payload would otherwise resolve.""" + repository = init_repository(tmp_path) + commit_registry(repository, registered(["candidate"]), "registry") + nested = "[" * 200_000 + "]" * 200_000 + with pytest.raises(RecursionError): + json.loads(nested) + if event_name == "push": + outer = {"before": "0" * 40, "created": True} + else: + outer = {"pull_request": {"head": {"sha": "a" * 40}}} + event = tmp_path / "event.json" + event.write_text( + json.dumps(outer)[:-1] + ', "deep": ' + nested + "}", encoding="utf-8" + ) + monkeypatch.setenv("GITHUB_EVENT_NAME", event_name) + monkeypatch.setenv("GITHUB_EVENT_PATH", str(event)) + with pytest.raises(LookupError): + if event_name == "push": + registry.base_commit(repository) + else: + registry.pull_request_head() + + @pytest.mark.parametrize( "payload", [ diff --git a/tests/tier_counts.json b/tests/tier_counts.json index cab2876f..d0756a4a 100644 --- a/tests/tier_counts.json +++ b/tests/tier_counts.json @@ -1,7 +1,7 @@ { "schema_version": 1, "counts": { - "unit": 18789, + "unit": 18795, "artifact": 4271, "integration_psid": 1471, "reproduction_legacy": 520,