@@ -33,7 +33,7 @@ class InstantiateTests(NbGraderPreprocessor):
33
33
tests = None
34
34
35
35
autotest_filename = Unicode (
36
- "tests .yml" ,
36
+ "autotests .yml" ,
37
37
help = "The filename where automatic testing code is stored"
38
38
).tag (config = True )
39
39
@@ -115,7 +115,7 @@ def preprocess(self, nb, resources):
115
115
raise ValueError (f"Kernel { kernel_name } has not been specified in InstantiateTests.comment_strs" )
116
116
if kernel_name not in self .sanitizers :
117
117
raise ValueError (f"Kernel { kernel_name } has not been specified in InstantiateTests.sanitizers" )
118
- self .log .debug (f "Found kernel { kernel_name } " )
118
+ self .log .debug ("Found kernel %s" , kernel_name )
119
119
resources ["kernel_name" ] = kernel_name
120
120
121
121
# load the template tests file
@@ -124,10 +124,10 @@ def preprocess(self, nb, resources):
124
124
self .global_tests_loaded = True
125
125
126
126
# set up the sanitizer
127
- self .log .debug ('Setting sanitizer for kernel {kernel_name}' )
127
+ self .log .debug ('Setting sanitizer for kernel %s' , kernel_name )
128
128
self .sanitizer = self .sanitizers [kernel_name ]
129
129
#start the kernel
130
- self .log .debug ('Starting client for kernel {kernel_name}' )
130
+ self .log .debug ('Starting client for kernel %s' , kernel_name )
131
131
km , self .kc = start_new_kernel (kernel_name = kernel_name )
132
132
133
133
# run the preprocessor
@@ -214,7 +214,9 @@ def preprocess_cell(self, cell, resources, index):
214
214
# appears in the line before the self.autotest_delimiter token
215
215
use_hash = (self .hashed_delimiter in line [:line .find (self .autotest_delimiter )])
216
216
if use_hash :
217
- self .log .debug ('Hashing delimiter found, using template: ' + self .hash_template )
217
+ if self .hash_template is None :
218
+ raise ValueError ('Found a hashing delimiter, but the hash property has not been set in autotests.yml' )
219
+ self .log .debug ('Hashing delimiter found, using template: %s' , self .hash_template )
218
220
else :
219
221
self .log .debug ('Hashing delimiter not found' )
220
222
@@ -233,18 +235,18 @@ def preprocess_cell(self, cell, resources, index):
233
235
234
236
# generate the test for each snippet
235
237
for snippet in snippets :
236
- self .log .debug ('Running autotest generation for snippet ' + snippet )
238
+ self .log .debug ('Running autotest generation for snippet %s' , snippet )
237
239
238
240
# create a random salt for this test
239
241
if use_hash :
240
242
salt = secrets .token_hex (8 )
241
- self .log .debug ('Using salt: ' + salt )
243
+ self .log .debug ('Using salt: %s' , salt )
242
244
else :
243
245
salt = None
244
246
245
247
# get the normalized(/hashed) template tests for this code snippet
246
248
self .log .debug (
247
- 'Instantiating normalized' + ( '/hashed ' if use_hash else ' ' ) + 'test templates based on type ' )
249
+ 'Instantiating normalized%s test templates based on type' , ' & hashed' if use_hash else ' ' )
248
250
instantiated_tests , test_values , fail_messages = self ._instantiate_tests (snippet , salt )
249
251
250
252
# add all the lines to the cell
@@ -253,21 +255,21 @@ def preprocess_cell(self, cell, resources, index):
253
255
for i in range (len (instantiated_tests )):
254
256
check_code = template .render (snippet = instantiated_tests [i ], value = test_values [i ],
255
257
message = fail_messages [i ])
256
- self .log .debug ('Test: ' + check_code )
258
+ self .log .debug ('Test: %s' , check_code )
257
259
new_lines .append (check_code )
258
260
259
261
# add an empty line after this block of test code
260
262
new_lines .append ('' )
261
263
264
+ # add the final success code and execute it
265
+ if is_grade_flag and self .global_tests_loaded and (self .autotest_delimiter in cell .source ) and (self .success_code is not None ):
266
+ new_lines .append (self .success_code )
267
+ non_autotest_code_lines .append (self .success_code )
268
+
262
269
# run the trailing non-autotest lines, if any remain
263
270
if len (non_autotest_code_lines ) > 0 :
264
271
self ._execute_code_snippet ("\n " .join (non_autotest_code_lines ))
265
272
266
- # add the final success message
267
- if is_grade_flag and self .global_tests_loaded :
268
- if self .autotest_delimiter in cell .source :
269
- new_lines .append (self .success_code )
270
-
271
273
# replace the cell source
272
274
cell .source = "\n " .join (new_lines )
273
275
@@ -279,36 +281,34 @@ def preprocess_cell(self, cell, resources, index):
279
281
# -------------------------------------------------------------------------------------
280
282
def _load_test_template_file (self , resources ):
281
283
"""
282
- attempts to load the tests .yml file within the assignment directory. In case such file is not found
284
+ attempts to load the autotests .yml file within the assignment directory. In case such file is not found
283
285
or perhaps cannot be loaded, it will attempt to load the default_tests.yaml file with the course_directory
284
286
"""
285
- self .log .debug ('loading template tests .yml...' )
286
- self .log .debug (f 'kernel_name: { resources ["kernel_name" ]} ' )
287
+ self .log .debug ('loading template autotests .yml...' )
288
+ self .log .debug ('kernel_name: %s' , resources ["kernel_name" ])
287
289
try :
288
290
with open (os .path .join (resources ['metadata' ]['path' ], self .autotest_filename ), 'r' ) as tests_file :
289
291
tests = yaml .safe_load (tests_file )
290
292
self .log .debug (tests )
291
293
292
294
except FileNotFoundError :
293
- # if there is no tests file, just load a default tests dict
295
+ # if there is no tests file, try to load default tests dict
294
296
self .log .warning (
295
- 'No tests.yml file found in the assignment directory. Loading the default tests.yml file in the course root directory' )
296
- # tests = {}
297
+ 'No autotests.yml file found in the assignment directory. Loading the default autotests.yml file in the course root directory' )
297
298
try :
298
299
with open (os .path .join (self .autotest_filename ), 'r' ) as tests_file :
299
300
tests = yaml .safe_load (tests_file )
300
301
except FileNotFoundError :
301
- # if there is no tests file, just create a default empty tests dict
302
- self .log .warning (
303
- 'No tests.yml file found. If AUTOTESTS appears in testing cells, an error will be thrown.' )
304
- tests = {}
302
+ # if there is not even a default tests file, re-raise the FileNotFound error
303
+ self .log .error ('No autotests.yml file found, but there were autotest directives found in the notebook. ' )
304
+ raise
305
305
except yaml .parser .ParserError as e :
306
- self .log .error ('tests .yml contains invalid YAML code.' )
306
+ self .log .error ('autotests .yml contains invalid YAML code.' )
307
307
self .log .error (e .msg )
308
308
raise
309
309
310
310
except yaml .parser .ParserError as e :
311
- self .log .error ('tests .yml contains invalid YAML code.' )
311
+ self .log .error ('autotests .yml contains invalid YAML code.' )
312
312
self .log .error (e .msg )
313
313
raise
314
314
@@ -322,10 +322,10 @@ def _load_test_template_file(self, resources):
322
322
self .dispatch_template = tests ['dispatch' ]
323
323
324
324
# get the success message template
325
- self .success_code = tests [ 'success' ]
325
+ self .success_code = tests . get ( 'success' , None )
326
326
327
327
# get the hash code template
328
- self .hash_template = tests [ 'hash' ]
328
+ self .hash_template = tests . get ( 'hash' , None )
329
329
330
330
# get the hash code template
331
331
self .check_template = tests ['check' ]
@@ -342,19 +342,19 @@ def _instantiate_tests(self, snippet, salt=None):
342
342
template = j2 .Environment (loader = j2 .BaseLoader ).from_string (self .dispatch_template )
343
343
dispatch_code = template .render (snippet = snippet )
344
344
dispatch_result = self ._execute_code_snippet (dispatch_code )
345
- self .log .debug ('Dispatch result returned by kernel: ' , dispatch_result )
345
+ self .log .debug ('Dispatch result returned by kernel: %s ' , dispatch_result )
346
346
# get the test code; if the type isn't in our dict, just default to 'default'
347
347
# if default isn't in the tests code, this will throw an error
348
348
try :
349
349
tests = self .test_templates_by_type .get (dispatch_result , self .test_templates_by_type ['default' ])
350
350
except KeyError :
351
- self .log .error ('tests .yml must contain a top-level "default" key with corresponding test code' )
351
+ self .log .error ('autotests .yml must contain a top-level "default" key with corresponding test code' )
352
352
raise
353
353
try :
354
354
test_templs = [t ['test' ] for t in tests ]
355
355
fail_msgs = [t ['fail' ] for t in tests ]
356
356
except KeyError :
357
- self .log .error ('each type in tests .yml must have a list of dictionaries with a "test" and "fail" key' )
357
+ self .log .error ('each type in autotests .yml must have a list of dictionaries with a "test" and "fail" key' )
358
358
self .log .error ('the "test" item should store the test template code, '
359
359
'and the "fail" item should store a failure message' )
360
360
raise
0 commit comments