diff options
-rw-r--r-- | jsonschema/tests/__init__.py | 0 | ||||
-rw-r--r-- | jsonschema/tests/compat.py | 15 | ||||
-rw-r--r-- | jsonschema/tests/test_format.py | 58 | ||||
-rw-r--r-- | jsonschema/tests/test_jsonschema_test_suite.py | 236 | ||||
-rw-r--r-- | jsonschema/tests/test_validators.py (renamed from test_jsonschema.py) | 302 | ||||
-rw-r--r-- | tox.ini | 5 |
6 files changed, 316 insertions, 300 deletions
diff --git a/jsonschema/tests/__init__.py b/jsonschema/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/jsonschema/tests/__init__.py diff --git a/jsonschema/tests/compat.py b/jsonschema/tests/compat.py new file mode 100644 index 0000000..b37483f --- /dev/null +++ b/jsonschema/tests/compat.py @@ -0,0 +1,15 @@ +import sys + + +if sys.version_info[:2] < (2, 7): # pragma: no cover + import unittest2 as unittest +else: + import unittest + +try: + from unittest import mock +except ImportError: + import mock + + +# flake8: noqa diff --git a/jsonschema/tests/test_format.py b/jsonschema/tests/test_format.py new file mode 100644 index 0000000..e7576b4 --- /dev/null +++ b/jsonschema/tests/test_format.py @@ -0,0 +1,58 @@ +from jsonschema.tests.compat import mock, unittest + +from jsonschema import FormatError, ValidationError, FormatChecker +from jsonschema.validators import Draft4Validator + + +class TestFormatChecker(unittest.TestCase): + def setUp(self): + self.fn = mock.Mock() + + def test_it_can_validate_no_formats(self): + checker = FormatChecker(formats=()) + self.assertFalse(checker.checkers) + + def test_it_raises_a_key_error_for_unknown_formats(self): + with self.assertRaises(KeyError): + FormatChecker(formats=["o noes"]) + + def test_it_can_register_cls_checkers(self): + with mock.patch.dict(FormatChecker.checkers, clear=True): + FormatChecker.cls_checks("new")(self.fn) + self.assertEqual(FormatChecker.checkers, {"new" : (self.fn, ())}) + + def test_it_can_register_checkers(self): + checker = FormatChecker() + checker.checks("new")(self.fn) + self.assertEqual( + checker.checkers, + dict(FormatChecker.checkers, new=(self.fn, ())) + ) + + def test_it_catches_registered_errors(self): + checker = FormatChecker() + cause = self.fn.side_effect = ValueError() + + checker.checks("foo", raises=ValueError)(self.fn) + + with self.assertRaises(FormatError) as cm: + checker.check("bar", "foo") + + self.assertIs(cm.exception.cause, cause) + self.assertIs(cm.exception.__cause__, cause) + + # Unregistered errors should not be caught + self.fn.side_effect = AttributeError + with self.assertRaises(AttributeError): + checker.check("bar", "foo") + + def test_format_error_causes_become_validation_error_causes(self): + checker = FormatChecker() + checker.checks("foo", raises=ValueError)(self.fn) + cause = self.fn.side_effect = ValueError() + validator = Draft4Validator({"format" : "foo"}, format_checker=checker) + + with self.assertRaises(ValidationError) as cm: + validator.validate("bar") + + self.assertIs(cm.exception.__cause__, cause) diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py new file mode 100644 index 0000000..b9c3c95 --- /dev/null +++ b/jsonschema/tests/test_jsonschema_test_suite.py @@ -0,0 +1,236 @@ +from decimal import Decimal +import glob +import json +import io +import itertools +import os +import re +import subprocess + +try: + from sys import pypy_version_info +except ImportError: + pypy_version_info = None + +from jsonschema import ( + FormatError, SchemaError, ValidationError, Draft3Validator, + Draft4Validator, FormatChecker, draft3_format_checker, + draft4_format_checker, validate, +) +from jsonschema.compat import PY3 +from jsonschema.tests.compat import mock, unittest +import jsonschema + + +REPO_ROOT = os.path.join(os.path.dirname(jsonschema.__file__), os.path.pardir) +SUITE = os.getenv("JSON_SCHEMA_TEST_SUITE", os.path.join(REPO_ROOT, "json")) + +if not os.path.isdir(SUITE): + raise ValueError( + "Can't find the JSON-Schema-Test-Suite directory. Set the " + "'JSON_SCHEMA_TEST_SUITE' environment variable or run the tests from " + "alongside a checkout of the suite." + ) + +TESTS_DIR = os.path.join(SUITE, "tests") +JSONSCHEMA_SUITE = os.path.join(SUITE, "bin", "jsonschema_suite") + +REMOTES = subprocess.Popen( + ["python", JSONSCHEMA_SUITE, "remotes"], stdout=subprocess.PIPE, +).stdout +if PY3: + REMOTES = io.TextIOWrapper(REMOTES) +REMOTES = json.load(REMOTES) + + +def make_case(schema, data, valid): + if valid: + def test_case(self): + kwargs = getattr(self, "validator_kwargs", {}) + validate(data, schema, cls=self.validator_class, **kwargs) + else: + def test_case(self): + kwargs = getattr(self, "validator_kwargs", {}) + with self.assertRaises(ValidationError): + validate(data, schema, cls=self.validator_class, **kwargs) + return test_case + + +def load_json_cases(tests_glob, ignore_glob="", basedir=TESTS_DIR, skip=None): + if ignore_glob: + ignore_glob = os.path.join(basedir, ignore_glob) + + def add_test_methods(test_class): + ignored = set(glob.iglob(ignore_glob)) + + for filename in glob.iglob(os.path.join(basedir, tests_glob)): + if filename in ignored: + continue + + validating, _ = os.path.splitext(os.path.basename(filename)) + id = itertools.count(1) + + with open(filename) as test_file: + data = json.load(test_file) + + for case in data: + for test in case["tests"]: + a_test = make_case( + case["schema"], + test["data"], + test["valid"], + ) + + test_name = "test_%s_%s_%s" % ( + validating, + next(id), + re.sub(r"[\W ]+", "_", test["description"]), + ) + + if not PY3: + test_name = test_name.encode("utf-8") + a_test.__name__ = test_name + + if skip is not None and skip(case): + a_test = unittest.skip("Checker not present.")( + a_test + ) + + assert not hasattr(test_class, test_name), test_name + setattr(test_class, test_name, a_test) + + return test_class + return add_test_methods + + +class TypesMixin(object): + @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode") + def test_string_a_bytestring_is_a_string(self): + self.validator_class({"type" : "string"}).validate(b"foo") + + +class DecimalMixin(object): + def test_it_can_validate_with_decimals(self): + schema = {"type" : "number"} + validator = self.validator_class( + schema, types={"number" : (int, float, Decimal)} + ) + + for valid in [1, 1.1, Decimal(1) / Decimal(8)]: + validator.validate(valid) + + for invalid in ["foo", {}, [], True, None]: + with self.assertRaises(ValidationError): + validator.validate(invalid) + + +def missing_format(checker): + def missing_format(case): + format = case["schema"].get("format") + return format not in checker.checkers or ( + # datetime.datetime is overzealous about typechecking in <=1.9 + format == "date-time" and + pypy_version_info is not None and + pypy_version_info[:2] <= (1, 9) + ) + return missing_format + + +class FormatMixin(object): + def test_it_returns_true_for_formats_it_does_not_know_about(self): + validator = self.validator_class( + {"format" : "carrot"}, format_checker=FormatChecker(), + ) + validator.validate("bugs") + + def test_it_does_not_validate_formats_by_default(self): + validator = self.validator_class({}) + self.assertIsNone(validator.format_checker) + + def test_it_validates_formats_if_a_checker_is_provided(self): + checker = mock.Mock(spec=FormatChecker) + validator = self.validator_class( + {"format" : "foo"}, format_checker=checker, + ) + + validator.validate("bar") + + checker.check.assert_called_once_with("bar", "foo") + + cause = ValueError() + checker.check.side_effect = FormatError('aoeu', cause=cause) + + with self.assertRaises(ValidationError) as cm: + validator.validate("bar") + # Make sure original cause is attached + self.assertIs(cm.exception.cause, cause) + + +@load_json_cases("draft3/*.json", ignore_glob="draft3/refRemote.json") +@load_json_cases( + "draft3/optional/format.json", skip=missing_format(draft3_format_checker) +) +@load_json_cases("draft3/optional/bignum.json") +@load_json_cases("draft3/optional/zeroTerminatedFloats.json") +class TestDraft3(unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin): + validator_class = Draft3Validator + validator_kwargs = {"format_checker" : draft3_format_checker} + + def test_any_type_is_valid_for_type_any(self): + validator = self.validator_class({"type" : "any"}) + validator.validate(mock.Mock()) + + # TODO: we're in need of more meta schema tests + def test_invalid_properties(self): + with self.assertRaises(SchemaError): + validate({}, {"properties": {"test": True}}, + cls=self.validator_class) + + def test_minItems_invalid_string(self): + with self.assertRaises(SchemaError): + # needs to be an integer + validate([1], {"minItems" : "1"}, cls=self.validator_class) + + +@load_json_cases("draft4/*.json", ignore_glob="draft4/refRemote.json") +@load_json_cases( + "draft4/optional/format.json", skip=missing_format(draft4_format_checker) +) +@load_json_cases("draft4/optional/bignum.json") +@load_json_cases("draft4/optional/zeroTerminatedFloats.json") +class TestDraft4(unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin): + validator_class = Draft4Validator + validator_kwargs = {"format_checker" : draft4_format_checker} + + # TODO: we're in need of more meta schema tests + def test_invalid_properties(self): + with self.assertRaises(SchemaError): + validate({}, {"properties": {"test": True}}, + cls=self.validator_class) + + def test_minItems_invalid_string(self): + with self.assertRaises(SchemaError): + # needs to be an integer + validate([1], {"minItems" : "1"}, cls=self.validator_class) + + +class RemoteRefResolutionMixin(object): + def setUp(self): + patch = mock.patch("jsonschema.validators.requests") + requests = patch.start() + requests.get.side_effect = self.resolve + self.addCleanup(patch.stop) + + def resolve(self, reference): + _, _, reference = reference.partition("http://localhost:1234/") + return mock.Mock(**{"json.return_value" : REMOTES.get(reference)}) + + +@load_json_cases("draft3/refRemote.json") +class Draft3RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase): + validator_class = Draft3Validator + + +@load_json_cases("draft4/refRemote.json") +class Draft4RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase): + validator_class = Draft4Validator diff --git a/test_jsonschema.py b/jsonschema/tests/test_validators.py index 7a07904..2fd1d91 100644 --- a/test_jsonschema.py +++ b/jsonschema/tests/test_validators.py @@ -1,254 +1,16 @@ from __future__ import unicode_literals -from decimal import Decimal import contextlib -import glob -import io -import itertools import json -import os import pprint -import re -import subprocess -import sys import textwrap -if sys.version_info[:2] < (2, 7): # pragma: no cover - import unittest2 as unittest -else: - import unittest - -try: - from unittest import mock -except ImportError: - import mock - -try: - from sys import pypy_version_info -except ImportError: - pypy_version_info = None - -from jsonschema import ( - FormatError, RefResolutionError, SchemaError, UnknownType, - ValidationError, ErrorTree, Draft3Validator, Draft4Validator, - FormatChecker, RefResolver, ValidatorMixin, draft3_format_checker, - draft4_format_checker, validate, -) +from jsonschema import FormatChecker from jsonschema.compat import PY3 - - -THIS_DIR = os.path.dirname(__file__) -TESTS_DIR = os.path.join(THIS_DIR, "json", "tests") - -JSONSCHEMA_SUITE = os.path.join(THIS_DIR, "json", "bin", "jsonschema_suite") - -REMOTES = subprocess.Popen( - ["python", JSONSCHEMA_SUITE, "remotes"], stdout=subprocess.PIPE, -).stdout -if PY3: - REMOTES = io.TextIOWrapper(REMOTES) -REMOTES = json.load(REMOTES) - - -def make_case(schema, data, valid): - if valid: - def test_case(self): - kwargs = getattr(self, "validator_kwargs", {}) - validate(data, schema, cls=self.validator_class, **kwargs) - else: - def test_case(self): - kwargs = getattr(self, "validator_kwargs", {}) - with self.assertRaises(ValidationError): - validate(data, schema, cls=self.validator_class, **kwargs) - return test_case - - -def load_json_cases(tests_glob, ignore_glob="", basedir=TESTS_DIR, skip=None): - if ignore_glob: - ignore_glob = os.path.join(basedir, ignore_glob) - - def add_test_methods(test_class): - ignored = set(glob.iglob(ignore_glob)) - - for filename in glob.iglob(os.path.join(basedir, tests_glob)): - if filename in ignored: - continue - - validating, _ = os.path.splitext(os.path.basename(filename)) - id = itertools.count(1) - - with open(filename) as test_file: - data = json.load(test_file) - - for case in data: - for test in case["tests"]: - a_test = make_case( - case["schema"], - test["data"], - test["valid"], - ) - - test_name = "test_%s_%s_%s" % ( - validating, - next(id), - re.sub(r"[\W ]+", "_", test["description"]), - ) - - if not PY3: - test_name = test_name.encode("utf-8") - a_test.__name__ = test_name - - if skip is not None and skip(case): - a_test = unittest.skip("Checker not present.")( - a_test - ) - - assert not hasattr(test_class, test_name), test_name - setattr(test_class, test_name, a_test) - - return test_class - return add_test_methods - - -class TypesMixin(object): - @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode") - def test_string_a_bytestring_is_a_string(self): - self.validator_class({"type" : "string"}).validate(b"foo") - - -class DecimalMixin(object): - def test_it_can_validate_with_decimals(self): - schema = {"type" : "number"} - validator = self.validator_class( - schema, types={"number" : (int, float, Decimal)} - ) - - for valid in [1, 1.1, Decimal(1) / Decimal(8)]: - validator.validate(valid) - - for invalid in ["foo", {}, [], True, None]: - with self.assertRaises(ValidationError): - validator.validate(invalid) - - -def missing_format(checker): - def missing_format(case): - format = case["schema"].get("format") - return format not in checker.checkers or ( - # datetime.datetime is overzealous about typechecking in <=1.9 - format == "date-time" and - pypy_version_info is not None and - pypy_version_info[:2] <= (1, 9) - ) - return missing_format - - -class FormatMixin(object): - def test_it_returns_true_for_formats_it_does_not_know_about(self): - validator = self.validator_class( - {"format" : "carrot"}, format_checker=FormatChecker(), - ) - validator.validate("bugs") - - def test_it_does_not_validate_formats_by_default(self): - validator = self.validator_class({}) - self.assertIsNone(validator.format_checker) - - def test_it_validates_formats_if_a_checker_is_provided(self): - checker = mock.Mock(spec=FormatChecker) - validator = self.validator_class( - {"format" : "foo"}, format_checker=checker, - ) - - validator.validate("bar") - - checker.check.assert_called_once_with("bar", "foo") - - cause = ValueError() - checker.check.side_effect = FormatError('aoeu', cause=cause) - - with self.assertRaises(ValidationError) as cm: - validator.validate("bar") - # Make sure original cause is attached - self.assertIs(cm.exception.cause, cause) - - -@load_json_cases( - "draft3/*.json", ignore_glob=os.path.join("draft3", "refRemote.json") -) -@load_json_cases( - "draft3/optional/format.json", skip=missing_format(draft3_format_checker) -) -@load_json_cases("draft3/optional/bignum.json") -@load_json_cases("draft3/optional/zeroTerminatedFloats.json") -class TestDraft3( - unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin -): - - validator_class = Draft3Validator - validator_kwargs = {"format_checker" : draft3_format_checker} - - def test_any_type_is_valid_for_type_any(self): - validator = self.validator_class({"type" : "any"}) - validator.validate(mock.Mock()) - - # TODO: we're in need of more meta schema tests - def test_invalid_properties(self): - with self.assertRaises(SchemaError): - validate({}, {"properties": {"test": True}}, - cls=self.validator_class) - - def test_minItems_invalid_string(self): - with self.assertRaises(SchemaError): - # needs to be an integer - validate([1], {"minItems" : "1"}, cls=self.validator_class) - - -@load_json_cases( - "draft4/*.json", ignore_glob=os.path.join("draft4", "refRemote.json") -) -@load_json_cases( - "draft4/optional/format.json", skip=missing_format(draft4_format_checker) +from jsonschema.tests.compat import mock, unittest +from jsonschema.validators import ( + RefResolutionError, UnknownType, ValidationError, ErrorTree, + Draft3Validator, Draft4Validator, RefResolver, ValidatorMixin, validate, ) -@load_json_cases("draft4/optional/bignum.json") -@load_json_cases("draft4/optional/zeroTerminatedFloats.json") -class TestDraft4( - unittest.TestCase, TypesMixin, DecimalMixin, FormatMixin -): - validator_class = Draft4Validator - validator_kwargs = {"format_checker" : draft4_format_checker} - - # TODO: we're in need of more meta schema tests - def test_invalid_properties(self): - with self.assertRaises(SchemaError): - validate({}, {"properties": {"test": True}}, - cls=self.validator_class) - - def test_minItems_invalid_string(self): - with self.assertRaises(SchemaError): - # needs to be an integer - validate([1], {"minItems" : "1"}, cls=self.validator_class) - - -class RemoteRefResolutionMixin(object): - def setUp(self): - patch = mock.patch("jsonschema.validators.requests") - requests = patch.start() - requests.get.side_effect = self.resolve - self.addCleanup(patch.stop) - - def resolve(self, reference): - _, _, reference = reference.partition("http://localhost:1234/") - return mock.Mock(**{"json.return_value" : REMOTES.get(reference)}) - - -@load_json_cases("draft3/refRemote.json") -class Draft3RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase): - validator_class = Draft3Validator - - -@load_json_cases("draft4/refRemote.json") -class Draft4RemoteResolution(RemoteRefResolutionMixin, unittest.TestCase): - validator_class = Draft4Validator class TestIterErrors(unittest.TestCase): @@ -963,60 +725,6 @@ class TestRefResolver(unittest.TestCase): self.assertEqual(str(err.exception), "Oh no! What's this?") -class TestFormatChecker(unittest.TestCase): - def setUp(self): - self.fn = mock.Mock() - - def test_it_can_validate_no_formats(self): - checker = FormatChecker(formats=()) - self.assertFalse(checker.checkers) - - def test_it_raises_a_key_error_for_unknown_formats(self): - with self.assertRaises(KeyError): - FormatChecker(formats=["o noes"]) - - def test_it_can_register_cls_checkers(self): - with mock.patch.dict(FormatChecker.checkers, clear=True): - FormatChecker.cls_checks("new")(self.fn) - self.assertEqual(FormatChecker.checkers, {"new" : (self.fn, ())}) - - def test_it_can_register_checkers(self): - checker = FormatChecker() - checker.checks("new")(self.fn) - self.assertEqual( - checker.checkers, - dict(FormatChecker.checkers, new=(self.fn, ())) - ) - - def test_it_catches_registered_errors(self): - checker = FormatChecker() - cause = self.fn.side_effect = ValueError() - - checker.checks("foo", raises=ValueError)(self.fn) - - with self.assertRaises(FormatError) as cm: - checker.check("bar", "foo") - - self.assertIs(cm.exception.cause, cause) - self.assertIs(cm.exception.__cause__, cause) - - # Unregistered errors should not be caught - self.fn.side_effect = AttributeError - with self.assertRaises(AttributeError): - checker.check("bar", "foo") - - def test_format_error_causes_become_validation_error_causes(self): - checker = FormatChecker() - checker.checks("foo", raises=ValueError)(self.fn) - cause = self.fn.side_effect = ValueError() - validator = Draft4Validator({"format" : "foo"}, format_checker=checker) - - with self.assertRaises(ValidationError) as cm: - validator.validate("bar") - - self.assertIs(cm.exception.__cause__, cause) - - def sorted_errors(errors): def key(error): return ( @@ -3,7 +3,7 @@ envlist = py26, py27, pypy, py32, py33, docs, style [testenv] commands = - py.test -s test_jsonschema.py + py.test -s jsonschema {envpython} -m doctest README.rst deps = {[testenv:notpy33]deps} @@ -22,7 +22,6 @@ commands = deps = flake8 commands = flake8 --max-complexity 10 jsonschema - flake8 test_jsonschema.py [flake8] ignore = E203,E302,E303,E701,F811 @@ -36,7 +35,7 @@ deps = [testenv:py33] commands = - py.test -s test_jsonschema.py + py.test -s jsonschema {envpython} -m doctest README.rst sphinx-build -b doctest docs {envtmpdir}/html deps = |