Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions problemtools/checks/graders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

from __future__ import annotations

from pathlib import Path

from ..diagnostics import Diagnostics
from ..metadata import Metadata
from ..model import Graders


def check_graders(graders: Graders, metadata: Metadata, diag: Diagnostics) -> None:
def check_graders(graders: Graders, metadata: Metadata, work_dir: str, diag: Diagnostics) -> None:
"""Run all checks on a problem's custom graders."""
if len(graders.graders) > 1:
diag.fatal('There is more than one custom grader')
Expand All @@ -19,6 +21,6 @@ def check_graders(graders: Graders, metadata: Metadata, diag: Diagnostics) -> No
if metadata.is_pass_fail():
diag.fatal('There is a grader but the problem is pass-fail')

success, msg = grader.compile()
if not success:
diag.fatal(f'Compile error for {grader}', msg)
result = grader.compile(Path(work_dir))
if not result.success:
diag.fatal(f'Compile error for {grader}', result.errmsg)
6 changes: 3 additions & 3 deletions problemtools/checks/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ def check_submissions(
)
continue

success, msg = sub.program.compile()
if not success:
diag.error(f'Compile error for {label} submission {sub.program}', additional_info=msg)
result = sub.program.compile(Path(tmpdir))
if not result.success:
diag.error(f'Compile error for {label} submission {sub.program}', additional_info=result.errmsg)
continue

