@@ -30,11 +30,15 @@ else:
3030
3131ROOT_DIR = Path (__file__ ).parent .parent
3232SUITE_ROOT_DIR = ROOT_DIR / "tests"
33+ OUTPUT_ROOT_DIR = ROOT_DIR / "output-tests"
3334
3435REMOTES_DIR = ROOT_DIR / "remotes"
3536REMOTES_BASE_URL = "http://localhost:1234/"
3637
37- TESTSUITE_SCHEMA = json .loads ((ROOT_DIR / "test-schema.json" ).read_text ())
38+ TEST_SCHEMA = json .loads (ROOT_DIR .joinpath ("test-schema.json" ).read_text ())
39+ OUTPUT_TEST_SCHEMA = json .loads (
40+ ROOT_DIR .joinpath ("output-test-schema.json" ).read_text (),
41+ )
3842
3943
4044def files (paths ):
@@ -67,7 +71,7 @@ def collect(root_dir):
6771 """
6872 All of the test file paths within the given root directory, recursively.
6973 """
70- return root_dir .glob ( "**/ *.json" )
74+ return root_dir .rglob ( " *.json" )
7175
7276
7377def url_for_path (path ):
@@ -80,20 +84,29 @@ def url_for_path(path):
8084
8185 return urljoin (
8286 REMOTES_BASE_URL ,
83- str (path .relative_to (REMOTES_DIR )).replace ("\\ " , "/" ) # Windows...
87+ str (path .relative_to (REMOTES_DIR )).replace ("\\ " , "/" ), # Windows...
8488 )
8589
8690
8791class SanityTests (unittest .TestCase ):
8892 @classmethod
8993 def setUpClass (cls ):
9094 print (f"Looking for tests in { SUITE_ROOT_DIR } " )
95+ print (f"Looking for output tests in { OUTPUT_ROOT_DIR } " )
9196 print (f"Looking for remotes in { REMOTES_DIR } " )
9297
9398 cls .test_files = list (collect (SUITE_ROOT_DIR ))
9499 assert cls .test_files , "Didn't find the test files!"
95100 print (f"Found { len (cls .test_files )} test files" )
96101
102+ cls .output_test_files = [
103+ each
104+ for each in collect (OUTPUT_ROOT_DIR )
105+ if each .name != "output-schema.json"
106+ ]
107+ assert cls .output_test_files , "Didn't find the output test files!"
108+ print (f"Found { len (cls .output_test_files )} output test files" )
109+
97110 cls .remote_files = list (collect (REMOTES_DIR ))
98111 assert cls .remote_files , "Didn't find the remote files!"
99112 print (f"Found { len (cls .remote_files )} remote files" )
@@ -131,22 +144,11 @@ class SanityTests(unittest.TestCase):
131144 self .assertNotRegex (description , r"\bshould\b" , message )
132145 self .assertNotRegex (description , r"(?i)\btest(s)? that\b" , message )
133146
134- def test_all_test_files_are_valid_json (self ):
135- """
136- All test files contain valid JSON.
137- """
138- for path in self .test_files :
139- with self .subTest (path = path ):
140- try :
141- json .loads (path .read_text ())
142- except ValueError as error :
143- self .fail (f"{ path } contains invalid JSON ({ error } )" )
144-
145- def test_all_remote_files_are_valid_json (self ):
147+ def test_all_json_files_are_valid (self ):
146148 """
147- All remote files contain valid JSON.
149+ All files (tests, output tests, remotes, etc.) contain valid JSON.
148150 """
149- for path in self . remote_files :
151+ for path in collect ( ROOT_DIR ) :
150152 with self .subTest (path = path ):
151153 try :
152154 json .loads (path .read_text ())
@@ -157,53 +159,57 @@ class SanityTests(unittest.TestCase):
157159 """
158160 All cases have reasonably long descriptions.
159161 """
160- for case in cases (self .test_files ):
162+ for case in cases (self .test_files + self . output_test_files ):
161163 with self .subTest (description = case ["description" ]):
162164 self .assertLess (
163165 len (case ["description" ]),
164166 150 ,
165- "Description is too long (keep it to less than 150 chars)."
167+ "Description is too long (keep it to less than 150 chars)." ,
166168 )
167169
168170 def test_all_test_descriptions_have_reasonable_length (self ):
169171 """
170172 All tests have reasonably long descriptions.
171173 """
172- for count , test in enumerate (tests (self .test_files )):
174+ for count , test in enumerate (
175+ tests (self .test_files + self .output_test_files )
176+ ):
173177 with self .subTest (description = test ["description" ]):
174178 self .assertLess (
175179 len (test ["description" ]),
176180 70 ,
177- "Description is too long (keep it to less than 70 chars)."
181+ "Description is too long (keep it to less than 70 chars)." ,
178182 )
179183 print (f"Found { count } tests." )
180184
181185 def test_all_case_descriptions_are_unique (self ):
182186 """
183187 All cases have unique descriptions in their files.
184188 """
185- for path , cases in files (self .test_files ):
189+ for path , cases in files (self .test_files + self . output_test_files ):
186190 with self .subTest (path = path ):
187191 self .assertUnique (case ["description" ] for case in cases )
188192
189193 def test_all_test_descriptions_are_unique (self ):
190194 """
191195 All test cases have unique test descriptions in their tests.
192196 """
193- for count , case in enumerate (cases (self .test_files )):
197+ for count , case in enumerate (
198+ cases (self .test_files + self .output_test_files )
199+ ):
194200 with self .subTest (description = case ["description" ]):
195201 self .assertUnique (
196202 test ["description" ] for test in case ["tests" ]
197203 )
198204 print (f"Found { count } test cases." )
199205
200206 def test_case_descriptions_do_not_use_modal_verbs (self ):
201- for case in cases (self .test_files ):
207+ for case in cases (self .test_files + self . output_test_files ):
202208 with self .subTest (description = case ["description" ]):
203209 self .assertFollowsDescriptionStyle (case ["description" ])
204210
205211 def test_test_descriptions_do_not_use_modal_verbs (self ):
206- for test in tests (self .test_files ):
212+ for test in tests (self .test_files + self . output_test_files ):
207213 with self .subTest (description = test ["description" ]):
208214 self .assertFollowsDescriptionStyle (test ["description" ])
209215
@@ -218,14 +224,21 @@ class SanityTests(unittest.TestCase):
218224
219225 Validator = VALIDATORS .get (version .name )
220226 if Validator is not None :
227+ # Valid (optional test) schemas contain regexes which
228+ # aren't valid Python regexes, so skip checking it
229+ Validator .FORMAT_CHECKER .checkers .pop ("regex" , None )
230+
221231 test_files = collect (version )
222232 for case in cases (test_files ):
223233 with self .subTest (case = case ):
224234 try :
225- Validator .check_schema (case ["schema" ])
235+ Validator .check_schema (
236+ case ["schema" ],
237+ format_checker = Validator .FORMAT_CHECKER ,
238+ )
226239 except jsonschema .SchemaError :
227240 self .fail (
228- "Found an invalid schema."
241+ "Found an invalid schema. "
229242 "See the traceback for details on why."
230243 )
231244 else :
@@ -236,15 +249,32 @@ class SanityTests(unittest.TestCase):
236249 """
237250 All test files are valid under test-schema.json.
238251 """
239- Validator = jsonschema .validators .validator_for (TESTSUITE_SCHEMA )
240- validator = Validator (TESTSUITE_SCHEMA )
252+ Validator = jsonschema .validators .validator_for (TEST_SCHEMA )
253+ validator = Validator (TEST_SCHEMA )
241254 for path , cases in files (self .test_files ):
242255 with self .subTest (path = path ):
243256 try :
244257 validator .validate (cases )
245258 except jsonschema .ValidationError as error :
246259 self .fail (str (error ))
247260
261+ @unittest .skipIf (jsonschema is None , "Validation library not present!" )
262+ def test_output_suites_are_valid (self ):
263+ """
264+ All output test files are valid under output-test-schema.json.
265+ """
266+ Validator = jsonschema .validators .validator_for (OUTPUT_TEST_SCHEMA )
267+ validator = Validator (OUTPUT_TEST_SCHEMA )
268+ for path , cases in files (self .output_test_files ):
269+ with self .subTest (path = path ):
270+ try :
271+ validator .validate (cases )
272+ except jsonschema .exceptions .RefResolutionError as error :
273+ # python-jsonschema/jsonschema#884
274+ pass
275+ except jsonschema .ValidationError as error :
276+ self .fail (str (error ))
277+
248278
249279def main (arguments ):
250280 if arguments .command == "check" :
@@ -277,15 +307,21 @@ def main(arguments):
277307 try :
278308 import flask
279309 except ImportError :
280- print (textwrap .dedent ("""
310+ print (
311+ textwrap .dedent (
312+ """
281313 The Flask library is required to serve the remote schemas.
282314
283315 You can install it by running `pip install Flask`.
284316
285317 Alternatively, see the `jsonschema_suite remotes` or
286318 `jsonschema_suite dump_remotes` commands to create static files
287319 that can be served with your own web server.
288- """ .strip ("\n " )))
320+ """ .strip (
321+ "\n "
322+ )
323+ )
324+ )
289325 sys .exit (1 )
290326
291327 app = flask .Flask (__name__ )
@@ -309,25 +345,27 @@ check = subparsers.add_parser("check", help="Sanity check the test suite.")
309345
310346flatten = subparsers .add_parser (
311347 "flatten" ,
312- help = "Output a flattened file containing a selected version's test cases."
348+ help = "Output a flattened file containing a selected version's test cases." ,
313349)
314350flatten .add_argument (
315351 "--randomize" ,
316352 action = "store_true" ,
317353 help = "Randomize the order of the outputted cases." ,
318354)
319355flatten .add_argument (
320- "version" , help = "The directory containing the version to output" ,
356+ "version" ,
357+ help = "The directory containing the version to output" ,
321358)
322359
323360remotes = subparsers .add_parser (
324361 "remotes" ,
325362 help = "Output the expected URLs and their associated schemas for remote "
326- "ref tests as a JSON object."
363+ "ref tests as a JSON object." ,
327364)
328365
329366dump_remotes = subparsers .add_parser (
330- "dump_remotes" , help = "Dump the remote ref schemas into a file tree" ,
367+ "dump_remotes" ,
368+ help = "Dump the remote ref schemas into a file tree" ,
331369)
332370dump_remotes .add_argument (
333371 "--update" ,
@@ -343,7 +381,7 @@ dump_remotes.add_argument(
343381
344382serve = subparsers .add_parser (
345383 "serve" ,
346- help = "Start a webserver to serve schemas used by remote ref tests."
384+ help = "Start a webserver to serve schemas used by remote ref tests." ,
347385)
348386
349387if __name__ == "__main__" :
0 commit comments