From 215756f7584c610323f7adb2036eb27065b36c75 Mon Sep 17 00:00:00 2001 From: Seth Fitzsimmons Date: Fri, 6 Mar 2026 10:30:34 -0800 Subject: [PATCH] feat(test): pytest plugin for golden-file baselines Helpers `assert_golden` and `assert_json_schema_golden` live in `overture.schema.system.testing`. The pytest plugin at `overture.schema.system.testing.plugin` registers `--update-baselines` and the `update_baselines` fixture; consuming packages opt in via `[project.entry-points.pytest11]`. Helpers live in `system` rather than a separate test-support package: `system` is foundational, helpers depend on `system.json_schema`, and themes already depend on `system`. Mirrors `numpy.testing` / `pandas.testing`. Per-package opt-in rather than a single entry point in `system`: keeps `--update-baselines` from appearing in `core`, `codegen`, or `cli` test runs when consumed in isolation. Signed-off-by: Seth Fitzsimmons --- Makefile | 6 +- .../pyproject.toml | 5 ++ .../test_address_json_schema_baseline.py | 34 ++------- packages/overture-schema-annex/pyproject.toml | 6 +- .../test_sources_json_schema_baseline.py | 30 ++------ .../overture-schema-base-theme/pyproject.toml | 5 +- .../test_bathymetry_json_schema_baseline.py | 34 ++------- ...est_infrastructure_json_schema_baseline.py | 35 ++------- .../test_land_cover_json_schema_baseline.py | 34 ++------- .../tests/test_land_json_schema_baseline.py | 32 ++------ .../test_land_use_json_schema_baseline.py | 34 ++------- .../tests/test_water_json_schema_baseline.py | 34 ++------- .../pyproject.toml | 3 + .../test_building_json_schema_baseline.py | 34 ++------- ...test_building_part_json_schema_baseline.py | 34 ++------- .../pyproject.toml | 5 +- ...test_division_area_json_schema_baseline.py | 34 ++------- ..._division_boundary_json_schema_baseline.py | 35 ++------- .../test_division_json_schema_baseline.py | 34 ++------- .../pyproject.toml | 5 +- .../tests/test_place_json_schema_baseline.py | 34 ++------- packages/overture-schema-system/README.md | 62 +++++++++++++++ .../schema/system/testing/__init__.py | 13 ++++ .../overture/schema/system/testing/golden.py | 61 +++++++++++++++ .../overture/schema/system/testing/plugin.py | 23 ++++++ .../tests/testing/test_golden.py | 75 +++++++++++++++++++ .../pyproject.toml | 5 +- .../test_connector_json_schema_baseline.py | 34 ++------- .../test_segment_json_schema_baseline.py | 34 ++------- pyproject.toml | 5 +- uv.lock | 4 + 31 files changed, 402 insertions(+), 421 deletions(-) create mode 100644 packages/overture-schema-system/src/overture/schema/system/testing/__init__.py create mode 100644 packages/overture-schema-system/src/overture/schema/system/testing/golden.py create mode 100644 packages/overture-schema-system/src/overture/schema/system/testing/plugin.py create mode 100644 packages/overture-schema-system/tests/testing/test_golden.py diff --git a/Makefile b/Makefile index 367874a10..edea9b34d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: default uv-sync check test-all test test-only docformat doctest doctest-only mypy mypy-only lint-only reset-baseline-schemas +.PHONY: default uv-sync check test-all test test-only docformat doctest doctest-only mypy mypy-only lint-only update-baselines default: test-all @@ -56,5 +56,5 @@ lint-only: @uv run ruff check -q packages/ @uv run ruff format --check packages/ -reset-baseline-schemas: - @find . -name \*_baseline_schema.json -delete +update-baselines: + @uv run pytest --update-baselines -m baseline -q packages/ diff --git a/packages/overture-schema-addresses-theme/pyproject.toml b/packages/overture-schema-addresses-theme/pyproject.toml index e794b90d8..339b20e90 100644 --- a/packages/overture-schema-addresses-theme/pyproject.toml +++ b/packages/overture-schema-addresses-theme/pyproject.toml @@ -4,6 +4,7 @@ maintainers = [ ] dependencies = [ "overture-schema-core", + "overture-schema-system", "pydantic>=2.0", ] description = "Overture Maps addresses theme models and structures" @@ -20,6 +21,7 @@ Issues = "https://github.com/OvertureMaps/schema/issues" [tool.uv.sources] overture-schema-core = { workspace = true } +overture-schema-system = { workspace = true } [build-system] @@ -39,6 +41,9 @@ testpaths = ["tests"] [project.entry-points."overture.models"] "overture:addresses:address" = "overture.schema.addresses:Address" +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Address]] id = "416ab01c-d836-4c4f-aedc-2f30941ce94d" geometry = "POINT (-176.5637854 -43.9471955)" diff --git a/packages/overture-schema-addresses-theme/tests/test_address_json_schema_baseline.py b/packages/overture-schema-addresses-theme/tests/test_address_json_schema_baseline.py index dc97faaeb..a49ddc2f7 100644 --- a/packages/overture-schema-addresses-theme/tests/test_address_json_schema_baseline.py +++ b/packages/overture-schema-addresses-theme/tests/test_address_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for address type.""" +"""Golden JSON Schema test for Address type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.addresses import Address -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "address_baseline_schema.json" -def test_address_json_schema_baseline() -> None: - """Test that Address generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Address) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "address_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_address_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Address, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-annex/pyproject.toml b/packages/overture-schema-annex/pyproject.toml index 41e8f263e..009eeff92 100644 --- a/packages/overture-schema-annex/pyproject.toml +++ b/packages/overture-schema-annex/pyproject.toml @@ -2,7 +2,7 @@ maintainers = [ {name = "Overture Maps Schema Working Group"}, ] -dependencies = ["overture-schema-core", "pydantic>=2.0"] +dependencies = ["overture-schema-core", "overture-schema-system", "pydantic>=2.0"] description = "Add your description here" dynamic = ["version"] license = "MIT" @@ -12,6 +12,7 @@ requires-python = ">=3.10" [tool.uv.sources] overture-schema-core = { workspace = true } +overture-schema-system = { workspace = true } [project.urls] Homepage = "https://overturemaps.org" @@ -31,6 +32,9 @@ packages = ["src/overture"] [project.entry-points."overture.models"] "annex:sources" = "overture.schema.annex:Sources" +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [tool.pytest.ini_options] pythonpath = ["src"] testpaths = ["tests"] diff --git a/packages/overture-schema-annex/tests/test_sources_json_schema_baseline.py b/packages/overture-schema-annex/tests/test_sources_json_schema_baseline.py index b34383be5..3ab9e9e48 100644 --- a/packages/overture-schema-annex/tests/test_sources_json_schema_baseline.py +++ b/packages/overture-schema-annex/tests/test_sources_json_schema_baseline.py @@ -1,28 +1,14 @@ -"""Baseline JSON Schema tests for sources model.""" +"""Golden JSON Schema test for Sources type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.annex import Sources -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "sources_baseline_schema.json" -def test_sources_json_schema_baseline() -> None: - """Ensure Sources model JSON Schema matches the committed baseline.""" - schema = json_schema(Sources) - baseline_file = os.path.join( - os.path.dirname(__file__), "sources_baseline_schema.json" - ) - - if not os.path.exists(baseline_file): - with open(baseline_file, "w", encoding="utf-8") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - with open(baseline_file, encoding="utf-8") as f: - baseline_schema = json.load(f) - - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_sources_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Sources, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/pyproject.toml b/packages/overture-schema-base-theme/pyproject.toml index aa9d3ba39..ffc6863aa 100644 --- a/packages/overture-schema-base-theme/pyproject.toml +++ b/packages/overture-schema-base-theme/pyproject.toml @@ -41,7 +41,10 @@ packages = ["src/overture"] "overture:base:land_cover" = "overture.schema.base:LandCover" "overture:base:land_use" = "overture.schema.base:LandUse" "overture:base:water" = "overture.schema.base:Water" - + +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Bathymetry]] id = "5d40bd6c-db14-5492-b29f-5e25a59032bc" geometry = "MULTIPOLYGON (((-170.71296928 -76.744313428, -170.719841483 -76.757076376, -170.731061124 -76.761566192, -170.775652756 -76.76338726, -170.853616381 -76.76253958, -170.918562293 -76.755380155, -170.970490492 -76.741908984, -170.998699301 -76.729180777, -171.003188718 -76.717195533, -170.990421551 -76.703765214, -170.960397802 -76.68888982, -170.940748072 -76.674697941, -170.931472364 -76.661189576, -170.927114414 -76.637296658, -170.927674224 -76.603019188, -170.939335393 -76.574637428, -170.962097922 -76.552151379, -170.999015387 -76.535715361, -171.050087788 -76.525329373, -171.079133298 -76.50751024, -171.086151917 -76.482257963, -171.098653755 -76.462747286, -171.11663881 -76.448978211, -171.146691397 -76.437601179, -171.188811514 -76.428616191, -171.296181785 -76.4228609, -171.468802209 -76.420335306, -171.566055241 -76.41501101, -171.587940879 -76.406888013, -171.59004284 -76.387987744, -171.572361122 -76.358310204, -171.549343725 -76.334488281, -171.520990649 -76.316521976, -171.453759127 -76.301763636, -171.347649159 -76.290213262, -171.30597166 -76.267707269, -171.328726628 -76.234245658, -171.36676019 -76.195627518, -171.420072345 -76.151852851, -171.444766298 -76.12494912, -171.44084205 -76.114916326, -171.378107286 -76.099627787, -171.256562007 -76.079083503, -171.228218647 -76.058825682, -171.293077208 -76.038854322, -171.421365419 -76.023534207, -171.613083278 -76.012865337, -171.76411833 -75.99938969, -171.874470572 -75.983107266, -172.121928361 -75.958403596, -172.506491695 -75.925278679, -172.744527804 -75.899736153, -172.836036689 -75.88177602, -172.904681746 -75.862406785, -172.950462974 -75.841628448, -173.000855857 -75.830396498, -173.055860393 -75.828710933, -173.177561398 -75.810743709, -173.365958872 -75.776494827, -173.493573084 -75.759370386, -173.560404033 -75.759370386, -173.620925776 -75.77158365, -173.675138312 -75.796010178, -173.733786206 -75.808642966, -173.796869456 -75.809482015, -173.847216433 -75.805553449, -173.884827135 -75.79685727, -173.90475244 -75.789177124, -173.906992347 -75.782513013, -173.881736947 -75.76894365, -173.828986239 -75.748469035, -173.797974615 -75.732298475, -173.788702075 -75.72043197, -173.82491541 -75.701013882, -173.90661462 -75.674044211, -173.977087913 -75.656066882, -174.03633529 -75.647081894, -174.150190099 -75.643010485, -174.31865234 -75.643852655, -174.444433211 -75.652836726, -174.527532713 -75.669962696, -174.581709229 -75.687086831, -174.606962758 -75.704209131, -174.631095834 -75.708279163, -174.654108458 -75.699296928, -174.688637451 -75.699296928, -174.734682816 -75.708279163, -174.797846917 -75.708699866, -174.878129754 -75.700559037, -174.939903816 -75.70870181, -174.9831691 -75.733128185, -175.025841122 -75.746602837, -175.06791988 -75.749125768, -175.09922327 -75.755318987, -175.119751293 -75.765182495, -175.127900229 -75.775197415, -175.123670077 -75.785363749, -175.111718372 -75.791289392, -175.092045112 -75.792974345, -175.049907399 -75.780622976, -174.985305232 -75.754235285, -174.935355308 -75.74552996, -174.900057628 -75.754507001, -174.886060973 -75.766815613, -174.893365345 -75.782455795, -174.907537393 -75.791536245, -174.928577117 -75.794056963, -174.971105378 -75.818213107, -175.035122174 -75.864004677, -175.060941949 -75.892403254, -175.048564703 -75.903408839, -175.020469049 -75.909193043, -174.976654988 -75.909755867, -174.944760829 -75.90482541, -174.924786572 -75.894401673, -174.92111336 -75.881479168, -174.933741192 -75.866057897, -174.900484967 -75.857513625, -174.821344686 -75.855846351, -174.752433709 -75.839289534, -174.693752038 -75.807843172, -174.652894268 -75.780747792, -174.629860399 -75.758003392, -174.571227588 -75.745793709, -174.476995837 -75.744118743, -174.398722205 -75.751841803, -174.336406693 -75.768962888, -174.300477946 -75.783262828, -174.290935964 -75.794741623, -174.28812912 -75.812412878, -174.292057414 -75.836276591, -174.289237223 -75.852155302, -174.279668547 -75.860049012, -174.205113931 -75.879998026, -174.065573375 -75.912002343, -173.957779122 -75.924071248, -173.881731171 -75.916204739, -173.846521251 -75.926706189, -173.852149361 -75.955575598, -173.845408416 -75.979439305, -173.826298414 -75.99829731, -173.76424232 -76.018956172, -173.659240133 -76.041415889, -173.560434089 -76.057698465, -173.467824188 -76.067803901, -173.404678836 -76.077625909, -173.370998032 -76.087164489, -173.332530272 -76.106814524, -173.289275555 -76.136576014, -173.231864101 -76.154545405, -173.160295911 -76.1607227, -173.093917454 -76.17278471, -173.032728732 -76.190731436, -173.009710709 -76.205560908, -173.024863387 -76.217273124, -173.048718935 -76.225374126, -173.081277354 -76.229863912, -173.219658797 -76.237442552, -173.463863265 -76.248110046, -173.60352174 -76.25793895, -173.638634223 -76.266929265, -173.658723482 -76.274676093, -173.663789516 -76.281179435, -173.661403366 -76.289363255, -173.651565032 -76.299227554, -173.627282775 -76.313843189, -173.588556596 -76.33321016, -173.575369172 -76.355231445, -173.587720504 -76.379907046, -173.573965869 -76.402499893, -173.53410527 -76.423009985, -173.518376226 -76.437156259, -173.526778738 -76.444938715, -173.559015515 -76.446303683, -173.615086557 -76.441251162, -173.686785609 -76.421600788, -173.774112673 -76.387352563, -173.854573513 -76.372333877, -173.928168128 -76.37654473, -173.968906731 -76.383732772, -173.97678932 -76.393898005, -173.979325549 -76.410884215, -173.976515417 -76.434691403, -174.000646474 -76.454452818, -174.051718722 -76.470168462, -174.08231827 -76.482963711, -174.092445119 -76.492838563, -174.075053216 -76.514344245, -174.030142562 -76.547480757, -174.016669929 -76.575274601, -174.034635317 -76.597725777, -174.037021169 -76.62030279, -174.023827484 -76.64300564, -174.034634583 -76.661942018, -174.069442464 -76.677111923, -174.086843964 -76.690616859, -174.086839082 -76.702456825, -174.080513222 -76.712456309, -174.067866385 -76.72061531, -174.036259441 -76.725116584, -173.98569239 -76.725960131, -173.93723318 -76.720486558, -173.89088181 -76.708695864, -173.780274695 -76.695221211, -173.605411835 -76.6800626, -173.487930602 -76.662096294, -173.427830996 -76.641322294, -173.370307559 -76.630935294, -173.315360292 -76.630935294, -173.249406002 -76.637251344, -173.17244469 -76.649883444, -173.110795196 -76.653532162, -173.06445752 -76.648197497, -173.029349452 -76.637355272, -173.005470993 -76.621005486, -173.01753216 -76.605236858, -173.065532955 -76.590049388, -173.096548505 -76.576599032, -173.11057881 -76.564885791, -173.108053605 -76.552301955, -173.08897289 -76.538847523, -173.051362225 -76.527628807, -172.99522161 -76.518645807, -172.891534181 -76.516119525, -172.740299938 -76.52004996, -172.648684331 -76.524540794, -172.61668736 -76.529592027, -172.584268588 -76.541098757, -172.551428016 -76.559060982, -172.533042741 -76.576141146, -172.529112765 -76.592339249, -172.540195073 -76.604524646, -172.566289666 -76.612697339, -172.576243291 -76.621303431, -172.570055947 -76.630342924, -172.555183534 -76.636123529, -172.531626051 -76.638645245, -172.517040304 -76.643518276, -172.511426292 -76.650742621, -172.551848294 -76.672312544, -172.63830631 -76.708228042, -172.701431121 -76.728711408, -172.741222726 -76.733762641, -172.81460886 -76.72534004, -172.921589524 -76.703443605, -173.006960733 -76.697273314, -173.070722487 -76.706829166, -173.101615682 -76.719791531, -173.099640316 -76.736160408, -173.033958817 -76.759064999, -172.904571183 -76.788505304, -172.847033841 -76.810916113, -172.861346791 -76.826297424, -172.924787296 -76.856444925, -173.037355356 -76.901358615, -173.149640378 -76.935043659, -173.26164236 -76.957500057, -173.354942309 -76.968728255, -173.429540223 -76.968728255, -173.487771718 -76.964657535, -173.529636796 -76.956516094, -173.572768938 -76.955559014, -173.617168145 -76.961786296, -173.614655836 -76.97446809, -173.565232013 -76.993604396, -173.461502424 -77.006682128, -173.303467069 -77.013701287, -173.163373388 -77.02787859, -173.041221382 -77.049214037, -172.918094542 -77.059179951, -172.793992869 -77.057776334, -172.720418717 -77.044861043, -172.697372088 -77.020434079, -172.675885915 -77.003730799, -172.655960197 -76.994751205, -172.60882792 -76.987594764, -172.534489083 -76.982261476, -172.480072837 -76.983094424, -172.445579184 -76.990093609, -172.428332542 -76.998610734, -172.428332911 -77.008645799, -172.435068344 -77.018150822, -172.448538839 -77.027125803, -172.490777829 -77.039613708, -172.561785312 -77.055614535, -172.628175119 -77.080598263, -172.68994725 -77.114564892, -172.751818039 -77.133793765, -172.813787485 -77.138284883, -172.900229764 -77.131828165, -173.011144875 -77.114423613, -173.119679588 -77.128474884, -173.2258339 -77.17398198, -173.273849553 -77.202664633, -173.263726547 -77.214522842, -173.165895559 -77.239681117, -172.980356589 -77.278139457, -172.880291531 -77.312658914, -172.865700386 -77.343239487, -172.867667457 -77.371126102, -172.886192744 -77.39631876, -172.999732531 -77.429966955, -173.208286817 -77.472070689, -173.335454668 -77.509278677, -173.381236082 -77.541590921, -173.403703936 -77.570407724, -173.40285823 -77.595729086, -173.378288408 -77.634921, -173.329994472 -77.687983467, -173.241287742 -77.735563094, -173.112168219 -77.777659882, -173.054064387 -77.81089869, -173.066976248 -77.835279519, -173.063736051 -77.854657976, -173.044343797 -77.869034061, -172.890349983 -77.896435115, -172.60175461 -77.936861139, -172.376181212 -77.961986812, -172.213629791 -77.971812135, -172.023427102 -77.967320559, -171.805573145 -77.948512083, -171.581263004 -77.918894833, -171.350496677 -77.87846881, -171.217147208 -77.851799157, -171.181214596 -77.838885875, -171.160572341 -77.826074082, -171.155220441 -77.813363779, -171.178789134 -77.790158543, -171.231278422 -77.756458375, -171.27338337 -77.70988804, -171.305103978 -77.65044754, -171.293875473 -77.602346602, -171.239697854 -77.565585227, -171.168401509 -77.532887375, -171.079986438 -77.504253044, -171.028614514 -77.483042244, -171.014285737 -77.469254974, -171.016677114 -77.456576914, -171.035788644 -77.445008064, -171.086879845 -77.431646501, -171.169950715 -77.416492226, -171.216537864 -77.403175691, -171.226641293 -77.391696895, -171.228607057 -77.378968685, -171.222435157 -77.364991059, -171.168824693 -77.334840949, -171.067775664 -77.288518355, -171.000402018 -77.24121644, -170.966703754 -77.192935206, -170.894838531 -77.157002595, -170.784806349 -77.133418606, -170.725150821 -77.11627156, -170.715871945 -77.105561456, -170.710674146 -77.077210652, -170.709557424 -77.031219147, -170.697909144 -76.992502178, -170.675729304 -76.961059744, -170.654536164 -76.940848729, -170.634329723 -76.931869135, -170.581564681 -76.922044903, -170.496241038 -76.911376032, -170.429709562 -76.893409727, -170.381970254 -76.868145986, -170.285260999 -76.838950739, -170.139581798 -76.805823986, -170.061542334 -76.78431495, -170.051142608 -76.77442363, -170.076677284 -76.763148845, -170.138146365 -76.750490597, -170.192753568 -76.731526593, -170.240498896 -76.706256833, -170.315896371 -76.686462585, -170.418945993 -76.67214385, -170.498267121 -76.665405567, -170.553859754 -76.666247738, -170.609039198 -76.673409769, -170.663805452 -76.68689166, -170.695686968 -76.698414281, -170.704683743 -76.70797763, -170.710444514 -76.723277346, -170.71296928 -76.744313428), (-172.46185717 -77.485683162, -172.491725041 -77.49003391, -172.535448064 -77.490594163, -172.566986057 -77.488349711, -172.586339021 -77.483300552, -172.598540475 -77.476173053, -172.60359042 -77.466967216, -172.601627836 -77.458872071, -172.592652724 -77.451887618, -172.556765055 -77.448396429, -172.49396483 -77.448398503, -172.453726685 -77.452881992, -172.436050621 -77.461846897, -172.429868964 -77.468114837, -172.435181715 -77.47168581, -172.44584445 -77.477541919, -172.46185717 -77.485683162), (-172.812798475 -76.363628771, -172.855573928 -76.365453015, -172.885037626 -76.36040045, -172.90720433 -76.351027386, -172.92207404 -76.337333821, -172.9168827 -76.324750727, -172.89163031 -76.313278104, -172.862193885 -76.307261221, -172.828573425 -76.30670008, -172.792121028 -76.311189877, -172.752836694 -76.320730613, -172.732062811 -76.331770033, -172.729799379 -76.344308139, -172.756711267 -76.354927718, -172.812798475 -76.363628771), (-171.932998671 -76.183124002, -172.010021088 -76.180457336, -172.070931389 -76.166984091, -172.113033554 -76.150312062, -172.136327583 -76.130441248, -172.133522137 -76.111120124, -172.104617217 -76.092348689, -172.06028165 -76.080296327, -172.000515436 -76.074963039, -171.918725408 -76.076928027, -171.814911566 -76.086191292, -171.745182124 -76.097695899, -171.709537083 -76.111441849, -171.696346087 -76.126554541, -171.705609136 -76.143033974, -171.731004713 -76.156183802, -171.77253282 -76.166004024, -171.83986414 -76.174984091, -171.932998671 -76.183124002), (-173.16885937 -76.066345013, -173.199147981 -76.070696107, -173.23950163 -76.071257052, -173.269213382 -76.065813298, -173.288283234 -76.054364845, -173.2799961 -76.038973879, -173.244351978 -76.0196404, -173.207608446 -76.007588038, -173.169765504 -76.002816794, -173.139490241 -76.003094691, -173.116782658 -76.008421729, -173.104589039 -76.016938854, -173.102909386 -76.028646065, -173.111183172 -76.03940804, -173.129410398 -76.049224779, -173.148635798 -76.05820377, -173.16885937 -76.066345013)))" diff --git a/packages/overture-schema-base-theme/tests/test_bathymetry_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_bathymetry_json_schema_baseline.py index 83e29eeef..c2f1347fd 100644 --- a/packages/overture-schema-base-theme/tests/test_bathymetry_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_bathymetry_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for bathymetry type.""" +"""Golden JSON Schema test for Bathymetry type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import Bathymetry -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "bathymetry_baseline_schema.json" -def test_bathymetry_json_schema_baseline() -> None: - """Test that Bathymetry generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Bathymetry) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "bathymetry_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_bathymetry_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Bathymetry, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/tests/test_infrastructure_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_infrastructure_json_schema_baseline.py index 1eef52106..91c83a336 100644 --- a/packages/overture-schema-base-theme/tests/test_infrastructure_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_infrastructure_json_schema_baseline.py @@ -1,33 +1,14 @@ -"""Baseline JSON Schema tests for infrastructure type.""" +"""Golden JSON Schema test for Infrastructure type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import Infrastructure -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "infrastructure_baseline_schema.json" -def test_infrastructure_json_schema_baseline() -> None: - """Test that Infrastructure generates consistent JSON Schema (baseline - comparison).""" - schema = json_schema(Infrastructure) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "infrastructure_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_infrastructure_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Infrastructure, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/tests/test_land_cover_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_land_cover_json_schema_baseline.py index f16534871..5ac478991 100644 --- a/packages/overture-schema-base-theme/tests/test_land_cover_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_land_cover_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Test land_cover JSON Schema baseline generation.""" +"""Golden JSON Schema test for LandCover type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import LandCover -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "land_cover_baseline_schema.json" -def test_land_cover_json_schema_baseline() -> None: - """Test that LandCover generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(LandCover) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "land_cover_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_land_cover_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(LandCover, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/tests/test_land_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_land_json_schema_baseline.py index 1feccbbcd..b6bc8e487 100644 --- a/packages/overture-schema-base-theme/tests/test_land_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_land_json_schema_baseline.py @@ -1,30 +1,14 @@ -"""Baseline JSON Schema tests for land type.""" +"""Golden JSON Schema test for Land type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import Land -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "land_baseline_schema.json" -def test_land_json_schema_baseline() -> None: - """Test that Land generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Land) - # Path to baseline file - baseline_file = os.path.join(os.path.dirname(__file__), "land_baseline_schema.json") - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_land_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Land, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/tests/test_land_use_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_land_use_json_schema_baseline.py index 2f9dbf55d..2ab678cf0 100644 --- a/packages/overture-schema-base-theme/tests/test_land_use_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_land_use_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Test land_use JSON Schema baseline generation.""" +"""Golden JSON Schema test for LandUse type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import LandUse -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "land_use_baseline_schema.json" -def test_land_use_json_schema_baseline() -> None: - """Test that LandUse generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(LandUse) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "land_use_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_land_use_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(LandUse, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-base-theme/tests/test_water_json_schema_baseline.py b/packages/overture-schema-base-theme/tests/test_water_json_schema_baseline.py index d85b1194d..aa08406fa 100644 --- a/packages/overture-schema-base-theme/tests/test_water_json_schema_baseline.py +++ b/packages/overture-schema-base-theme/tests/test_water_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for water type.""" +"""Golden JSON Schema test for Water type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.base import Water -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "water_baseline_schema.json" -def test_water_json_schema_baseline() -> None: - """Test that Water generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Water) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "water_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_water_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Water, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-buildings-theme/pyproject.toml b/packages/overture-schema-buildings-theme/pyproject.toml index 07e418faa..880be3367 100644 --- a/packages/overture-schema-buildings-theme/pyproject.toml +++ b/packages/overture-schema-buildings-theme/pyproject.toml @@ -38,6 +38,9 @@ packages = ["src/overture"] "overture:buildings:building" = "overture.schema.buildings:Building" "overture:buildings:building_part" = "overture.schema.buildings:BuildingPart" +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Building]] id = "148f35b1-7bc1-4180-9280-10d39b13883b" geometry = "POLYGON ((-176.6435004 -43.9938042, -176.6435738 -43.9937107, -176.6437726 -43.9937913, -176.6436992 -43.9938849, -176.6435004 -43.9938042))" diff --git a/packages/overture-schema-buildings-theme/tests/test_building_json_schema_baseline.py b/packages/overture-schema-buildings-theme/tests/test_building_json_schema_baseline.py index 917cc9b15..8c30bfbcc 100644 --- a/packages/overture-schema-buildings-theme/tests/test_building_json_schema_baseline.py +++ b/packages/overture-schema-buildings-theme/tests/test_building_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for building type.""" +"""Golden JSON Schema test for Building type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.buildings.building import Building -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "building_baseline_schema.json" -def test_building_json_schema_baseline() -> None: - """Test that Building generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Building) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "building_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_building_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Building, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-buildings-theme/tests/test_building_part_json_schema_baseline.py b/packages/overture-schema-buildings-theme/tests/test_building_part_json_schema_baseline.py index 9150966be..11b3ef530 100644 --- a/packages/overture-schema-buildings-theme/tests/test_building_part_json_schema_baseline.py +++ b/packages/overture-schema-buildings-theme/tests/test_building_part_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for building part type.""" +"""Golden JSON Schema test for BuildingPart type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.buildings.building_part import BuildingPart -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "building_part_baseline_schema.json" -def test_building_part_json_schema_baseline() -> None: - """Test that BuildingPart generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(BuildingPart) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "building_part_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_building_part_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(BuildingPart, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-divisions-theme/pyproject.toml b/packages/overture-schema-divisions-theme/pyproject.toml index 0a1cd9e61..09ecd5067 100644 --- a/packages/overture-schema-divisions-theme/pyproject.toml +++ b/packages/overture-schema-divisions-theme/pyproject.toml @@ -37,7 +37,10 @@ packages = ["src/overture"] "overture:divisions:division" = "overture.schema.divisions:Division" "overture:divisions:division_area" = "overture.schema.divisions:DivisionArea" "overture:divisions:division_boundary" = "overture.schema.divisions:DivisionBoundary" - + +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Division]] id = "350e85f6-68ba-4114-9906-c2844815988b" geometry = "POINT (-175.2551522 -21.1353686)" diff --git a/packages/overture-schema-divisions-theme/tests/test_division_area_json_schema_baseline.py b/packages/overture-schema-divisions-theme/tests/test_division_area_json_schema_baseline.py index ae55d0666..53a5d3821 100644 --- a/packages/overture-schema-divisions-theme/tests/test_division_area_json_schema_baseline.py +++ b/packages/overture-schema-divisions-theme/tests/test_division_area_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for division area type.""" +"""Golden JSON Schema test for DivisionArea type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.divisions import DivisionArea -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "division_area_baseline_schema.json" -def test_division_area_json_schema_baseline() -> None: - """Test that DivisionArea generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(DivisionArea) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "division_area_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_division_area_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(DivisionArea, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-divisions-theme/tests/test_division_boundary_json_schema_baseline.py b/packages/overture-schema-divisions-theme/tests/test_division_boundary_json_schema_baseline.py index 4f43dbcb2..66733892e 100644 --- a/packages/overture-schema-divisions-theme/tests/test_division_boundary_json_schema_baseline.py +++ b/packages/overture-schema-divisions-theme/tests/test_division_boundary_json_schema_baseline.py @@ -1,33 +1,14 @@ -"""Baseline JSON Schema tests for division boundary type.""" +"""Golden JSON Schema test for DivisionBoundary type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.divisions import DivisionBoundary -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "division_boundary_baseline_schema.json" -def test_division_boundary_json_schema_baseline() -> None: - """Test that DivisionBoundary generates consistent JSON Schema (baseline - comparison).""" - schema = json_schema(DivisionBoundary) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "division_boundary_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_division_boundary_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(DivisionBoundary, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-divisions-theme/tests/test_division_json_schema_baseline.py b/packages/overture-schema-divisions-theme/tests/test_division_json_schema_baseline.py index 99ab0779d..cf91ce8f8 100644 --- a/packages/overture-schema-divisions-theme/tests/test_division_json_schema_baseline.py +++ b/packages/overture-schema-divisions-theme/tests/test_division_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for division type.""" +"""Golden JSON Schema test for Division type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.divisions import Division -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "division_baseline_schema.json" -def test_division_json_schema_baseline() -> None: - """Test that Division generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Division) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "division_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_division_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Division, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-places-theme/pyproject.toml b/packages/overture-schema-places-theme/pyproject.toml index b374835be..a1ad0604b 100644 --- a/packages/overture-schema-places-theme/pyproject.toml +++ b/packages/overture-schema-places-theme/pyproject.toml @@ -36,7 +36,10 @@ packages = ["src/overture"] [project.entry-points."overture.models"] "overture:places:place" = "overture.schema.places:Place" - + +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Place]] id = "99003ee6-e75b-4dd6-8a8a-53a5a716c50d" geometry = "POINT (-150.46875 -79.1713346)" diff --git a/packages/overture-schema-places-theme/tests/test_place_json_schema_baseline.py b/packages/overture-schema-places-theme/tests/test_place_json_schema_baseline.py index 0b142a712..6360911d3 100644 --- a/packages/overture-schema-places-theme/tests/test_place_json_schema_baseline.py +++ b/packages/overture-schema-places-theme/tests/test_place_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for place type.""" +"""Golden JSON Schema test for Place type.""" -import json -import os +from pathlib import Path +import pytest from overture.schema.places import Place -from overture.schema.system.json_schema import json_schema +from overture.schema.system.testing import assert_json_schema_golden +GOLDEN = Path(__file__).parent / "place_baseline_schema.json" -def test_place_json_schema_baseline() -> None: - """Test that Place generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Place) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "place_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_place_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Place, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-system/README.md b/packages/overture-schema-system/README.md index 238984eb0..5936933e7 100644 --- a/packages/overture-schema-system/README.md +++ b/packages/overture-schema-system/README.md @@ -115,3 +115,65 @@ class ParkBench(Identified): - **DocumentedEnum** -- base class for enumerations whose members carry their own docstrings, enabling code generation tools to produce documented output. - **Metadata** -- internal key-value store used by model constraints to attach data to classes. - **JSON Schema** -- schema generator that treats `T | None = None` as "omit when unset" rather than Pydantic's default "nullable with null default." Also handles unions of models. + +## Baseline Testing + +`overture.schema.system.testing` provides a pytest plugin for golden-file baseline tests of generated JSON Schemas. Implementers building feature packages use it to detect unintended schema drift. + +### Helpers + +- `assert_golden(actual, golden_path, *, update)` -- compare a string against a golden file. On mismatch, raises `AssertionError` with a unified diff. When `update=True`, writes `actual` to `golden_path` instead of comparing. +- `assert_json_schema_golden(model_or_union, golden_path, *, update)` -- generate the JSON Schema for a Pydantic model (or discriminated union type alias) via `overture.schema.system.json_schema` and delegate to `assert_golden`. + +### Opting In + +Activation is opt-in so the `--update-baselines` flag does not pollute pytest runs of packages that do not declare it. Add the plugin to your package's `pyproject.toml`: + +```toml +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" +``` + +The plugin registers: + +- `--update-baselines` -- pytest CLI flag. +- `update_baselines` -- bool fixture, true when the flag is passed. + +### Writing a Baseline Test + +```python +from pathlib import Path + +import pytest +from overture.schema.system.testing import assert_json_schema_golden + +from yourpackage import Mountain # the model you're locking down + +GOLDEN = Path(__file__).parent / "mountain_baseline_schema.json" + + +@pytest.mark.baseline +def test_mountain_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Mountain, GOLDEN, update=update_baselines) +``` + +Convention: the golden file lives next to the test, named `_baseline_schema.json`. + +Mark each baseline test with `@pytest.mark.baseline` so it can be selected or skipped via `pytest -m baseline` / `pytest -m "not baseline"`. Register the marker in your pyproject: + +```toml +[tool.pytest.ini_options] +markers = [ + "baseline: golden file baseline tests", +] +``` + +### Updating Baselines + +After an intentional schema change: + +```bash +pytest -m baseline --update-baselines +``` + +Inspect `git diff` on the regenerated golden files to confirm the changes are intended before committing. diff --git a/packages/overture-schema-system/src/overture/schema/system/testing/__init__.py b/packages/overture-schema-system/src/overture/schema/system/testing/__init__.py new file mode 100644 index 000000000..f3616f19c --- /dev/null +++ b/packages/overture-schema-system/src/overture/schema/system/testing/__init__.py @@ -0,0 +1,13 @@ +"""Test helpers for Overture schema implementers. + +Helpers for golden file comparison and JSON Schema baseline tests. Pure +helpers live in `golden`; pytest plumbing lives in `plugin` and is opt-in +via a `pytest11` entry point declared by consuming packages. +""" + +from .golden import assert_golden, assert_json_schema_golden + +__all__ = [ + "assert_golden", + "assert_json_schema_golden", +] diff --git a/packages/overture-schema-system/src/overture/schema/system/testing/golden.py b/packages/overture-schema-system/src/overture/schema/system/testing/golden.py new file mode 100644 index 000000000..a1927ec12 --- /dev/null +++ b/packages/overture-schema-system/src/overture/schema/system/testing/golden.py @@ -0,0 +1,61 @@ +"""Golden file comparison helpers for baseline tests.""" + +import json +from difflib import unified_diff +from pathlib import Path + +from overture.schema.system.json_schema import json_schema + + +def assert_golden(actual: str, golden_path: Path, *, update: bool) -> None: + """Compare actual output against a golden file. + + Parameters + ---------- + actual + Generated content to compare. + golden_path + Path to the golden file. + update + When True, write actual to golden_path instead of comparing. + """ + if update: + golden_path.parent.mkdir(parents=True, exist_ok=True) + golden_path.write_text(actual) + return + if not golden_path.exists(): + raise AssertionError( + f"Golden file not found: {golden_path}\n" + "Run 'make update-baselines' to generate it." + ) + expected = golden_path.read_text() + if actual != expected: + diff = "\n".join( + unified_diff( + expected.splitlines(), + actual.splitlines(), + fromfile=str(golden_path), + tofile="actual", + lineterm="", + ) + ) + raise AssertionError(f"Golden file mismatch:\n{diff}") + + +def assert_json_schema_golden( + model_or_union: object, golden_path: Path, *, update: bool +) -> None: + """Generate JSON Schema and compare against a golden file. + + Parameters + ---------- + model_or_union + Pydantic model class or union type alias. + golden_path + Path to the baseline JSON file. + update + When True, write generated schema to golden_path instead of comparing. + """ + schema = json_schema(model_or_union) + actual = json.dumps(schema, indent=2, sort_keys=True) + assert_golden(actual, golden_path, update=update) diff --git a/packages/overture-schema-system/src/overture/schema/system/testing/plugin.py b/packages/overture-schema-system/src/overture/schema/system/testing/plugin.py new file mode 100644 index 000000000..8a2ca47df --- /dev/null +++ b/packages/overture-schema-system/src/overture/schema/system/testing/plugin.py @@ -0,0 +1,23 @@ +"""Pytest plugin: ``--update-baselines`` option and ``update_baselines`` fixture. + +Activated by consuming packages via ``[project.entry-points.pytest11]`` in +their ``pyproject.toml``. Importing this module is a no-op outside pytest. +""" + +import pytest + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Register the ``--update-baselines`` CLI option.""" + parser.addoption( + "--update-baselines", + action="store_true", + default=False, + help="Regenerate baseline golden files instead of comparing", + ) + + +@pytest.fixture +def update_baselines(request: pytest.FixtureRequest) -> bool: + """Whether to regenerate baseline files instead of comparing.""" + return bool(request.config.getoption("--update-baselines")) diff --git a/packages/overture-schema-system/tests/testing/test_golden.py b/packages/overture-schema-system/tests/testing/test_golden.py new file mode 100644 index 000000000..16b292783 --- /dev/null +++ b/packages/overture-schema-system/tests/testing/test_golden.py @@ -0,0 +1,75 @@ +"""Tests for the golden file comparison helpers.""" + +import json +from pathlib import Path + +import pytest +from pydantic import BaseModel + +from overture.schema.system.testing import ( + assert_golden, + assert_json_schema_golden, +) + + +class _Minimal(BaseModel): + name: str + + +class TestAssertGolden: + """Tests for assert_golden.""" + + def test_passes_when_content_matches(self, tmp_path: Path) -> None: + golden = tmp_path / "test.txt" + golden.write_text("hello") + assert_golden("hello", golden, update=False) + + def test_fails_with_diff_on_mismatch(self, tmp_path: Path) -> None: + golden = tmp_path / "test.txt" + golden.write_text("hello") + with pytest.raises(AssertionError, match="Golden file mismatch"): + assert_golden("goodbye", golden, update=False) + + def test_diff_shows_both_sides(self, tmp_path: Path) -> None: + golden = tmp_path / "test.txt" + golden.write_text("line1\nline2") + with pytest.raises(AssertionError, match=r"(?s)-line2.*\+line3"): + assert_golden("line1\nline3", golden, update=False) + + def test_update_writes_file(self, tmp_path: Path) -> None: + golden = tmp_path / "test.txt" + assert_golden("new content", golden, update=True) + assert golden.read_text() == "new content" + + def test_update_overwrites_existing(self, tmp_path: Path) -> None: + golden = tmp_path / "test.txt" + golden.write_text("old") + assert_golden("new", golden, update=True) + assert golden.read_text() == "new" + + def test_missing_file_without_update_fails(self, tmp_path: Path) -> None: + golden = tmp_path / "nonexistent.txt" + with pytest.raises(AssertionError, match="make update-baselines"): + assert_golden("anything", golden, update=False) + + +class TestAssertJsonSchemaGolden: + """Tests for assert_json_schema_golden.""" + + def test_passes_when_schema_matches(self, tmp_path: Path) -> None: + golden = tmp_path / "minimal.json" + assert_json_schema_golden(_Minimal, golden, update=True) + assert_json_schema_golden(_Minimal, golden, update=False) + + def test_update_writes_schema(self, tmp_path: Path) -> None: + golden = tmp_path / "minimal.json" + assert_json_schema_golden(_Minimal, golden, update=True) + written = json.loads(golden.read_text()) + assert written["title"] == "_Minimal" + assert "name" in written["properties"] + + def test_fails_on_schema_mismatch(self, tmp_path: Path) -> None: + golden = tmp_path / "minimal.json" + golden.write_text('{"wrong": "schema"}') + with pytest.raises(AssertionError, match="Golden file mismatch"): + assert_json_schema_golden(_Minimal, golden, update=False) diff --git a/packages/overture-schema-transportation-theme/pyproject.toml b/packages/overture-schema-transportation-theme/pyproject.toml index 08811df39..ebd85c9dd 100644 --- a/packages/overture-schema-transportation-theme/pyproject.toml +++ b/packages/overture-schema-transportation-theme/pyproject.toml @@ -37,7 +37,10 @@ packages = ["src/overture"] [project.entry-points."overture.models"] "overture:transportation:connector" = "overture.schema.transportation:Connector" "overture:transportation:segment" = "overture.schema.transportation:Segment" - + +[project.entry-points.pytest11] +overture_baselines = "overture.schema.system.testing.plugin" + [[examples.Connector]] id = "39542bee-230f-4b91-b7e5-a9b58e0c59b1" geometry = "POINT (-176.5472979 -43.9679472)" diff --git a/packages/overture-schema-transportation-theme/tests/test_connector_json_schema_baseline.py b/packages/overture-schema-transportation-theme/tests/test_connector_json_schema_baseline.py index 132ac9977..da6a43280 100644 --- a/packages/overture-schema-transportation-theme/tests/test_connector_json_schema_baseline.py +++ b/packages/overture-schema-transportation-theme/tests/test_connector_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for connector type.""" +"""Golden JSON Schema test for Connector type.""" -import json -import os +from pathlib import Path -from overture.schema.system.json_schema import json_schema +import pytest +from overture.schema.system.testing import assert_json_schema_golden from overture.schema.transportation import Connector +GOLDEN = Path(__file__).parent / "connector_baseline_schema.json" -def test_connector_json_schema_baseline() -> None: - """Test that Connector generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Connector) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "connector_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_connector_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Connector, GOLDEN, update=update_baselines) diff --git a/packages/overture-schema-transportation-theme/tests/test_segment_json_schema_baseline.py b/packages/overture-schema-transportation-theme/tests/test_segment_json_schema_baseline.py index 529ced367..0952851c1 100644 --- a/packages/overture-schema-transportation-theme/tests/test_segment_json_schema_baseline.py +++ b/packages/overture-schema-transportation-theme/tests/test_segment_json_schema_baseline.py @@ -1,32 +1,14 @@ -"""Baseline JSON Schema tests for segment type.""" +"""Golden JSON Schema test for Segment type.""" -import json -import os +from pathlib import Path -from overture.schema.system.json_schema import json_schema +import pytest +from overture.schema.system.testing import assert_json_schema_golden from overture.schema.transportation import Segment +GOLDEN = Path(__file__).parent / "segment_baseline_schema.json" -def test_segment_json_schema_baseline() -> None: - """Test that Segment generates consistent JSON Schema (baseline comparison).""" - schema = json_schema(Segment) - # Path to baseline file - baseline_file = os.path.join( - os.path.dirname(__file__), "segment_baseline_schema.json" - ) - - # If baseline doesn't exist, create it - if not os.path.exists(baseline_file): - with open(baseline_file, "w") as f: - json.dump(schema, f, indent=2, sort_keys=True) - - # Load baseline and compare - with open(baseline_file) as f: - baseline_schema = json.load(f) - - # Compare the generated schema with the baseline - assert schema == baseline_schema, ( - "Generated JSON Schema differs from baseline. " - "If this change is intentional, delete the baseline file to regenerate it." - ) +@pytest.mark.baseline +def test_segment_json_schema(update_baselines: bool) -> None: + assert_json_schema_golden(Segment, GOLDEN, update=update_baselines) diff --git a/pyproject.toml b/pyproject.toml index a33dea1e7..a643057a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,9 @@ dev = [ ] [tool.pytest.ini_options] -verbosity_subtests = 0 +markers = [ + "baseline: golden file baseline tests", +] pythonpath = [ "packages/overture-schema-addresses-theme/tests", "packages/overture-schema-annex/tests", @@ -73,3 +75,4 @@ pythonpath = [ "packages/overture-schema-transportation-theme/tests", "packages/overture-schema/tests", ] +verbosity_subtests = 0 diff --git a/uv.lock b/uv.lock index 2829dc729..4f0f3228b 100644 --- a/uv.lock +++ b/uv.lock @@ -697,12 +697,14 @@ name = "overture-schema-addresses-theme" source = { editable = "packages/overture-schema-addresses-theme" } dependencies = [ { name = "overture-schema-core" }, + { name = "overture-schema-system" }, { name = "pydantic" }, ] [package.metadata] requires-dist = [ { name = "overture-schema-core", editable = "packages/overture-schema-core" }, + { name = "overture-schema-system", editable = "packages/overture-schema-system" }, { name = "pydantic", specifier = ">=2.0" }, ] @@ -711,12 +713,14 @@ name = "overture-schema-annex" source = { editable = "packages/overture-schema-annex" } dependencies = [ { name = "overture-schema-core" }, + { name = "overture-schema-system" }, { name = "pydantic" }, ] [package.metadata] requires-dist = [ { name = "overture-schema-core", editable = "packages/overture-schema-core" }, + { name = "overture-schema-system", editable = "packages/overture-schema-system" }, { name = "pydantic", specifier = ">=2.0" }, ]