if has_testcases:
Expand Down
21 changes: 10 additions & 11 deletions problemtools/checks/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ def check_input_validators(validators: InputValidators, testdata: TestDataGroup,

for val in validators.validators:
try:
success, msg = val.compile()
if not success:
diag.error(f'Compile error for {val}', msg)
result = val.compile(Path(work_dir))
if not result.success:
diag.error(f'Compile error for {val}', result.errmsg)
except ProgramError as e:
diag.error(str(e))

Expand All @@ -132,7 +132,7 @@ def collect_flags(group: TestDataGroup, flags: set[str]) -> None:
for flags_str in all_flags:
flags = flags_str.split()
for val in validators.validators:
status, _ = val.run(file_name, args=flags, work_dir=work_dir)
status, _ = val.run(file_name, args=flags, work_dir=Path(work_dir))
if os.WEXITSTATUS(status) != 42:
break
else:
Expand All @@ -154,7 +154,7 @@ def modified_input_validates(applicable: Callable[[str], bool], modifier: Callab
for flags_str in all_flags:
flags = flags_str.split()
for val in validators.validators:
status, _ = val.run(file_name, args=flags, work_dir=work_dir)
status, _ = val.run(file_name, args=flags, work_dir=Path(work_dir))
if os.WEXITSTATUS(status) != 42:
# expected behavior; validator rejects modified input
return False
Expand All @@ -179,12 +179,11 @@ def check_testcase_input(validators: InputValidators, testcase: TestCase, work_d

for val in validators.validators:
# A validator that failed to compile was already reported by check_input_validators; skip it.
success, _ = val.compile()
if not success:
if not val.compile(Path(work_dir)).success:
continue

with tempfile.NamedTemporaryFile() as outfile, tempfile.NamedTemporaryFile() as errfile:
status, _ = val.run(str(testcase.infile), outfile.name, errfile.name, args=flags, work_dir=work_dir)
status, _ = val.run(str(testcase.infile), outfile.name, errfile.name, args=flags, work_dir=Path(work_dir))
if not os.WIFEXITED(status):
emsg = f'Input format validator {val} crashed on input {testcase.infile}'
elif os.WEXITSTATUS(status) != 42:
Expand Down Expand Up @@ -234,9 +233,9 @@ def check_output_validators(
diag.fatal('problem.yaml specifies custom validator but no validator programs found')

try:
success, msg = selected.compile()
if not success:
diag.fatal(f'Compile error for output validator {selected}', msg)
result = selected.compile(Path(work_dir))
if not result.success:
diag.fatal(f'Compile error for output validator {selected}', result.errmsg)
except ProgramError as e:
diag.fatal(f'Compile error for output validator {selected}', str(e))

Expand Down
25 changes: 16 additions & 9 deletions problemtools/judge/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,27 @@ def _run_normal(
metadata: Metadata,
timelim: float,
execution_dir: Path,
base_dir: Path,
diag: Diagnostics,
) -> SubmissionResult:
"""Run a submission once (non-interactive)"""
outfile = execution_dir / 'submission_stdout'
errfile = execution_dir / 'submission_stderr'
sub_path = sub.compile(base_dir).path
status, runtime = sub.run(
infile=str(infile),
outfile=str(outfile),
errfile=str(errfile),
timelim=math.ceil(timelim) + 1,
memlim=metadata.limits.memory,
work_dir=sub.path,
work_dir=sub_path,
)
if _is_TLE(status) or runtime > timelim:
result = SubmissionResult('TLE')
elif _is_RTE(status):
result = SubmissionResult('RTE', reason=_rte_reason(status), additional_info=_read_safe(errfile))
else:
result = _validate_output(testcase, outfile, output_validator, metadata, execution_dir, diag, infile=infile)
result = _validate_output(testcase, outfile, output_validator, metadata, execution_dir, base_dir, diag, infile=infile)
result.runtime = runtime
return result

Expand All @@ -100,6 +102,7 @@ def _run_interactive(
metadata: Metadata,
timelim: float,
execution_dir: Path,
base_dir: Path,
diag: Diagnostics,
) -> SubmissionResult:
"""Run a submission once (interactive)"""
Expand All @@ -108,9 +111,11 @@ def _run_interactive(
diag.error('Could not locate interactive runner')
return SubmissionResult('JE', reason='Could not locate interactive runner')

if not output_validator.compile()[0]:
if not output_validator.compile(base_dir).success:
return SubmissionResult('JE', reason=f'output validator {output_validator} failed to compile')

sub_path = sub.compile(base_dir).path

feedback_dir = execution_dir / 'feedback'
interactive_out = execution_dir / 'interactive_output'

Expand All @@ -124,7 +129,7 @@ def _run_interactive(
+ [';']
+ sub.get_runcmd(memlim=metadata.limits.memory)
),
work_dir=sub.path,
work_dir=sub_path,
)

if _is_RTE(i_status):
Expand Down Expand Up @@ -172,12 +177,13 @@ def _run_pass(
metadata: Metadata,
timelim: float,
execution_dir: Path,
base_dir: Path,
diag: Diagnostics,
) -> SubmissionResult:
"""Run a submission once (the common case, or one pass for a multi-pass problem)"""
if metadata.is_interactive():
return _run_interactive(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, diag)
return _run_normal(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, diag)
return _run_interactive(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, base_dir, diag)
return _run_normal(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, base_dir, diag)


def _run_multipass(
Expand All @@ -187,13 +193,14 @@ def _run_multipass(
metadata: Metadata,
timelim: float,
execution_dir: Path,
base_dir: Path,
diag: Diagnostics,
) -> SubmissionResult:
infile = testcase.infile
slowest = 0.0
feedback_dir = execution_dir / 'feedback'
for _ in range(metadata.limits.validation_passes):
result = _run_pass(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, diag)
result = _run_pass(infile, testcase, sub, output_validator, metadata, timelim, execution_dir, base_dir, diag)
slowest = max(slowest, result.runtime)
result.runtime = slowest
nextpass = feedback_dir / 'nextpass.in'
Expand Down Expand Up @@ -222,9 +229,9 @@ def execute_testcase(
execution_dir = Path(exec_dir)
(execution_dir / 'feedback').mkdir()
if metadata.is_multi_pass():
result = _run_multipass(testcase, sub, output_validator, metadata, timelim, execution_dir, diag)
result = _run_multipass(testcase, sub, output_validator, metadata, timelim, execution_dir, base_dir, diag)
else:
result = _run_pass(testcase.infile, testcase, sub, output_validator, metadata, timelim, execution_dir, diag)
result = _run_pass(testcase.infile, testcase, sub, output_validator, metadata, timelim, execution_dir, base_dir, diag)
result.test_node = testcase
result.runtime_testcase = testcase
return result
2 changes: 1 addition & 1 deletion problemtools/judge/grade.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def grade_group(
if not sub_results:
return ('AC', 0.0)

if not grader.compile()[0]:
if not grader.compile(base_dir).success:
diag.error(f'Failed to compile grader {grader}')
return ('JE', None)

Expand Down
5 changes: 3 additions & 2 deletions problemtools/judge/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def _validate_output(
output_validator: Program,
metadata: Metadata,
execution_dir: Path,
base_dir: Path,
diag: Diagnostics,
infile: Path | None = None,
) -> SubmissionResult:
Expand All @@ -93,7 +94,7 @@ def _validate_output(
'OLE', reason=f'output ({output_size:.1f} MiB) exceeds output limit ({metadata.limits.output} MiB)'
)

if not output_validator.compile()[0]:
if not output_validator.compile(base_dir).success:
return SubmissionResult('JE', reason=f'output validator {output_validator} failed to compile')
val_stdout = execution_dir / 'val_stdout'
val_stderr = execution_dir / 'val_stderr'
Expand Down Expand Up @@ -125,4 +126,4 @@ def validate_output(
with tempfile.TemporaryDirectory(dir=base_dir) as exec_dir:
execution_dir = Path(exec_dir)
(execution_dir / 'feedback').mkdir()
return _validate_output(testcase, submission_output, output_validator, metadata, execution_dir, diag)
return _validate_output(testcase, submission_output, output_validator, metadata, execution_dir, base_dir, diag)
4 changes: 2 additions & 2 deletions problemtools/model/graders.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ def grader(self) -> Program | None:
return self.graders[0] if len(self.graders) == 1 else None


def load_graders(probdir: Path, language_config: Languages, work_dir: str) -> Graders:
graders = find_programs(str(probdir / 'graders'), language_config=language_config, work_dir=work_dir)
def load_graders(probdir: Path, language_config: Languages) -> Graders:
graders = find_programs(str(probdir / 'graders'), language_config=language_config)
return Graders(graders=graders)
4 changes: 2 additions & 2 deletions problemtools/model/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ class Submissions:
policy: LegacyPolicy = field(default_factory=LegacyPolicy)


def load_submissions(probdir: Path, language_config: Languages, work_dir: str, includes: Includes) -> Submissions:
def load_submissions(probdir: Path, language_config: Languages, includes: Includes) -> Submissions:
subs_root = probdir / 'submissions'
if not subs_root.is_dir():
return Submissions()

submissions = []
for entry in sorted(subs_root.iterdir()):
if entry.is_dir():
for program in find_programs(str(entry), language_config=language_config, work_dir=work_dir, includes=includes):
for program in find_programs(str(entry), language_config=language_config, includes=includes):
submissions.append(Submission(program=program, path=Path(entry.name) / program.name))
return Submissions(submissions=submissions)
12 changes: 4 additions & 8 deletions problemtools/model/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ class InputValidators:
uses_old_path: bool = False


def load_input_validators(probdir: Path, language_config: Languages, work_dir: str) -> InputValidators:
def load_input_validators(probdir: Path, language_config: Languages) -> InputValidators:
old_path = probdir / 'input_format_validators'
uses_old_path = old_path.is_dir()
validators_path = old_path if uses_old_path else probdir / 'input_validators'
validators = find_programs(
str(validators_path), language_config=language_config, allow_validation_script=True, work_dir=work_dir
)
validators = find_programs(str(validators_path), language_config=language_config, allow_validation_script=True)
return InputValidators(validators=validators, uses_old_path=uses_old_path)


Expand All @@ -50,8 +48,6 @@ def select(self, format: FormatVersion, metadata: Metadata) -> Program | None:
return self.validators[0]


def load_output_validators(probdir: Path, format: FormatVersion, language_config: Languages, work_dir: str) -> OutputValidators:
validators = find_programs(
str(probdir / format.output_validator_directory), language_config=language_config, work_dir=work_dir
)
def load_output_validators(probdir: Path, format: FormatVersion, language_config: Languages) -> OutputValidators:
validators = find_programs(str(probdir / format.output_validator_directory), language_config=language_config)
return OutputValidators(validators=validators)
11 changes: 2 additions & 9 deletions problemtools/run/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
def find_programs(
path: str,
language_config: Languages,
work_dir: str,
includes: 'Includes | None' = None,
allow_validation_script: bool = False,
) -> list[Program]:
Expand All @@ -36,8 +35,6 @@ def find_programs(
programming language of source code and providing info
on how to compile and run the source code.

work_dir: temp directory in which to compile programs etc

includes: include files to add to programs found, resolved
per-program based on its detected language (see
Includes.get_includes_for_language).
Expand All @@ -57,7 +54,6 @@ def find_programs(
run = get_program(
fullpath,
language_config=language_config,
work_dir=work_dir,
includes=includes,
allow_validation_script=allow_validation_script,
)
Expand All @@ -69,7 +65,6 @@ def find_programs(
def get_program(
path: str,
language_config: Languages,
work_dir: str,
includes: 'Includes | None' = None,
allow_validation_script: bool = False,
) -> Program | None:
Expand All @@ -84,8 +79,6 @@ def get_program(
programming language of source code and providing info
on how to compile and run the source code.

work_dir: temp directory in which to compile programs etc

includes: include files to add to the program, resolved per
the program's detected language (see
Includes.get_includes_for_language). Defaults to no includes.
Expand Down Expand Up @@ -115,10 +108,10 @@ def get_program(
else:
build = os.path.join(path, 'build')
if os.path.isfile(build) and os.access(build, os.X_OK):
return BuildRun(path, work_dir)
return BuildRun(path)
files = rutil.list_files_recursive(path)

lang = language_config.detect_language(files)
if lang is not None:
return SourceCode(path, lang, work_dir=work_dir, includes=includes.get_includes_for_language(lang.lang_id))
return SourceCode(path, lang, includes=includes.get_includes_for_language(lang.lang_id))
return None
Loading