@@ -40,14 +40,14 @@ def files(paths):
40
40
Each test file in the provided paths, as an array of test cases.
41
41
"""
42
42
for path in paths :
43
- yield json .loads (path .read_text ())
43
+ yield path , json .loads (path .read_text ())
44
44
45
45
46
46
def cases (paths ):
47
47
"""
48
48
Each test case within each file in the provided paths.
49
49
"""
50
- for test_file in files (paths ):
50
+ for _ , test_file in files (paths ):
51
51
yield from test_file
52
52
53
53
@@ -82,52 +82,115 @@ class SanityTests(unittest.TestCase):
82
82
assert cls .remote_files , "Didn't find the remote files!"
83
83
print (f"Found { len (cls .remote_files )} remote files" )
84
84
85
+ def assertUnique (self , iterable ):
86
+ """
87
+ Assert that the elements of an iterable are unique.
88
+ """
89
+
90
+ seen , duplicated = set (), set ()
91
+ for each in iterable :
92
+ if each in seen :
93
+ duplicated .add (each )
94
+ seen .add (each )
95
+ self .assertFalse (duplicated , "Elements are not unique." )
96
+
85
97
def test_all_test_files_are_valid_json (self ):
86
98
"""
87
99
All test files contain valid JSON.
88
100
"""
89
101
for path in self .test_files :
90
- try :
91
- json .loads (path .read_text ())
92
- except ValueError as error :
93
- self .fail (f"{ path } contains invalid JSON ({ error } )" )
102
+ with self .subTest (path = path ):
103
+ try :
104
+ json .loads (path .read_text ())
105
+ except ValueError as error :
106
+ self .fail (f"{ path } contains invalid JSON ({ error } )" )
94
107
95
108
def test_all_remote_files_are_valid_json (self ):
96
109
"""
97
110
All remote files contain valid JSON.
98
111
"""
99
112
for path in self .remote_files :
100
- try :
101
- json .loads (path .read_text ())
102
- except ValueError as error :
103
- self .fail (f"{ path } contains invalid JSON ({ error } )" )
113
+ with self .subTest (path = path ):
114
+ try :
115
+ json .loads (path .read_text ())
116
+ except ValueError as error :
117
+ self .fail (f"{ path } contains invalid JSON ({ error } )" )
104
118
105
- def test_all_descriptions_have_reasonable_length (self ):
119
+ def test_all_case_descriptions_have_reasonable_length (self ):
120
+ """
121
+ All cases have reasonably long descriptions.
122
+ """
123
+ for case in cases (self .test_files ):
124
+ with self .subTest (description = case ["description" ]):
125
+ self .assertLess (
126
+ len (case ["description" ]),
127
+ 150 ,
128
+ "Description is too long (keep it to less than 150 chars)."
129
+ )
130
+
131
+ def test_all_test_descriptions_have_reasonable_length (self ):
106
132
"""
107
133
All tests have reasonably long descriptions.
108
134
"""
109
135
for count , test in enumerate (tests (self .test_files )):
110
- description = test ["description" ]
111
- self .assertLess (
112
- len (description ),
113
- 70 ,
114
- f" { description !r } is too long! (keep it to less than 70 chars)"
115
- )
136
+ with self . subTest ( description = test ["description" ]):
137
+ self .assertLess (
138
+ len (test [ " description" ] ),
139
+ 70 ,
140
+ "Description is too long (keep it to less than 70 chars). "
141
+ )
116
142
print (f"Found { count } tests." )
117
143
118
- def test_all_descriptions_are_unique (self ):
144
+ def test_all_case_descriptions_are_unique (self ):
145
+ """
146
+ All cases have unique descriptions in their files.
147
+ """
148
+ for path , cases in files (self .test_files ):
149
+ with self .subTest (path = path ):
150
+ self .assertUnique (case ["description" ] for case in cases )
151
+
152
+ def test_all_test_descriptions_are_unique (self ):
119
153
"""
120
154
All test cases have unique test descriptions in their tests.
121
155
"""
122
156
for count , case in enumerate (cases (self .test_files )):
123
- descriptions = set (test ["description" ] for test in case ["tests" ])
124
- self .assertEqual (
125
- len (descriptions ),
126
- len (case ["tests" ]),
127
- f"{ case !r} contains a duplicate description" ,
128
- )
157
+ with self .subTest (description = case ["description" ]):
158
+ self .assertUnique (
159
+ test ["description" ] for test in case ["tests" ]
160
+ )
129
161
print (f"Found { count } test cases." )
130
162
163
+ def test_descriptions_do_not_use_modal_verbs (self ):
164
+ """
165
+ Instead of saying "test that X frobs" or "X should frob" use "X frobs".
166
+
167
+ See e.g. https://jml.io/pages/test-docstrings.html
168
+
169
+ This test isn't comprehensive (it doesn't catch all the extra
170
+ verbiage there), but it's just to catch whatever it manages to
171
+ cover.
172
+ """
173
+
174
+ message = (
175
+ "In descriptions, don't say 'Test that X frobs' or 'X should "
176
+ "frob' or 'X should be valid'. Just say 'X frobs' or 'X is "
177
+ "valid'. It's shorter, and the test suite is entirely about "
178
+ "what *should* be already. "
179
+ "See https://jml.io/pages/test-docstrings.html for help."
180
+ )
181
+ for test in tests (self .test_files ):
182
+ with self .subTest (description = test ["description" ]):
183
+ self .assertNotRegex (
184
+ test ["description" ],
185
+ r"\bshould\b" ,
186
+ message ,
187
+ )
188
+ self .assertNotRegex (
189
+ test ["description" ],
190
+ r"(?i)\btest(s)? that\b" ,
191
+ message ,
192
+ )
193
+
131
194
@unittest .skipIf (jsonschema is None , "Validation library not present!" )
132
195
def test_all_schemas_are_valid (self ):
133
196
"""
@@ -141,12 +204,14 @@ class SanityTests(unittest.TestCase):
141
204
if Validator is not None :
142
205
test_files = collect (version )
143
206
for case in cases (test_files ):
144
- try :
145
- Validator .check_schema (case ["schema" ])
146
- except jsonschema .SchemaError as error :
147
- self .fail (
148
- f"{ case } contains an invalid schema ({ error } )" ,
149
- )
207
+ with self .subTest (case = case ):
208
+ try :
209
+ Validator .check_schema (case ["schema" ])
210
+ except jsonschema .SchemaError :
211
+ self .fail (
212
+ "Found an invalid schema."
213
+ "See the traceback for details on why."
214
+ )
150
215
else :
151
216
warnings .warn (f"No schema validator for { version .name } " )
152
217
@@ -157,11 +222,12 @@ class SanityTests(unittest.TestCase):
157
222
"""
158
223
Validator = jsonschema .validators .validator_for (TESTSUITE_SCHEMA )
159
224
validator = Validator (TESTSUITE_SCHEMA )
160
- for tests in files (self .test_files ):
161
- try :
162
- validator .validate (tests )
163
- except jsonschema .ValidationError as error :
164
- self .fail (str (error ))
225
+ for path , cases in files (self .test_files ):
226
+ with self .subTest (path = path ):
227
+ try :
228
+ validator .validate (cases )
229
+ except jsonschema .ValidationError as error :
230
+ self .fail (str (error ))
165
231
166
232
167
233
def main (arguments ):
0 commit comments