Skip to content

Commit aaac68f

Browse files
Improved logging, fix unicode error, include_dir buildrun
1 parent 563f0d1 commit aaac68f

File tree

6 files changed

+86
-34
lines changed

6 files changed

+86
-34
lines changed

problemtools/problem2html.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import logging
88
import subprocess
99

10-
import plasTeX.TeX
11-
import plasTeX.Logging
12-
13-
from .ProblemPlasTeX import ProblemRenderer
14-
from .ProblemPlasTeX import ProblemsetMacros
15-
from . import template
1610

1711
def convert(options: argparse.Namespace) -> None:
12+
import plasTeX.TeX
13+
import plasTeX.Logging
14+
15+
from .ProblemPlasTeX import ProblemRenderer
16+
from .ProblemPlasTeX import ProblemsetMacros
17+
from . import template
18+
1819
problem = os.path.realpath(options.problem)
1920

2021
problembase = os.path.splitext(os.path.basename(problem))[0]

problemtools/run/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,18 @@ def get_program(path, language_config=None, work_dir=None, include_dir=None,
102102
files = [path]
103103
else:
104104
build = os.path.join(path, 'build')
105-
if os.path.isfile(build) and os.access(path, os.X_OK):
105+
if os.path.isfile(build) and os.access(build, os.X_OK):
106106
return BuildRun(path, work_dir)
107107
files = rutil.list_files_recursive(path)
108108

109109
if language_config is not None:
110110
lang = language_config.detect_language(files)
111111
if lang is not None:
112-
return SourceCode(path, lang,
113-
work_dir=work_dir, include_dir=include_dir)
112+
if include_dir is not None:
113+
lang_dir = os.path.join(include_dir, lang.lang_id)
114+
build = os.path.join(lang_dir, 'build')
115+
if os.path.isfile(build) and os.access(build, os.X_OK):
116+
return BuildRun(path, work_dir=work_dir, include_dir=lang_dir)
117+
118+
return SourceCode(path, lang, work_dir=work_dir, include_dir=include_dir)
114119
return None

problemtools/run/buildrun.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
from .program import Program
1313
from . import rutil
1414

15+
log = logging.getLogger(__file__)
16+
1517

1618
class BuildRun(Program):
1719
"""Class for build/run-script program.
1820
"""
1921

20-
def __init__(self, path, work_dir=None):
22+
def __init__(self, path, work_dir=None, include_dir=None):
2123
"""Instantiate BuildRun object.
2224
2325
Args:
@@ -28,12 +30,6 @@ def __init__(self, path, work_dir=None):
2830
if not os.path.isdir(path):
2931
raise ProgramError('%s is not a directory' % path)
3032

31-
build = os.path.join(path, 'build')
32-
if not os.path.isfile(build):
33-
raise ProgramError('%s does not have a build script' % path)
34-
if not os.access(build, os.X_OK):
35-
raise ProgramError('%s/build is not executable' % path)
36-
3733
if work_dir is None:
3834
work_dir = tempfile.mkdtemp()
3935

@@ -47,7 +43,14 @@ def __init__(self, path, work_dir=None):
4743
os.makedirs(self.path)
4844

4945
rutil.add_files(path, self.path)
46+
if include_dir is not None and os.path.isdir(include_dir):
47+
rutil.add_files(include_dir, self.path)
5048

49+
build = os.path.join(self.path, 'build')
50+
if not os.path.isfile(build):
51+
raise ProgramError('%s does not have a build script' % path)
52+
if not os.access(build, os.X_OK):
53+
raise ProgramError('%s/build is not executable' % path)
5154

5255
def __str__(self):
5356
"""String representation"""
@@ -65,8 +68,8 @@ def compile(self):
6568
run = os.path.join(self.path, 'run')
6669

6770
if status:
68-
logging.debug('Build script failed (status %d) when compiling %s\n', status, self.name)
69-
self._compile_result = (False, 'build script failed with exit code %d' % (status))
71+
log.debug('Build script failed (status %d) when compiling %s', status, self.name)
72+
self._compile_result = (False, f'build script failed with exit code {status:d}')
7073
elif not os.path.isfile(run) or not os.access(run, os.X_OK):
7174
self._compile_result = (False, 'build script did not produce an executable called "run"')
7275
else:

problemtools/run/program.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
from .errors import ProgramError
1010

11+
log = logging.getLogger(__name__)
12+
13+
1114
class Program(object):
1215
"""Abstract base class for programs.
1316
"""
@@ -70,7 +73,7 @@ def should_skip_memory_rlimit(self):
7073

7174
@staticmethod
7275
def __run_wait(argv, infile, outfile, errfile, timelim, memlim, working_directory=None):
73-
logging.debug('run "%s < %s > %s 2> %s"',
76+
log.debug('run "%s < %s > %s 2> %s"',
7477
' '.join(argv), infile, outfile, errfile)
7578
pid = os.fork()
7679
if pid == 0: # child
@@ -111,7 +114,7 @@ def __run_wait(argv, infile, outfile, errfile, timelim, memlim, working_director
111114
print(exc)
112115
os.kill(os.getpid(), signal.SIGTERM)
113116
# Unreachable
114-
logging.error("Unreachable part of run_wait reached")
117+
log.error("Unreachable part of run_wait reached")
115118
os.kill(os.getpid(), signal.SIGTERM)
116119
(pid, status, rusage) = os.wait4(pid, 0)
117120
return status, rusage.ru_utime + rusage.ru_stime

problemtools/run/source.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from .program import Program
1313
from . import rutil
1414

15+
log = logging.getLogger(__name__)
16+
17+
1518
class SourceCode(Program):
1619
"""Class representing a program provided by source code.
1720
"""
@@ -103,7 +106,7 @@ def compile(self):
103106
if not os.path.isfile(compiler) or not os.access(compiler, os.X_OK):
104107
return (False, '%s does not seem to be installed, expected to find compiler at %s' % (self.language.name, compiler))
105108

106-
logging.debug('compile command: %s', command)
109+
log.debug('compile command: %s', command)
107110

108111
try:
109112
subprocess.check_output(command, stderr=subprocess.STDOUT)

problemtools/verifyproblem.py

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
from typing import Callable, Literal, Pattern, Match
3333

34+
log = logging.getLogger(__name__)
35+
3436
Verdict = Literal['AC', 'TLE', 'OLE', 'MLE', 'RTE', 'WA', 'PAC', 'JE']
3537

3638
def 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

@@ -113,25 +116,25 @@ def __append_additional_info(msg: str, additional_info: str|None) -> str:
113116
def error(self, msg: str, additional_info: str|None=None) -> None:
114117
self._check_res = False
115118
ProblemAspect.errors += 1
116-
logging.error('in %s: %s', self, ProblemAspect.__append_additional_info(msg, additional_info))
119+
self.log.error(ProblemAspect.__append_additional_info(msg, additional_info))
117120
if ProblemAspect.bail_on_error:
118121
raise VerifyError(msg)
119122

123+
def __init__(self, name):
124+
self.log = log.getChild(name)
125+
120126
def warning(self, msg: str, additional_info: str|None=None) -> None:
121127
if ProblemAspect.consider_warnings_errors:
122128
self.error(msg)
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))
129132

130133
def info(self, msg: str) -> None:
131-
logging.info(': %s', msg)
134+
self.log.info(msg)
132135

133136
def debug(self, msg: str) -> None:
134-
logging.debug(': %s', msg)
137+
self.log.debug(msg)
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

141144
class 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,16 @@ 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+
with open(errfile, mode="rt") as f:
273+
info = f.read()
274+
res_high = SubmissionResult('RTE', additional_info=info)
266275
else:
267276
res_high = self._problem.output_validators.validate(self, outfile)
268277
res_high.runtime = runtime
@@ -318,8 +327,13 @@ def __init__(self, problem: Problem, datadir: str, parent: TestCaseGroup|None=No
318327
self._parent = parent
319328
self._problem = problem
320329
self._datadir = datadir
330+
self.name = os.path.relpath(os.path.abspath(self._datadir),
331+
os.path.abspath(self._problem.probdir)).replace("/", ".")
332+
333+
super().__init__(f"{problem.shortname}.test.{self.name}")
334+
321335
self._seen_oob_scores = False
322-
self.debug(f' Loading test data group {datadir}')
336+
self.debug('Loading test data group %s' % datadir)
323337
configfile = os.path.join(self._datadir, 'testdata.yaml')
324338
self.config = {}
325339
if os.path.isfile(configfile):
@@ -374,7 +388,7 @@ def __init__(self, problem: Problem, datadir: str, parent: TestCaseGroup|None=No
374388

375389

376390
def __str__(self) -> str:
377-
return f'test case group {os.path.relpath(self._datadir, os.path.join(self._problem.probdir))}'
391+
return f'test case group {self.name}'
378392

379393
def set_symlinks(self) -> None:
380394
for sub in self._items:
@@ -627,6 +641,7 @@ class ProblemConfig(ProblemAspect):
627641
_VALID_LICENSES = ['unknown', 'public domain', 'cc0', 'cc by', 'cc by-sa', 'educational', 'permission']
628642

629643
def __init__(self, problem: Problem):
644+
super().__init__(f"{problem.shortname}.config")
630645
self.debug(' Loading problem config')
631646
self._problem = problem
632647
self.configfile = os.path.join(problem.probdir, 'problem.yaml')
@@ -1061,6 +1076,7 @@ def check(self, args: argparse.Namespace) -> bool:
10611076

10621077
class ProblemStatement(ProblemAspect):
10631078
def __init__(self, problem: Problem):
1079+
super().__init__(f"{problem.shortname}.statement")
10641080
self.debug(' Loading problem statement')
10651081
self._problem = problem
10661082
self.languages = []
@@ -1136,6 +1152,7 @@ class Attachments(ProblemAspect):
11361152
"""
11371153

11381154
def __init__(self, problem: Problem):
1155+
super().__init__(f"{problem.shortname}.attachments")
11391156
attachments_path = os.path.join(problem.probdir, 'attachments')
11401157
self.attachments: list[str] = []
11411158
if os.path.isdir(attachments_path):
@@ -1185,6 +1202,7 @@ def _build_junk_modifier(desc: str, pattern: str, repl: str|Callable[[Match], st
11851202
class InputFormatValidators(ProblemAspect):
11861203

11871204
def __init__(self, problem: Problem):
1205+
super().__init__(f"{problem.shortname}.input_validator")
11881206
self._problem = problem
11891207
input_validators_path = os.path.join(problem.probdir, 'input_format_validators')
11901208
if os.path.isdir(input_validators_path):
@@ -1304,6 +1322,7 @@ class Graders(ProblemAspect):
13041322
_default_grader = run.get_tool('default_grader')
13051323

13061324
def __init__(self, problem: Problem):
1325+
super().__init__(f"{problem.shortname}.grader")
13071326
self._problem = problem
13081327
self._graders: list = run.find_programs(os.path.join(problem.probdir, 'graders'),
13091328
language_config=problem.language_config,
@@ -1382,7 +1401,7 @@ def grade(self, sub_results: list[SubmissionResult], testcasegroup: TestCaseGrou
13821401
# TODO: check that all graders give same result
13831402

13841403
if not shadow_result:
1385-
self.info(f'Grade on {testcasegroup} is {verdict} ({score})')
1404+
self.debug(f'Grade on {testcasegroup} is {verdict} ({score})')
13861405

13871406
return (verdict, score)
13881407

@@ -1392,6 +1411,7 @@ class OutputValidators(ProblemAspect):
13921411

13931412

13941413
def __init__(self, problem: Problem):
1414+
super().__init__(f"{problem.shortname}.output_validator")
13951415
self._problem = problem
13961416
self._validators = run.find_programs(os.path.join(problem.probdir,
13971417
'output_validators'),
@@ -1585,11 +1605,26 @@ def validate(self, testcase: TestCase, submission_output: str) -> SubmissionResu
15851605
for val in self._actual_validators():
15861606
if val is not None and val.compile()[0]:
15871607
feedbackdir = tempfile.mkdtemp(prefix='feedback', dir=self._problem.tmpdir)
1608+
validator_output = tempfile.mkdtemp(prefix='checker_out', dir=self._problem.tmpdir)
1609+
outfile = validator_output + "/out.txt"
1610+
errfile = validator_output + "/err.txt"
15881611
status, runtime = val.run(submission_output,
15891612
args=[testcase.infile, testcase.ansfile, feedbackdir] + flags,
1590-
timelim=val_timelim, memlim=val_memlim)
1613+
timelim=val_timelim, memlim=val_memlim,
1614+
outfile=outfile, errfile=errfile)
1615+
if log.isEnabledFor(logging.DEBUG):
1616+
with open(outfile, mode="rt") as f:
1617+
output = f.read()
1618+
if output:
1619+
log.debug("Validator output:\n%s", output)
1620+
with open(errfile, mode="rt") as f:
1621+
error = f.read()
1622+
if error:
1623+
log.debug("Validator stderr:\n%s", error)
1624+
15911625
res = self._parse_validator_results(val, status, feedbackdir, testcase)
15921626
shutil.rmtree(feedbackdir)
1627+
shutil.rmtree(validator_output)
15931628
if res.verdict != 'AC':
15941629
return res
15951630

@@ -1609,6 +1644,7 @@ class Submissions(ProblemAspect):
16091644
]
16101645

16111646
def __init__(self, problem: Problem):
1647+
super().__init__(f"{problem.shortname}.submission")
16121648
self._submissions = {}
16131649
self._problem = problem
16141650
srcdir = os.path.join(problem.probdir, 'submissions')
@@ -1742,6 +1778,7 @@ class Problem(ProblemAspect):
17421778
def __init__(self, probdir: str):
17431779
self.probdir = os.path.realpath(probdir)
17441780
self.shortname: str|None = os.path.basename(self.probdir)
1781+
super().__init__(self.shortname)
17451782
self.language_config = languages.load_language_config()
17461783

17471784
def __enter__(self) -> Problem:

0 commit comments

Comments
 (0)