|
| 1 | +"""Official OpenStatSpec SPSS SAV/ZSAV 1.0 manifest conformance.""" |
| 2 | + |
| 3 | +import json |
| 4 | +import os |
| 5 | +from pathlib import Path |
| 6 | +from uuid import uuid4 |
| 7 | + |
| 8 | +import pytest |
| 9 | + |
| 10 | +import openstatspec |
| 11 | +from conformance import compare_sav_semantics |
| 12 | + |
| 13 | + |
| 14 | +def _specification_root() -> Path: |
| 15 | + configured = os.environ.get("OPENSTATSPEC_SPECIFICATION_DIR") |
| 16 | + candidates = [ |
| 17 | + Path(configured) if configured else None, |
| 18 | + Path(__file__).resolve().parents[2] / "specification", |
| 19 | + ] |
| 20 | + for candidate in candidates: |
| 21 | + if candidate and (candidate / "conformance/spss-sav-zsav-1.0.json").is_file(): |
| 22 | + return candidate |
| 23 | + raise RuntimeError( |
| 24 | + "The official OpenStatSpec specification checkout is required; " |
| 25 | + "set OPENSTATSPEC_SPECIFICATION_DIR." |
| 26 | + ) |
| 27 | + |
| 28 | + |
| 29 | +def _official_fixtures() -> list[tuple[str, Path]]: |
| 30 | + root = _specification_root() |
| 31 | + manifest = json.loads( |
| 32 | + (root / "conformance/spss-sav-zsav-1.0.json").read_text(encoding="utf-8") |
| 33 | + ) |
| 34 | + assert manifest["manifest_version"] == "1.0" |
| 35 | + fixtures = [] |
| 36 | + for fixture in manifest["fixtures"]: |
| 37 | + if fixture["id"] == "preflight-failure": |
| 38 | + continue |
| 39 | + source = root / "conformance" / fixture["source"] |
| 40 | + assert source.is_file(), f"Missing official fixture: {fixture['id']}" |
| 41 | + fixtures.append((fixture["id"], source)) |
| 42 | + return fixtures |
| 43 | + |
| 44 | + |
| 45 | +def _assert_round_trip( |
| 46 | + *, fixture_id: str, source: Path, database_url: str, tmp_path: Path, profile: str, |
| 47 | +) -> None: |
| 48 | + token = uuid4().hex[:10] |
| 49 | + dataset_id = f"official_{profile}_{fixture_id}_{token}".replace("-", "_") |
| 50 | + destination = tmp_path / f"{dataset_id}{source.suffix}" |
| 51 | + |
| 52 | + imported = openstatspec.import_sav( |
| 53 | + source, database_url=database_url, dataset_id=dataset_id, |
| 54 | + ) |
| 55 | + assert imported.diagnostics == () |
| 56 | + assert openstatspec.validate( |
| 57 | + database_url=database_url, dataset_id=dataset_id, |
| 58 | + )["valid"] is True |
| 59 | + |
| 60 | + exported = openstatspec.export_sav( |
| 61 | + database_url=database_url, dataset_id=dataset_id, destination=destination, |
| 62 | + ) |
| 63 | + assert exported.diagnostics == () |
| 64 | + assert compare_sav_semantics(source, destination) == { |
| 65 | + "equivalent": True, |
| 66 | + "differences": [], |
| 67 | + } |
| 68 | + |
| 69 | + |
| 70 | +@pytest.mark.parametrize(("fixture_id", "source"), _official_fixtures()) |
| 71 | +def test_official_manifest_round_trips_through_sqlite( |
| 72 | + fixture_id: str, source: Path, tmp_path: Path, |
| 73 | +) -> None: |
| 74 | + _assert_round_trip( |
| 75 | + fixture_id=fixture_id, |
| 76 | + source=source, |
| 77 | + database_url=f"sqlite:///{tmp_path / 'official.sqlite'}", |
| 78 | + tmp_path=tmp_path, |
| 79 | + profile="sqlite", |
| 80 | + ) |
| 81 | + |
| 82 | + |
| 83 | +@pytest.mark.services |
| 84 | +@pytest.mark.parametrize( |
| 85 | + ("environment_name", "profile"), |
| 86 | + [ |
| 87 | + ("OPENSTATSPEC_POSTGRES_URL", "postgresql"), |
| 88 | + ("OPENSTATSPEC_MYSQL_URL", "mysql"), |
| 89 | + ("OPENSTATSPEC_MARIADB_URL", "mariadb"), |
| 90 | + ], |
| 91 | +) |
| 92 | +@pytest.mark.parametrize(("fixture_id", "source"), _official_fixtures()) |
| 93 | +def test_official_manifest_round_trips_through_server_profiles( |
| 94 | + environment_name: str, profile: str, fixture_id: str, source: Path, tmp_path: Path, |
| 95 | +) -> None: |
| 96 | + database_url = os.environ.get(environment_name) |
| 97 | + if not database_url: |
| 98 | + pytest.skip(f"{environment_name} is not configured") |
| 99 | + _assert_round_trip( |
| 100 | + fixture_id=fixture_id, |
| 101 | + source=source, |
| 102 | + database_url=database_url, |
| 103 | + tmp_path=tmp_path, |
| 104 | + profile=profile, |
| 105 | + ) |
0 commit comments