@@ -69,6 +69,8 @@ class Batch(TaskType):
6969 input, correct output and user output) and should write the
7070 outcome to stdout and the text to stderr.
7171
72+ Note that this class is used as a base class for the BatchAndOutput task
73+ type.
7274 """
7375 # Codename of the checker, if it is used.
7476 CHECKER_CODENAME = "checker"
@@ -114,7 +116,7 @@ class Batch(TaskType):
114116 ACCEPTED_PARAMETERS = [_COMPILATION , _USE_FILE , _EVALUATION ]
115117
116118 @property
117- def name (self ):
119+ def name (self ) -> str :
118120 """See TaskType.name."""
119121 # TODO add some details if a grader/comparator is used, etc...
120122 return "Batch"
@@ -145,7 +147,10 @@ def get_compilation_commands(self, submission_format):
145147 codenames_to_compile = []
146148 if self ._uses_grader ():
147149 codenames_to_compile .append (self .GRADER_BASENAME + ".%l" )
148- codenames_to_compile .extend (submission_format )
150+ # For regular batch, all parts of the submission format end with %l.
151+ # For batch+output only, some might not.
152+ codenames_to_compile .extend (
153+ [x for x in submission_format if x .endswith ('.%l' )])
149154 res = dict ()
150155 for language in LANGUAGES :
151156 source_ext = language .source_extension
@@ -187,18 +192,14 @@ def _executable_filename(codenames: Iterable[str], language: Language) -> str:
187192 return: a deterministic executable name.
188193
189194 """
190- name = "_" .join (sorted (codename .replace (".%l" , "" )
191- for codename in codenames ))
195+ name = "_" .join (sorted (codename .replace (".%l" , "" )
196+ for codename in codenames ))
192197 return name + language .executable_extension
193198
194- def compile (self , job , file_cacher ):
195- """See TaskType.compile."""
199+ def _do_compile (self , job , file_cacher ):
196200 language = get_language (job .language )
197201 source_ext = language .source_extension
198202
199- if not check_files_number (job , 1 , or_more = True ):
200- return
201-
202203 # Create the list of filenames to be passed to the compiler. If we use
203204 # a grader, it needs to be in first position in the command line, and
204205 # we check that it exists.
@@ -215,6 +216,8 @@ def compile(self, job, file_cacher):
215216 job .managers [grader_filename ].digest
216217 # User's submitted file(s) (copy and add to compilation).
217218 for codename , file_ in job .files .items ():
219+ if not codename .endswith (".%l" ):
220+ continue
218221 filename = codename .replace (".%l" , source_ext )
219222 filenames_to_compile .append (filename )
220223 filenames_and_digests_to_get [filename ] = file_ .digest
@@ -256,16 +259,19 @@ def compile(self, job, file_cacher):
256259 # Cleanup.
257260 delete_sandbox (sandbox , job .success , job .keep_sandbox )
258261
259- def evaluate (self , job , file_cacher ):
260- """See TaskType.evaluate ."""
261- if not check_executables_number (job , 1 ):
262+ def compile (self , job , file_cacher ):
263+ """See TaskType.compile ."""
264+ if not check_files_number (job , 1 , or_more = True ):
262265 return
263266
267+ self ._do_compile (job , file_cacher )
268+
269+ def _execution_step (self , job , file_cacher ):
264270 # Prepare the execution
265271 executable_filename = next (iter (job .executables .keys ()))
266272 language = get_language (job .language )
267273 main = self .GRADER_BASENAME if self ._uses_grader () \
268- else os .path .splitext (executable_filename )[0 ]
274+ else os .path .splitext (executable_filename )[0 ]
269275 commands = language .get_evaluation_commands (
270276 executable_filename , main = main )
271277 executables_to_get = {
@@ -311,6 +317,7 @@ def evaluate(self, job, file_cacher):
311317
312318 outcome = None
313319 text = None
320+ output_file_params = None
314321
315322 # Error in the sandbox: nothing to do!
316323 if not box_success :
@@ -347,20 +354,41 @@ def evaluate(self, job, file_cacher):
347354 outcome = 0.0
348355 text = [N_ ("Execution completed successfully" )]
349356
350- # Otherwise evaluate the output file.
357+ # Otherwise prepare to evaluate the output file.
351358 else :
352- box_success , outcome , text = eval_output (
353- file_cacher , job ,
354- self .CHECKER_CODENAME
355- if self ._uses_checker () else None ,
356- user_output_path = sandbox .relative_path (
359+ output_file_params = {
360+ 'user_output_path' : sandbox .relative_path (
357361 self ._actual_output ),
358- user_output_filename = self .output_filename )
362+ 'user_output_filename' : self .output_filename }
363+
364+ return outcome , text , output_file_params , stats , box_success , sandbox
365+
366+ def _evaluate_step (self , job , file_cacher , output_file_params , outcome , text , stats , box_success , sandbox , extra_args ):
367+ if box_success :
368+ assert (output_file_params is None ) == (outcome is not None )
369+ if output_file_params is not None :
370+ box_success , outcome , text = eval_output (
371+ file_cacher , job ,
372+ self .CHECKER_CODENAME
373+ if self ._uses_checker () else None ,
374+ ** output_file_params , extra_args = extra_args )
359375
360376 # Fill in the job with the results.
361377 job .success = box_success
362378 job .outcome = str (outcome ) if outcome is not None else None
363379 job .text = text
364380 job .plus = stats
365381
366- delete_sandbox (sandbox , job .success , job .keep_sandbox )
382+ if sandbox is not None :
383+ delete_sandbox (sandbox , job .success , job .keep_sandbox )
384+
385+ def evaluate (self , job , file_cacher ):
386+ """See TaskType.evaluate."""
387+ if not check_executables_number (job , 1 ):
388+ return
389+
390+ outcome , text , output_file_params , stats , box_success , sandbox = self ._execution_step (
391+ job , file_cacher )
392+
393+ self ._evaluate_step (job , file_cacher , output_file_params ,
394+ outcome , text , stats , box_success , sandbox , extra_args = None )
0 commit comments