3131
3232from typing import Callable , Literal , Pattern , Match
3333
34+ log = logging .getLogger (__name__ )
35+
3436Verdict = Literal ['AC' , 'TLE' , 'OLE' , 'MLE' , 'RTE' , 'WA' , 'PAC' , 'JE' ]
3537
3638def is_TLE (status : int , may_signal_with_usr1 : bool = False ) -> bool :
@@ -91,6 +93,7 @@ class ProblemAspect:
9193 warnings = 0
9294 bail_on_error = False
9395 _check_res : bool | None = None
96+ consider_warnings_errors = False
9497 basename_regex = re .compile ('^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]$' )
9598 consider_warnings_errors : bool
9699
@@ -110,28 +113,28 @@ def __append_additional_info(msg: str, additional_info: str|None) -> str:
110113
111114 return f'{ msg } :\n ' + '\n ' .join (' ' * 8 + line for line in lines )
112115
113- def error (self , msg : str , additional_info : str | None = None ) -> None :
116+ def __init__ (self , name ):
117+ self .log = log .getChild (name )
118+
119+ def error (self , msg : str , additional_info : str | None = None , * args ) -> None :
114120 self ._check_res = False
115121 ProblemAspect .errors += 1
116- logging . error ('in %s: %s' , self , ProblemAspect .__append_additional_info (msg , additional_info ))
122+ self . log . error (ProblemAspect .__append_additional_info (msg , additional_info ), * args )
117123 if ProblemAspect .bail_on_error :
118124 raise VerifyError (msg )
119125
120- def warning (self , msg : str , additional_info : str | None = None ) -> None :
126+ def warning (self , msg : str , additional_info : str | None = None , * args ) -> None :
121127 if ProblemAspect .consider_warnings_errors :
122- self .error (msg )
128+ self .error (msg , additional_info , * args )
123129 return
124130 ProblemAspect .warnings += 1
125- logging .warning ('in %s: %s' , self , ProblemAspect .__append_additional_info (msg , additional_info ))
126-
127- def msg (self , msg : str ) -> None :
128- print (msg )
131+ self .log .warning (ProblemAspect .__append_additional_info (msg , additional_info ), * args )
129132
130- def info (self , msg : str ) -> None :
131- logging . info (': %s' , msg )
133+ def info (self , msg : str , * args ) -> None :
134+ self . log . info (msg , * args )
132135
133- def debug (self , msg : str ) -> None :
134- logging . debug (': %s' , msg )
136+ def debug (self , msg : str , * args ) -> None :
137+ self . log . debug (msg , * args )
135138
136139 def check_basename (self , path : str ) -> None :
137140 basename = os .path .basename (path )
@@ -140,6 +143,7 @@ def check_basename(self, path: str) -> None:
140143
141144class TestCase (ProblemAspect ):
142145 def __init__ (self , problem : Problem , base : str , testcasegroup : TestCaseGroup ):
146+ super ().__init__ (f"{ problem .shortname } .test.{ testcasegroup .name } .{ os .path .basename (base )} " )
143147 self ._base = base
144148 self .infile = f'{ base } .in'
145149 self .ansfile = f'{ base } .ans'
@@ -248,6 +252,8 @@ def _run_submission_real(self, sub, args: argparse.Namespace, timelim: int, time
248252 return (res , res_low , res_high , True )
249253
250254 outfile = os .path .join (self ._problem .tmpdir , 'output' )
255+ errfile = os .path .join (self ._problem .tmpdir , 'error' )
256+
251257 if sys .stdout .isatty ():
252258 msg = f'Running { sub } on { self } ...'
253259 sys .stdout .write (msg )
@@ -256,13 +262,22 @@ def _run_submission_real(self, sub, args: argparse.Namespace, timelim: int, time
256262 if self ._problem .is_interactive :
257263 res_high = self ._problem .output_validators .validate_interactive (self , sub , timelim_high , self ._problem .submissions )
258264 else :
259- status , runtime = sub .run (self .infile , outfile ,
265+ status , runtime = sub .run (infile = self .infile , outfile = outfile , errfile = errfile ,
260266 timelim = timelim_high + 1 ,
261267 memlim = self ._problem .config .get ('limits' )['memory' ], set_work_dir = True )
262268 if is_TLE (status ) or runtime > timelim_high :
263269 res_high = SubmissionResult ('TLE' )
264270 elif is_RTE (status ):
265- res_high = SubmissionResult ('RTE' )
271+ if os .path .isfile (errfile ):
272+ try :
273+ with open (errfile , mode = "rt" ) as f :
274+ info = f .read ()
275+ except IOError :
276+ self .info ("Failed to read error file %s" , errfile )
277+ info = None
278+ else :
279+ info = None
280+ res_high = SubmissionResult ('RTE' , additional_info = info )
266281 else :
267282 res_high = self ._problem .output_validators .validate (self , outfile )
268283 res_high .runtime = runtime
@@ -318,8 +333,13 @@ def __init__(self, problem: Problem, datadir: str, parent: TestCaseGroup|None=No
318333 self ._parent = parent
319334 self ._problem = problem
320335 self ._datadir = datadir
336+ self .name = os .path .relpath (os .path .abspath (self ._datadir ),
337+ os .path .abspath (self ._problem .probdir )).replace ("/" , "." )
338+
339+ super ().__init__ (f"{ problem .shortname } .test.{ self .name } " )
340+
321341 self ._seen_oob_scores = False
322- self .debug (f' Loading test data group { datadir } ' )
342+ self .debug (' Loading test data group %s' , datadir )
323343 configfile = os .path .join (self ._datadir , 'testdata.yaml' )
324344 self .config = {}
325345 if os .path .isfile (configfile ):
@@ -374,7 +394,7 @@ def __init__(self, problem: Problem, datadir: str, parent: TestCaseGroup|None=No
374394
375395
376396 def __str__ (self ) -> str :
377- return f'test case group { os . path . relpath ( self ._datadir , os . path . join ( self . _problem . probdir )) } '
397+ return f'test case group { self .name } '
378398
379399 def set_symlinks (self ) -> None :
380400 for sub in self ._items :
@@ -627,6 +647,7 @@ class ProblemConfig(ProblemAspect):
627647 _VALID_LICENSES = ['unknown' , 'public domain' , 'cc0' , 'cc by' , 'cc by-sa' , 'educational' , 'permission' ]
628648
629649 def __init__ (self , problem : Problem ):
650+ super ().__init__ (f"{ problem .shortname } .config" )
630651 self .debug (' Loading problem config' )
631652 self ._problem = problem
632653 self .configfile = os .path .join (problem .probdir , 'problem.yaml' )
@@ -1061,6 +1082,7 @@ def check(self, args: argparse.Namespace) -> bool:
10611082
10621083class ProblemStatement (ProblemAspect ):
10631084 def __init__ (self , problem : Problem ):
1085+ super ().__init__ (f"{ problem .shortname } .statement" )
10641086 self .debug (' Loading problem statement' )
10651087 self ._problem = problem
10661088 self .languages = []
@@ -1136,6 +1158,7 @@ class Attachments(ProblemAspect):
11361158 """
11371159
11381160 def __init__ (self , problem : Problem ):
1161+ super ().__init__ (f"{ problem .shortname } .attachments" )
11391162 attachments_path = os .path .join (problem .probdir , 'attachments' )
11401163 self .attachments : list [str ] = []
11411164 if os .path .isdir (attachments_path ):
@@ -1165,7 +1188,7 @@ def __str__(self) -> str:
11651188
11661189_JUNK_CASES = [
11671190 ('an empty file' , b'' ),
1168- ('a binary file with byte values 0 up to 256 ' , bytearray (x for x in range (256 ))),
1191+ ('a binary file with byte values 0 up to 127 ' , bytearray (x for x in range (127 ))),
11691192 ('a text file with the ASCII characters 32 up to 127' , bytearray (x for x in range (32 , 127 ))),
11701193 ('a random text file with printable ASCII characters' , bytearray (random .choice (string .printable .encode ('utf8' )) for _ in range (200 ))),
11711194]
@@ -1185,6 +1208,7 @@ def _build_junk_modifier(desc: str, pattern: str, repl: str|Callable[[Match], st
11851208class InputFormatValidators (ProblemAspect ):
11861209
11871210 def __init__ (self , problem : Problem ):
1211+ super ().__init__ (f"{ problem .shortname } .input_validator" )
11881212 self ._problem = problem
11891213 input_validators_path = os .path .join (problem .probdir , 'input_format_validators' )
11901214 if os .path .isdir (input_validators_path ):
@@ -1304,6 +1328,7 @@ class Graders(ProblemAspect):
13041328 _default_grader = run .get_tool ('default_grader' )
13051329
13061330 def __init__ (self , problem : Problem ):
1331+ super ().__init__ (f"{ problem .shortname } .grader" )
13071332 self ._problem = problem
13081333 self ._graders : list = run .find_programs (os .path .join (problem .probdir , 'graders' ),
13091334 language_config = problem .language_config ,
@@ -1382,7 +1407,7 @@ def grade(self, sub_results: list[SubmissionResult], testcasegroup: TestCaseGrou
13821407 # TODO: check that all graders give same result
13831408
13841409 if not shadow_result :
1385- self .info (f'Grade on { testcasegroup } is { verdict } ({ score } )' )
1410+ self .debug (f'Grade on { testcasegroup } is { verdict } ({ score } )' )
13861411
13871412 return (verdict , score )
13881413
@@ -1392,6 +1417,7 @@ class OutputValidators(ProblemAspect):
13921417
13931418
13941419 def __init__ (self , problem : Problem ):
1420+ super ().__init__ (f"{ problem .shortname } .output_validator" )
13951421 self ._problem = problem
13961422 self ._validators = run .find_programs (os .path .join (problem .probdir ,
13971423 'output_validators' ),
@@ -1585,11 +1611,28 @@ def validate(self, testcase: TestCase, submission_output: str) -> SubmissionResu
15851611 for val in self ._actual_validators ():
15861612 if val is not None and val .compile ()[0 ]:
15871613 feedbackdir = tempfile .mkdtemp (prefix = 'feedback' , dir = self ._problem .tmpdir )
1614+ validator_output = tempfile .mkdtemp (prefix = 'checker_out' , dir = self ._problem .tmpdir )
1615+ outfile = validator_output + "/out.txt"
1616+ errfile = validator_output + "/err.txt"
15881617 status , runtime = val .run (submission_output ,
15891618 args = [testcase .infile , testcase .ansfile , feedbackdir ] + flags ,
1590- timelim = val_timelim , memlim = val_memlim )
1619+ timelim = val_timelim , memlim = val_memlim ,
1620+ outfile = outfile , errfile = errfile )
1621+ if self .log .isEnabledFor (logging .DEBUG ):
1622+ try :
1623+ with open (outfile , mode = "rt" ) as f :
1624+ output = f .read ()
1625+ if output :
1626+ self .log .debug ("Validator output:\n %s" , output )
1627+ with open (errfile , mode = "rt" ) as f :
1628+ error = f .read ()
1629+ if error :
1630+ self .log .debug ("Validator stderr:\n %s" , error )
1631+ except IOError as e :
1632+ self .info ("Failed to read validator output: %s" , e )
15911633 res = self ._parse_validator_results (val , status , feedbackdir , testcase )
15921634 shutil .rmtree (feedbackdir )
1635+ shutil .rmtree (validator_output )
15931636 if res .verdict != 'AC' :
15941637 return res
15951638
@@ -1609,6 +1652,7 @@ class Submissions(ProblemAspect):
16091652 ]
16101653
16111654 def __init__ (self , problem : Problem ):
1655+ super ().__init__ (f"{ problem .shortname } .submission" )
16121656 self ._submissions = {}
16131657 self ._problem = problem
16141658 srcdir = os .path .join (problem .probdir , 'submissions' )
@@ -1742,6 +1786,7 @@ class Problem(ProblemAspect):
17421786 def __init__ (self , probdir : str ):
17431787 self .probdir = os .path .realpath (probdir )
17441788 self .shortname : str | None = os .path .basename (self .probdir )
1789+ super ().__init__ (self .shortname )
17451790 self .language_config = languages .load_language_config ()
17461791
17471792 def __enter__ (self ) -> Problem :
0 commit comments