An ArcGIS layer without supportsPagination accepts resultOffset and ignores it, answering every page with the first one. The VHA profile reader advanced its offset by the page length and stopped at the announced count, so for a count that is a multiple of the page size it collected N copies of page one — and its completeness check, len(features) == candidate_count, passed. Four announced records became four stored records, two of them duplicates, filed under an official provenance. That is the substitution bounded acquisition exists to prevent, arriving through the front door. The reader now refuses a record it already collected. It fails rather than silently dropping the duplicate: a provider that cannot page is a provider whose count proves nothing, so a smaller-but-clean result would still be unverifiable. Its watercourse-name loop was worse — a bare `while True` that ended only when the provider stopped setting exceededTransferLimit, with names deduplicated by code so a stuck provider produced no visible change while the requests continued. It now refuses a repeated page body, and both loops have the page budget the sibling readers already had. Those siblings turned out to be fine. GRB and official vector already refuse a repeated page URL, bound the page count, and deduplicate on feature identity — but none of it had a test, so none of it was known to work. Exercised now, including the case where distinct URLs defeat the loop check and the budget is the only backstop. A duplicate across two genuinely different pages is kept once rather than failing, because a cursor over a changing table produces that legitimately. Also: _bash_path fell back to the raw path whenever wslpath failed, except on timeout, which propagated and reddened the suite when starting WSL took more than ten seconds under load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _bash_path(path: Path) -> str:
|
|
raw_path = str(path)
|
|
if os.name != "nt":
|
|
return raw_path
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
["bash", "-lc", f"wslpath -a {shlex.quote(raw_path)}"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
check=False,
|
|
)
|
|
except (subprocess.TimeoutExpired, OSError):
|
|
# Starting WSL can exceed ten seconds while the rest of the suite is
|
|
# running. The fallback below is what this helper already does whenever
|
|
# the conversion does not work, so a slow shell must not red the suite.
|
|
return raw_path
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
return result.stdout.strip()
|
|
return raw_path
|
|
|
|
|
|
def test_split_background_promotion_workflow_runs_split_then_split_aware_report() -> None:
|
|
script_path = ROOT / "scripts" / "run_split_background_promotion_workflow.sh"
|
|
assert script_path.exists()
|
|
source = script_path.read_text(encoding="utf-8")
|
|
|
|
assert "run_background_corpus_split_matrix.sh" in source
|
|
assert "build_detection_model_promotion_report.py" in source
|
|
assert "--background-split-summary" in source
|
|
assert "background_corpus_split_summary.json" in source
|
|
assert "PROMOTION_POSITIVE_PORTFOLIO_PATH" in source
|
|
assert "BACKGROUND_SPLIT_OUTPUT_DIR" in source
|
|
assert "PROMOTION_OUTPUT_DIR" in source
|
|
assert "--hard-negative-summary" not in source
|
|
assert "download model" not in source.lower()
|
|
assert "promote model default" not in source.lower()
|
|
|
|
|
|
def test_split_background_promotion_workflow_has_safe_preflight_mode() -> None:
|
|
source = (ROOT / "scripts" / "run_split_background_promotion_workflow.sh").read_text(encoding="utf-8")
|
|
|
|
assert "--preflight-only" in source
|
|
assert "PREFLIGHT_ONLY" in source
|
|
assert "curl -fsS" in source
|
|
assert "OPERATOR_SAMPLE_MANIFEST_PATH is required" in source
|
|
assert "pure_empty_negative" in source
|
|
assert "sparse_building_context" in source
|
|
assert "Split-background promotion preflight passed" in source
|
|
|
|
|
|
def test_split_background_preflight_derives_missing_background_categories(tmp_path: Path) -> None:
|
|
positive_portfolio = tmp_path / "positive.json"
|
|
positive_portfolio.write_text('{"items":[]}', encoding="utf-8")
|
|
manifest = tmp_path / "operator_samples_manifest.json"
|
|
manifest.write_text(
|
|
json.dumps(
|
|
{
|
|
"samples": [
|
|
{
|
|
"sample_slug": "postel_bos",
|
|
"sample_role": "background_candidate",
|
|
"reference_feature_count": 0,
|
|
},
|
|
{
|
|
"sample_slug": "kasterlee_bos",
|
|
"sample_role": "background_candidate",
|
|
"reference_feature_count": 7,
|
|
},
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
api_root = tmp_path / "api-root"
|
|
projects_endpoint = api_root / "api" / "v1" / "projects"
|
|
projects_endpoint.parent.mkdir(parents=True)
|
|
projects_endpoint.write_text('{"data":{"items":[]}}', encoding="utf-8")
|
|
|
|
command = (
|
|
f"PROMOTION_POSITIVE_PORTFOLIO_PATH={shlex.quote(_bash_path(positive_portfolio))} "
|
|
f"OPERATOR_SAMPLE_MANIFEST_PATH={shlex.quote(_bash_path(manifest))} "
|
|
f"bash scripts/run_split_background_promotion_workflow.sh --preflight-only "
|
|
f"{shlex.quote(f'file://{_bash_path(api_root)}')}"
|
|
)
|
|
result = subprocess.run(
|
|
["bash", "-lc", command],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "Split-background promotion preflight passed" in result.stdout
|
|
|
|
|
|
def test_readiness_checks_split_background_promotion_workflow_syntax() -> None:
|
|
readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8")
|
|
|
|
assert "bash -n scripts/run_split_background_promotion_workflow.sh" in readiness
|