|
|
|
"""
|
|
|
|
Test runner for the JSON Schema official test suite
|
|
|
|
|
|
|
|
Tests comprehensive correctness of each draft's validator.
|
|
|
|
|
|
|
|
See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from jsonschema.tests._helpers import bug
|
|
|
|
from jsonschema.tests._suite import Suite
|
|
|
|
import jsonschema
|
|
|
|
|
|
|
|
SUITE = Suite()
|
|
|
|
DRAFT3 = SUITE.version(name="draft3")
|
|
|
|
DRAFT4 = SUITE.version(name="draft4")
|
|
|
|
DRAFT6 = SUITE.version(name="draft6")
|
|
|
|
DRAFT7 = SUITE.version(name="draft7")
|
|
|
|
DRAFT201909 = SUITE.version(name="draft2019-09")
|
|
|
|
DRAFT202012 = SUITE.version(name="draft2020-12")
|
|
|
|
|
|
|
|
|
|
|
|
def skip(message, **kwargs):
|
|
|
|
def skipper(test):
|
|
|
|
if all(value == getattr(test, attr) for attr, value in kwargs.items()):
|
|
|
|
return message
|
|
|
|
return skipper
|
|
|
|
|
|
|
|
|
|
|
|
def missing_format(Validator):
|
|
|
|
def missing_format(test): # pragma: no cover
|
|
|
|
schema = test.schema
|
|
|
|
if (
|
|
|
|
schema is True
|
|
|
|
or schema is False
|
|
|
|
or "format" not in schema
|
|
|
|
or schema["format"] in Validator.FORMAT_CHECKER.checkers
|
|
|
|
or test.valid
|
|
|
|
):
|
|
|
|
return
|
|
|
|
|
|
|
|
return "Format checker {0!r} not found.".format(schema["format"])
|
|
|
|
return missing_format
|
|
|
|
|
|
|
|
|
|
|
|
def complex_email_validation(test):
|
|
|
|
if test.subject != "email":
|
|
|
|
return
|
|
|
|
|
|
|
|
message = "Complex email validation is (intentionally) unsupported."
|
|
|
|
return skip(
|
|
|
|
message=message,
|
|
|
|
description="an invalid domain",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
description="an invalid IPv4-address-literal",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
description="dot after local part is not valid",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
description="dot before local part is not valid",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
description="two subsequent dots inside local part are not valid",
|
|
|
|
)(test)
|
|
|
|
|
|
|
|
|
|
|
|
is_narrow_build = sys.maxunicode == 2 ** 16 - 1
|
|
|
|
if is_narrow_build: # pragma: no cover
|
|
|
|
message = "Not running surrogate Unicode case, this Python is narrow."
|
|
|
|
|
|
|
|
def narrow_unicode_build(test): # pragma: no cover
|
|
|
|
return skip(
|
|
|
|
message=message,
|
|
|
|
description=(
|
|
|
|
"one supplementary Unicode code point is not long enough"
|
|
|
|
),
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
description="two supplementary Unicode code points is long enough",
|
|
|
|
)(test)
|
|
|
|
else:
|
|
|
|
def narrow_unicode_build(test): # pragma: no cover
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if sys.version_info < (3, 9): # pragma: no cover
|
|
|
|
message = "Rejecting leading zeros is 3.9+"
|
|
|
|
allowed_leading_zeros = skip(
|
|
|
|
message=message,
|
|
|
|
subject="ipv4",
|
|
|
|
description="invalid leading zeroes, as they are treated as octals",
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
def allowed_leading_zeros(test): # pragma: no cover
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def leap_second(test):
|
|
|
|
message = "Leap seconds are unsupported."
|
|
|
|
return skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="a valid time string with leap second",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="a valid time string with leap second, Zulu",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="a valid time string with leap second with offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="valid leap second, positive time-offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="valid leap second, negative time-offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="valid leap second, large positive time-offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="valid leap second, large negative time-offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="time",
|
|
|
|
description="valid leap second, zero time-offset",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="date-time",
|
|
|
|
description="a valid date-time with a leap second, UTC",
|
|
|
|
)(test) or skip(
|
|
|
|
message=message,
|
|
|
|
subject="date-time",
|
|
|
|
description="a valid date-time with a leap second, with minus offset",
|
|
|
|
)(test)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft3 = DRAFT3.to_unittest_testcase(
|
|
|
|
DRAFT3.tests(),
|
|
|
|
DRAFT3.format_tests(),
|
|
|
|
DRAFT3.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT3.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
DRAFT3.optional_tests_of(name="zeroTerminatedFloats"),
|
|
|
|
Validator=jsonschema.Draft3Validator,
|
|
|
|
format_checker=jsonschema.Draft3Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
narrow_unicode_build(test)
|
|
|
|
or missing_format(jsonschema.Draft3Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
valid=False,
|
|
|
|
case_description=(
|
|
|
|
"$ref prevents a sibling id from changing the base uri"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft4 = DRAFT4.to_unittest_testcase(
|
|
|
|
DRAFT4.tests(),
|
|
|
|
DRAFT4.format_tests(),
|
|
|
|
DRAFT4.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT4.optional_tests_of(name="float-overflow"),
|
|
|
|
DRAFT4.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
DRAFT4.optional_tests_of(name="zeroTerminatedFloats"),
|
|
|
|
Validator=jsonschema.Draft4Validator,
|
|
|
|
format_checker=jsonschema.Draft4Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
narrow_unicode_build(test)
|
|
|
|
or allowed_leading_zeros(test)
|
|
|
|
or leap_second(test)
|
|
|
|
or missing_format(jsonschema.Draft4Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description="Recursive references between schemas",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"Location-independent identifier with "
|
|
|
|
"base URI change in subschema"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"$ref prevents a sibling id from changing the base uri"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="id",
|
|
|
|
description="match $ref to id",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="id",
|
|
|
|
description="no match on enum or $ref to id",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="refRemote",
|
|
|
|
case_description="base URI change - change folder in subschema",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"id must be resolved against nearest parent, "
|
|
|
|
"not just immediate parent"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft6 = DRAFT6.to_unittest_testcase(
|
|
|
|
DRAFT6.tests(),
|
|
|
|
DRAFT6.format_tests(),
|
|
|
|
DRAFT6.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT6.optional_tests_of(name="float-overflow"),
|
|
|
|
DRAFT6.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
Validator=jsonschema.Draft6Validator,
|
|
|
|
format_checker=jsonschema.Draft6Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
narrow_unicode_build(test)
|
|
|
|
or allowed_leading_zeros(test)
|
|
|
|
or leap_second(test)
|
|
|
|
or missing_format(jsonschema.Draft6Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="refRemote",
|
|
|
|
case_description="base URI change - change folder in subschema",
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft7 = DRAFT7.to_unittest_testcase(
|
|
|
|
DRAFT7.tests(),
|
|
|
|
DRAFT7.format_tests(),
|
|
|
|
DRAFT7.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT7.optional_tests_of(name="cross-draft"),
|
|
|
|
DRAFT7.optional_tests_of(name="float-overflow"),
|
|
|
|
DRAFT7.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
Validator=jsonschema.Draft7Validator,
|
|
|
|
format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
narrow_unicode_build(test)
|
|
|
|
or allowed_leading_zeros(test)
|
|
|
|
or leap_second(test)
|
|
|
|
or missing_format(jsonschema.Draft7Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="refRemote",
|
|
|
|
case_description="base URI change - change folder in subschema",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"$id must be resolved against nearest parent, "
|
|
|
|
"not just immediate parent"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft201909 = DRAFT201909.to_unittest_testcase(
|
|
|
|
DRAFT201909.tests(),
|
|
|
|
DRAFT201909.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT201909.optional_tests_of(name="cross-draft"),
|
|
|
|
DRAFT201909.optional_tests_of(name="float-overflow"),
|
|
|
|
DRAFT201909.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
DRAFT201909.optional_tests_of(name="refOfUnknownKeyword"),
|
|
|
|
Validator=jsonschema.Draft201909Validator,
|
|
|
|
skip=lambda test: (
|
|
|
|
skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
case_description=(
|
|
|
|
"$recursiveRef with no $recursiveAnchor in "
|
|
|
|
"the initial target schema resource"
|
|
|
|
),
|
|
|
|
description=(
|
|
|
|
"leaf node does not match: recursion uses the inner schema"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description="leaf node matches: recursion uses the inner schema",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
case_description=(
|
|
|
|
"dynamic $recursiveRef destination (not predictable "
|
|
|
|
"at schema compile time)"
|
|
|
|
),
|
|
|
|
description="integer node",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
case_description=(
|
|
|
|
"multiple dynamic paths to the $recursiveRef keyword"
|
|
|
|
),
|
|
|
|
description="recurse to integerNode - floats are not allowed",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description="integer does not match as a property value",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description=(
|
|
|
|
"leaf node does not match: "
|
|
|
|
"recursion only uses inner schema"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description=(
|
|
|
|
"leaf node matches: "
|
|
|
|
"recursion only uses inner schema"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description=(
|
|
|
|
"two levels, integer does not match as a property value"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description="recursive mismatch",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="recursiveRef",
|
|
|
|
description="two levels, no match",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="recursiveRef support isn't working yet.",
|
|
|
|
subject="id",
|
|
|
|
case_description=(
|
|
|
|
"Invalid use of fragments in location-independent $id"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="defs",
|
|
|
|
description="invalid definition schema",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="anchor",
|
|
|
|
case_description="same $anchor with different base uri",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="Vocabulary support is still in-progress.",
|
|
|
|
subject="vocabulary",
|
|
|
|
description=(
|
|
|
|
"no validation: invalid number, but it still validates"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"$id must be resolved against nearest parent, "
|
|
|
|
"not just immediate parent"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="refRemote",
|
|
|
|
case_description="remote HTTP ref with nested absolute ref",
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft201909Format = DRAFT201909.to_unittest_testcase(
|
|
|
|
DRAFT201909.format_tests(),
|
|
|
|
name="TestDraft201909Format",
|
|
|
|
Validator=jsonschema.Draft201909Validator,
|
|
|
|
format_checker=jsonschema.Draft201909Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
complex_email_validation(test)
|
|
|
|
or allowed_leading_zeros(test)
|
|
|
|
or leap_second(test)
|
|
|
|
or missing_format(jsonschema.Draft201909Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft202012 = DRAFT202012.to_unittest_testcase(
|
|
|
|
DRAFT202012.tests(),
|
|
|
|
DRAFT202012.optional_tests_of(name="bignum"),
|
|
|
|
DRAFT202012.optional_tests_of(name="cross-draft"),
|
|
|
|
DRAFT202012.optional_tests_of(name="float-overflow"),
|
|
|
|
DRAFT202012.optional_tests_of(name="non-bmp-regex"),
|
|
|
|
DRAFT202012.optional_tests_of(name="refOfUnknownKeyword"),
|
|
|
|
Validator=jsonschema.Draft202012Validator,
|
|
|
|
skip=lambda test: (
|
|
|
|
narrow_unicode_build(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="The recursive part is not valid against the root",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="incorrect extended schema",
|
|
|
|
case_description=(
|
|
|
|
"$ref and $dynamicAnchor are independent of order - "
|
|
|
|
"$defs first"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="correct extended schema",
|
|
|
|
case_description=(
|
|
|
|
"$ref and $dynamicAnchor are independent of order - "
|
|
|
|
"$defs first"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="correct extended schema",
|
|
|
|
case_description=(
|
|
|
|
"$ref and $dynamicAnchor are independent of order - $ref first"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="incorrect extended schema",
|
|
|
|
case_description=(
|
|
|
|
"$ref and $dynamicAnchor are independent of order - $ref first"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description=(
|
|
|
|
"/then/$defs/thingy is the final stop for the $dynamicRef"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description=(
|
|
|
|
"string matches /$defs/thingy, but the $dynamicRef "
|
|
|
|
"does not stop here"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description=(
|
|
|
|
"string matches /$defs/thingy, but the $dynamicRef "
|
|
|
|
"does not stop here"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="dynamicRef",
|
|
|
|
description="recurse to integerNode - floats are not allowed",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="defs",
|
|
|
|
description="invalid definition schema",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="dynamicRef support isn't fully working yet.",
|
|
|
|
subject="anchor",
|
|
|
|
case_description="same $anchor with different base uri",
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message="Vocabulary support is still in-progress.",
|
|
|
|
subject="vocabulary",
|
|
|
|
description=(
|
|
|
|
"no validation: invalid number, but it still validates"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="ref",
|
|
|
|
case_description=(
|
|
|
|
"$id must be resolved against nearest parent, "
|
|
|
|
"not just immediate parent"
|
|
|
|
),
|
|
|
|
)(test)
|
|
|
|
or skip(
|
|
|
|
message=bug(),
|
|
|
|
subject="refRemote",
|
|
|
|
case_description="remote HTTP ref with nested absolute ref",
|
|
|
|
)(test)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
TestDraft202012Format = DRAFT202012.to_unittest_testcase(
|
|
|
|
DRAFT202012.format_tests(),
|
|
|
|
name="TestDraft202012Format",
|
|
|
|
Validator=jsonschema.Draft202012Validator,
|
|
|
|
format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER,
|
|
|
|
skip=lambda test: (
|
|
|
|
complex_email_validation(test)
|
|
|
|
or allowed_leading_zeros(test)
|
|
|
|
or leap_second(test)
|
|
|
|
or missing_format(jsonschema.Draft202012Validator)(test)
|
|
|
|
or complex_email_validation(test)
|
|
|
|
),
|
|
|
|
)
|