diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 4d75d9e5..de818c17 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -20,6 +20,13 @@ from unittest import mock import yaml +try: + import jsonschema # noqa: F401 +except ImportError: + HAS_JSONSCHEMA = False +else: + HAS_JSONSCHEMA = True + ROOT = Path(__file__).resolve().parent.parent TMP_ROOT = ROOT / "tmp" / "tests" TMP_ROOT.mkdir(parents=True, exist_ok=True) @@ -229,6 +236,7 @@ class UnreachableCitationRegressions(unittest.TestCase): check is offline and runs on every push. """ + @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def _hits(self, text: str) -> bool: import validate_schemas @@ -255,6 +263,7 @@ class UnreachableCitationRegressions(unittest.TestCase): with self.subTest(citation=good): self.assertFalse(self._hits(good)) + @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def test_the_corpus_carries_none(self): import validate_schemas @@ -1039,6 +1048,7 @@ class InstallerBoundaryRegressions(unittest.TestCase): with self.assertRaisesRegex(ValueError, "unsafe"): install._validate_manifest(manifest, "retroarch") + @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def test_target_schema_accepts_the_null_the_generator_emits(self): """The schema and generate_target_manifests must agree on null. @@ -1056,6 +1066,7 @@ class InstallerBoundaryRegressions(unittest.TestCase): document = {"windows": None, "switch": ["a5200"]} self.assertEqual(list(validator.iter_errors(document)), []) + @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def test_pack_manifests_are_read_from_inside_the_archive(self): """generate_pack writes manifest.json into the ZIP, not beside it. diff --git a/tests/test_directory_entries.py b/tests/test_directory_entries.py index 7bc522d8..814667e1 100644 --- a/tests/test_directory_entries.py +++ b/tests/test_directory_entries.py @@ -19,6 +19,13 @@ from pathlib import Path import yaml +try: + import jsonschema # noqa: F401 +except ImportError: + HAS_JSONSCHEMA = False +else: + HAS_JSONSCHEMA = True + REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) @@ -246,6 +253,7 @@ class PackCarriesADirectory(DirectoryFixture): self.assertEqual(missing, ["Fonts"]) +@unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") class ProfileContract(unittest.TestCase): """One way to say an entry is a directory, so one place reads it.""" diff --git a/tests/test_optional_dependencies.py b/tests/test_optional_dependencies.py new file mode 100644 index 00000000..86fa427a --- /dev/null +++ b/tests/test_optional_dependencies.py @@ -0,0 +1,55 @@ +"""The suite runs with only the build dependencies installed. + +jsonschema is needed by the schema validator and by CI, nowhere else. Six +tests imported it unguarded, so a contributor with stdlib and pyyaml saw +fifteen errors instead of skipped schema checks. +""" + +from __future__ import annotations + +import subprocess +import sys +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + +HIDE_AND_RUN = """ +import io +import sys +import unittest + +sys.modules["jsonschema"] = None +names = sys.argv[1:] +suite = unittest.defaultTestLoader.loadTestsFromNames(names) +result = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(suite) +for test, trace in result.errors + result.failures: + print(test.id()) + print(trace.strip().splitlines()[-1]) +sys.exit(0 if result.wasSuccessful() else 1) +""" + + +class SuiteWithoutJsonschema(unittest.TestCase): + def test_every_module_skips_instead_of_erroring(self): + names = sorted( + f"tests.{path.stem}" + for path in (REPO_ROOT / "tests").glob("test_*.py") + if path.stem != Path(__file__).stem + and any( + token in path.read_text(encoding="utf-8") + for token in ("jsonschema", "validate_schemas") + ) + ) + completed = subprocess.run( + [sys.executable, "-c", HIDE_AND_RUN, *names], + cwd=REPO_ROOT, + capture_output=True, + text=True, + timeout=600, + ) + self.assertEqual(completed.returncode, 0, completed.stdout + completed.stderr[-2000:]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_pack_counts.py b/tests/test_pack_counts.py index 66a3727f..dddfb6a5 100644 --- a/tests/test_pack_counts.py +++ b/tests/test_pack_counts.py @@ -20,6 +20,13 @@ from pathlib import Path import yaml +try: + import jsonschema # noqa: F401 +except ImportError: + HAS_JSONSCHEMA = False +else: + HAS_JSONSCHEMA = True + REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) @@ -142,6 +149,7 @@ class ManifestStatesWhatThePackHolds(PackCountFixture): self.assertEqual(manifest["total_files"], 2) self.assertEqual(manifest["pack_files"], 2 + 3 + len(builder.PACK_DOCUMENTS)) + @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def test_the_manifest_schema_accepts_the_two_figures(self): from jsonschema import Draft202012Validator