@@ -30,11 +30,15 @@ else:
30
30
31
31
ROOT_DIR = Path (__file__ ).parent .parent
32
32
SUITE_ROOT_DIR = ROOT_DIR / "tests"
33
+ OUTPUT_ROOT_DIR = ROOT_DIR / "output-tests"
33
34
34
35
REMOTES_DIR = ROOT_DIR / "remotes"
35
36
REMOTES_BASE_URL = "http://localhost:1234/"
36
37
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
+ )
38
42
39
43
40
44
def files (paths ):
@@ -67,7 +71,7 @@ def collect(root_dir):
67
71
"""
68
72
All of the test file paths within the given root directory, recursively.
69
73
"""
70
- return root_dir .glob ( "**/ *.json" )
74
+ return root_dir .rglob ( " *.json" )
71
75
72
76
73
77
def url_for_path (path ):
@@ -80,20 +84,29 @@ def url_for_path(path):
80
84
81
85
return urljoin (
82
86
REMOTES_BASE_URL ,
83
- str (path .relative_to (REMOTES_DIR )).replace ("\\ " , "/" ) # Windows...
87
+ str (path .relative_to (REMOTES_DIR )).replace ("\\ " , "/" ), # Windows...
84
88
)
85
89
86
90
87
91
class SanityTests (unittest .TestCase ):
88
92
@classmethod
89
93
def setUpClass (cls ):
90
94
print (f"Looking for tests in { SUITE_ROOT_DIR } " )
95
+ print (f"Looking for output tests in { OUTPUT_ROOT_DIR } " )
91
96
print (f"Looking for remotes in { REMOTES_DIR } " )
92
97
93
98
cls .test_files = list (collect (SUITE_ROOT_DIR ))
94
99
assert cls .test_files , "Didn't find the test files!"
95
100
print (f"Found { len (cls .test_files )} test files" )
96
101
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
+
97
110
cls .remote_files = list (collect (REMOTES_DIR ))
98
111
assert cls .remote_files , "Didn't find the remote files!"
99
112
print (f"Found { len (cls .remote_files )} remote files" )
@@ -131,22 +144,11 @@ class SanityTests(unittest.TestCase):
131
144
self .assertNotRegex (description , r"\bshould\b" , message )
132
145
self .assertNotRegex (description , r"(?i)\btest(s)? that\b" , message )
133
146
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 ):
146
148
"""
147
- All remote files contain valid JSON.
149
+ All files (tests, output tests, remotes, etc.) contain valid JSON.
148
150
"""
149
- for path in self . remote_files :
151
+ for path in collect ( ROOT_DIR ) :
150
152
with self .subTest (path = path ):
151
153
try :
152
154
json .loads (path .read_text ())
@@ -157,53 +159,57 @@ class SanityTests(unittest.TestCase):
157
159
"""
158
160
All cases have reasonably long descriptions.
159
161
"""
160
- for case in cases (self .test_files ):
162
+ for case in cases (self .test_files + self . output_test_files ):
161
163
with self .subTest (description = case ["description" ]):
162
164
self .assertLess (
163
165
len (case ["description" ]),
164
166
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)." ,
166
168
)
167
169
168
170
def test_all_test_descriptions_have_reasonable_length (self ):
169
171
"""
170
172
All tests have reasonably long descriptions.
171
173
"""
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
+ ):
173
177
with self .subTest (description = test ["description" ]):
174
178
self .assertLess (
175
179
len (test ["description" ]),
176
180
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)." ,
178
182
)
179
183
print (f"Found { count } tests." )
180
184
181
185
def test_all_case_descriptions_are_unique (self ):
182
186
"""
183
187
All cases have unique descriptions in their files.
184
188
"""
185
- for path , cases in files (self .test_files ):
189
+ for path , cases in files (self .test_files + self . output_test_files ):
186
190
with self .subTest (path = path ):
187
191
self .assertUnique (case ["description" ] for case in cases )
188
192
189
193
def test_all_test_descriptions_are_unique (self ):
190
194
"""
191
195
All test cases have unique test descriptions in their tests.
192
196
"""
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
+ ):
194
200
with self .subTest (description = case ["description" ]):
195
201
self .assertUnique (
196
202
test ["description" ] for test in case ["tests" ]
197
203
)
198
204
print (f"Found { count } test cases." )
199
205
200
206
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 ):
202
208
with self .subTest (description = case ["description" ]):
203
209
self .assertFollowsDescriptionStyle (case ["description" ])
204
210
205
211
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 ):
207
213
with self .subTest (description = test ["description" ]):
208
214
self .assertFollowsDescriptionStyle (test ["description" ])
209
215
@@ -218,14 +224,21 @@ class SanityTests(unittest.TestCase):
218
224
219
225
Validator = VALIDATORS .get (version .name )
220
226
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
+
221
231
test_files = collect (version )
222
232
for case in cases (test_files ):
223
233
with self .subTest (case = case ):
224
234
try :
225
- Validator .check_schema (case ["schema" ])
235
+ Validator .check_schema (
236
+ case ["schema" ],
237
+ format_checker = Validator .FORMAT_CHECKER ,
238
+ )
226
239
except jsonschema .SchemaError :
227
240
self .fail (
228
- "Found an invalid schema."
241
+ "Found an invalid schema. "
229
242
"See the traceback for details on why."
230
243
)
231
244
else :
@@ -236,15 +249,32 @@ class SanityTests(unittest.TestCase):
236
249
"""
237
250
All test files are valid under test-schema.json.
238
251
"""
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 )
241
254
for path , cases in files (self .test_files ):
242
255
with self .subTest (path = path ):
243
256
try :
244
257
validator .validate (cases )
245
258
except jsonschema .ValidationError as error :
246
259
self .fail (str (error ))
247
260
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
+
248
278
249
279
def main (arguments ):
250
280
if arguments .command == "check" :
@@ -277,15 +307,21 @@ def main(arguments):
277
307
try :
278
308
import flask
279
309
except ImportError :
280
- print (textwrap .dedent ("""
310
+ print (
311
+ textwrap .dedent (
312
+ """
281
313
The Flask library is required to serve the remote schemas.
282
314
283
315
You can install it by running `pip install Flask`.
284
316
285
317
Alternatively, see the `jsonschema_suite remotes` or
286
318
`jsonschema_suite dump_remotes` commands to create static files
287
319
that can be served with your own web server.
288
- """ .strip ("\n " )))
320
+ """ .strip (
321
+ "\n "
322
+ )
323
+ )
324
+ )
289
325
sys .exit (1 )
290
326
291
327
app = flask .Flask (__name__ )
@@ -309,25 +345,27 @@ check = subparsers.add_parser("check", help="Sanity check the test suite.")
309
345
310
346
flatten = subparsers .add_parser (
311
347
"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." ,
313
349
)
314
350
flatten .add_argument (
315
351
"--randomize" ,
316
352
action = "store_true" ,
317
353
help = "Randomize the order of the outputted cases." ,
318
354
)
319
355
flatten .add_argument (
320
- "version" , help = "The directory containing the version to output" ,
356
+ "version" ,
357
+ help = "The directory containing the version to output" ,
321
358
)
322
359
323
360
remotes = subparsers .add_parser (
324
361
"remotes" ,
325
362
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." ,
327
364
)
328
365
329
366
dump_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" ,
331
369
)
332
370
dump_remotes .add_argument (
333
371
"--update" ,
@@ -343,7 +381,7 @@ dump_remotes.add_argument(
343
381
344
382
serve = subparsers .add_parser (
345
383
"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." ,
347
385
)
348
386
349
387
if __name__ == "__main__" :
0 commit comments