test_jsonschema_test_suite.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. """
  2. Test runner for the JSON Schema official test suite
  3. Tests comprehensive correctness of each draft's validator.
  4. See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details.
  5. """
  6. import sys
  7. from jsonschema.tests._suite import Suite
  8. import jsonschema
  9. SUITE = Suite()
  10. DRAFT3 = SUITE.version(name="draft3")
  11. DRAFT4 = SUITE.version(name="draft4")
  12. DRAFT6 = SUITE.version(name="draft6")
  13. DRAFT7 = SUITE.version(name="draft7")
  14. DRAFT201909 = SUITE.version(name="draft2019-09")
  15. DRAFT202012 = SUITE.version(name="draft2020-12")
  16. def skip(message, **kwargs):
  17. def skipper(test):
  18. if all(value == getattr(test, attr) for attr, value in kwargs.items()):
  19. return message
  20. return skipper
  21. def missing_format(Validator):
  22. def missing_format(test): # pragma: no cover
  23. schema = test.schema
  24. if (
  25. schema is True
  26. or schema is False
  27. or "format" not in schema
  28. or schema["format"] in Validator.FORMAT_CHECKER.checkers
  29. or test.valid
  30. ):
  31. return
  32. return f"Format checker {schema['format']!r} not found."
  33. return missing_format
  34. def complex_email_validation(test):
  35. if test.subject != "email":
  36. return
  37. message = "Complex email validation is (intentionally) unsupported."
  38. return skip(
  39. message=message,
  40. description="an invalid domain",
  41. )(test) or skip(
  42. message=message,
  43. description="an invalid IPv4-address-literal",
  44. )(test) or skip(
  45. message=message,
  46. description="dot after local part is not valid",
  47. )(test) or skip(
  48. message=message,
  49. description="dot before local part is not valid",
  50. )(test) or skip(
  51. message=message,
  52. description="two subsequent dots inside local part are not valid",
  53. )(test)
  54. if sys.version_info < (3, 9): # pragma: no cover
  55. message = "Rejecting leading zeros is 3.9+"
  56. allowed_leading_zeros = skip(
  57. message=message,
  58. subject="ipv4",
  59. description="invalid leading zeroes, as they are treated as octals",
  60. )
  61. else:
  62. def allowed_leading_zeros(test): # pragma: no cover
  63. return
  64. def leap_second(test):
  65. message = "Leap seconds are unsupported."
  66. return skip(
  67. message=message,
  68. subject="time",
  69. description="a valid time string with leap second",
  70. )(test) or skip(
  71. message=message,
  72. subject="time",
  73. description="a valid time string with leap second, Zulu",
  74. )(test) or skip(
  75. message=message,
  76. subject="time",
  77. description="a valid time string with leap second with offset",
  78. )(test) or skip(
  79. message=message,
  80. subject="time",
  81. description="valid leap second, positive time-offset",
  82. )(test) or skip(
  83. message=message,
  84. subject="time",
  85. description="valid leap second, negative time-offset",
  86. )(test) or skip(
  87. message=message,
  88. subject="time",
  89. description="valid leap second, large positive time-offset",
  90. )(test) or skip(
  91. message=message,
  92. subject="time",
  93. description="valid leap second, large negative time-offset",
  94. )(test) or skip(
  95. message=message,
  96. subject="time",
  97. description="valid leap second, zero time-offset",
  98. )(test) or skip(
  99. message=message,
  100. subject="date-time",
  101. description="a valid date-time with a leap second, UTC",
  102. )(test) or skip(
  103. message=message,
  104. subject="date-time",
  105. description="a valid date-time with a leap second, with minus offset",
  106. )(test)
  107. TestDraft3 = DRAFT3.to_unittest_testcase(
  108. DRAFT3.cases(),
  109. DRAFT3.format_cases(),
  110. DRAFT3.optional_cases_of(name="bignum"),
  111. DRAFT3.optional_cases_of(name="non-bmp-regex"),
  112. DRAFT3.optional_cases_of(name="zeroTerminatedFloats"),
  113. Validator=jsonschema.Draft3Validator,
  114. format_checker=jsonschema.Draft3Validator.FORMAT_CHECKER,
  115. skip=lambda test: (
  116. missing_format(jsonschema.Draft3Validator)(test)
  117. or complex_email_validation(test)
  118. ),
  119. )
  120. TestDraft4 = DRAFT4.to_unittest_testcase(
  121. DRAFT4.cases(),
  122. DRAFT4.format_cases(),
  123. DRAFT4.optional_cases_of(name="bignum"),
  124. DRAFT4.optional_cases_of(name="float-overflow"),
  125. DRAFT4.optional_cases_of(name="id"),
  126. DRAFT4.optional_cases_of(name="non-bmp-regex"),
  127. DRAFT4.optional_cases_of(name="zeroTerminatedFloats"),
  128. Validator=jsonschema.Draft4Validator,
  129. format_checker=jsonschema.Draft4Validator.FORMAT_CHECKER,
  130. skip=lambda test: (
  131. allowed_leading_zeros(test)
  132. or leap_second(test)
  133. or missing_format(jsonschema.Draft4Validator)(test)
  134. or complex_email_validation(test)
  135. ),
  136. )
  137. TestDraft6 = DRAFT6.to_unittest_testcase(
  138. DRAFT6.cases(),
  139. DRAFT6.format_cases(),
  140. DRAFT6.optional_cases_of(name="bignum"),
  141. DRAFT6.optional_cases_of(name="float-overflow"),
  142. DRAFT6.optional_cases_of(name="id"),
  143. DRAFT6.optional_cases_of(name="non-bmp-regex"),
  144. Validator=jsonschema.Draft6Validator,
  145. format_checker=jsonschema.Draft6Validator.FORMAT_CHECKER,
  146. skip=lambda test: (
  147. allowed_leading_zeros(test)
  148. or leap_second(test)
  149. or missing_format(jsonschema.Draft6Validator)(test)
  150. or complex_email_validation(test)
  151. ),
  152. )
  153. TestDraft7 = DRAFT7.to_unittest_testcase(
  154. DRAFT7.cases(),
  155. DRAFT7.format_cases(),
  156. DRAFT7.optional_cases_of(name="bignum"),
  157. DRAFT7.optional_cases_of(name="cross-draft"),
  158. DRAFT7.optional_cases_of(name="float-overflow"),
  159. DRAFT6.optional_cases_of(name="id"),
  160. DRAFT7.optional_cases_of(name="non-bmp-regex"),
  161. DRAFT7.optional_cases_of(name="unknownKeyword"),
  162. Validator=jsonschema.Draft7Validator,
  163. format_checker=jsonschema.Draft7Validator.FORMAT_CHECKER,
  164. skip=lambda test: (
  165. allowed_leading_zeros(test)
  166. or leap_second(test)
  167. or missing_format(jsonschema.Draft7Validator)(test)
  168. or complex_email_validation(test)
  169. ),
  170. )
  171. TestDraft201909 = DRAFT201909.to_unittest_testcase(
  172. DRAFT201909.cases(),
  173. DRAFT201909.optional_cases_of(name="anchor"),
  174. DRAFT201909.optional_cases_of(name="bignum"),
  175. DRAFT201909.optional_cases_of(name="cross-draft"),
  176. DRAFT201909.optional_cases_of(name="float-overflow"),
  177. DRAFT201909.optional_cases_of(name="id"),
  178. DRAFT201909.optional_cases_of(name="no-schema"),
  179. DRAFT201909.optional_cases_of(name="non-bmp-regex"),
  180. DRAFT201909.optional_cases_of(name="refOfUnknownKeyword"),
  181. DRAFT201909.optional_cases_of(name="unknownKeyword"),
  182. Validator=jsonschema.Draft201909Validator,
  183. skip=skip(
  184. message="Vocabulary support is still in-progress.",
  185. subject="vocabulary",
  186. description=(
  187. "no validation: invalid number, but it still validates"
  188. ),
  189. ),
  190. )
  191. TestDraft201909Format = DRAFT201909.to_unittest_testcase(
  192. DRAFT201909.format_cases(),
  193. name="TestDraft201909Format",
  194. Validator=jsonschema.Draft201909Validator,
  195. format_checker=jsonschema.Draft201909Validator.FORMAT_CHECKER,
  196. skip=lambda test: (
  197. complex_email_validation(test)
  198. or allowed_leading_zeros(test)
  199. or leap_second(test)
  200. or missing_format(jsonschema.Draft201909Validator)(test)
  201. or complex_email_validation(test)
  202. ),
  203. )
  204. TestDraft202012 = DRAFT202012.to_unittest_testcase(
  205. DRAFT202012.cases(),
  206. DRAFT201909.optional_cases_of(name="anchor"),
  207. DRAFT202012.optional_cases_of(name="bignum"),
  208. DRAFT202012.optional_cases_of(name="cross-draft"),
  209. DRAFT202012.optional_cases_of(name="float-overflow"),
  210. DRAFT202012.optional_cases_of(name="id"),
  211. DRAFT202012.optional_cases_of(name="no-schema"),
  212. DRAFT202012.optional_cases_of(name="non-bmp-regex"),
  213. DRAFT202012.optional_cases_of(name="refOfUnknownKeyword"),
  214. DRAFT202012.optional_cases_of(name="unknownKeyword"),
  215. Validator=jsonschema.Draft202012Validator,
  216. skip=skip(
  217. message="Vocabulary support is still in-progress.",
  218. subject="vocabulary",
  219. description=(
  220. "no validation: invalid number, but it still validates"
  221. ),
  222. ),
  223. )
  224. TestDraft202012Format = DRAFT202012.to_unittest_testcase(
  225. DRAFT202012.format_cases(),
  226. name="TestDraft202012Format",
  227. Validator=jsonschema.Draft202012Validator,
  228. format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER,
  229. skip=lambda test: (
  230. complex_email_validation(test)
  231. or allowed_leading_zeros(test)
  232. or leap_second(test)
  233. or missing_format(jsonschema.Draft202012Validator)(test)
  234. or complex_email_validation(test)
  235. ),
  236. )