77shows progress and can identify which provider caused a hang.
88
99Marked as an integration test — skipped by default, run with:
10- uv run pytest tests/utilities/json_schema_type/test_real_world_schemas.py -m integration -v
10+
11+ uv run pytest tests/utilities/json_schema_type/test_real_world_schemas.py -m integration -n auto --timeout-method=thread -q
12+
13+ On a 16-core machine this takes ~3-5 minutes. The CI workflow
14+ (.github/workflows/run-schema-crash-test.yml) runs it on push/PR when
15+ relevant source files change.
16+
17+ ## Aggregate baselines
18+
19+ After each run, conftest.py compares aggregate crash counts against baseline
20+ caps in ``conftest.py``. Ratchet those caps DOWN whenever a cluster of errors
21+ is fixed — never up. See ``conftest.py`` for the current caps and comments
22+ on what was fixed.
23+
24+ ## Collecting and analysing failures
25+
26+ Set ``DUMP_SCHEMA_FAILURES`` to a directory to write per-failure JSONL records:
27+
28+ RUN_REAL_WORLD_SCHEMA_TEST=1 \\
29+ OPENAPI_DIRECTORY_PATH=/tmp/openapi-directory \\
30+ DUMP_SCHEMA_FAILURES=/tmp/schema_failures \\
31+ uv run pytest tests/utilities/json_schema_type/test_real_world_schemas.py \\
32+ -m integration -n auto --timeout-method=thread -q
33+
34+ Then cluster the results to find root causes:
35+
36+ python tests/utilities/json_schema_type/cluster_failures.py
37+
38+ See ``cluster_failures.py`` for the full fix workflow (reproduce → unit test →
39+ fix → ratchet baseline → commit).
40+
41+ ## Cloning the corpus locally
42+
43+ The test auto-clones when ``RUN_REAL_WORLD_SCHEMA_TEST=1``. To pre-clone
44+ manually (~800 MB):
45+
46+ git clone --depth 1 https://github.com/APIs-guru/openapi-directory.git /tmp/openapi-directory
47+
48+ Then point at it:
49+
50+ OPENAPI_DIRECTORY_PATH=/tmp/openapi-directory \\
51+ RUN_REAL_WORLD_SCHEMA_TEST=1 \\
52+ uv run pytest ...
1153"""
1254
1355from __future__ import annotations
@@ -223,6 +265,37 @@ def _test_provider(provider: str) -> ProviderResult:
223265 result = ProviderResult ()
224266 use_alarm = hasattr (signal , "SIGALRM" )
225267
268+ # Optional per-failure dump: when DUMP_SCHEMA_FAILURES is set to a directory,
269+ # write one JSONL record per failure so we can cluster them later.
270+ dump_dir_env = os .environ .get ("DUMP_SCHEMA_FAILURES" )
271+ dump_path : Path | None = None
272+ if dump_dir_env :
273+ dump_path = Path (dump_dir_env ) / f"{ provider } .jsonl"
274+ dump_path .parent .mkdir (parents = True , exist_ok = True )
275+ # Truncate any prior content from a previous run of this provider.
276+ dump_path .write_text ("" )
277+
278+ def _record_failure (
279+ bucket : str , schema_name : str , schema_obj : dict , exc : BaseException
280+ ) -> None :
281+ if dump_path is None :
282+ return
283+ # Cap the schema snippet so huge recursive specs don't blow up disk.
284+ try :
285+ schema_repr = json .dumps (schema_obj , default = str )[:2000 ]
286+ except Exception :
287+ schema_repr = "<unserializable>"
288+ record = {
289+ "provider" : provider ,
290+ "name" : schema_name ,
291+ "bucket" : bucket ,
292+ "error_type" : type (exc ).__name__ ,
293+ "error_msg" : str (exc )[:500 ],
294+ "schema" : schema_repr ,
295+ }
296+ with dump_path .open ("a" ) as fh :
297+ fh .write (json .dumps (record ) + "\n " )
298+
226299 for spec_file in _spec_files_for_provider (provider ):
227300 spec = _load_spec (spec_file )
228301 if spec is None :
@@ -246,16 +319,26 @@ def _test_provider(provider: str) -> ProviderResult:
246319 try :
247320 T = json_schema_to_type (schema )
248321 TypeAdapter (T )
249- except _SchemaTimeout :
322+ except _SchemaTimeout as e :
323+ if use_alarm :
324+ signal .alarm (0 )
250325 result .timeouts += 1
251- except TypeError :
326+ _record_failure ("timeouts" , _name , schema , e )
327+ except TypeError as e :
328+ if use_alarm :
329+ signal .alarm (0 )
252330 result .type_errors += 1
331+ _record_failure ("type_errors" , _name , schema , e )
253332 except Exception as e :
333+ if use_alarm :
334+ signal .alarm (0 )
254335 err_type = type (e ).__name__
255336 if "SchemaError" in err_type or "schema" in str (e ).lower ()[:50 ]:
256337 result .schema_errors += 1
338+ _record_failure ("schema_errors" , _name , schema , e )
257339 else :
258340 result .other_errors += 1
341+ _record_failure ("other_errors" , _name , schema , e )
259342 finally :
260343 if use_alarm :
261344 signal .alarm (0 )
0 commit comments