Skip to content

Commit 22980eb

Browse files
committed
[validate] Add OutputVisualizer as special type of Validator
1 parent 8fb2e29 commit 22980eb

File tree

3 files changed

+88
-11
lines changed

3 files changed

+88
-11
lines changed

bin/problem.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,9 @@ def _validators(
835835
paths = [problem.path / validate.OutputValidator.source_dir]
836836
else:
837837
paths = [config.TOOLS_ROOT / "support" / "default_output_validator.cpp"]
838+
elif cls == validate.OutputVisualizer:
839+
# TODO: if not config.args.no_output_visualizer:
840+
paths = [problem.path / validate.OutputVisualizer.source_dir]
838841
else:
839842
assert hasattr(cls, "source_dirs")
840843
paths = [
@@ -872,7 +875,7 @@ def has_constraints_checking(f):
872875
)
873876
for path in paths
874877
]
875-
bar = ProgressBar(f"Building {cls.validator_type} validator", items=validators)
878+
bar = ProgressBar(f"Building {cls.validator_type}", items=validators)
876879

877880
def build_program(p):
878881
localbar = bar.start(p)
@@ -891,9 +894,10 @@ def prepare_run(problem):
891894
if not testcases:
892895
return False
893896

894-
# Pre build the output validator to prevent nested ProgressBars.
897+
# Pre build the output validator and visualizer to prevent nested ProgressBars.
895898
if not problem.validators(validate.OutputValidator):
896899
return False
900+
problem.validators(validate.OutputVisualizer)
897901

898902
submissions = problem.submissions()
899903
if not submissions:

bin/run.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from colorama import Fore, Style
77
from pathlib import Path
8-
from typing import cast
8+
from typing import Optional, cast
99

1010
import config
1111
import interactive
@@ -174,7 +174,9 @@ def run(self, bar, *, interaction=None, submission_args=None):
174174

175175
result.duration = max_duration
176176

177-
# Delete .out files larger than 1MB.
177+
self._visualize_output(bar)
178+
179+
# Delete .out files larger than 1GB.
178180
if (
179181
not config.args.error
180182
and self.out_path.is_file()
@@ -215,7 +217,7 @@ def _prepare_nextpass(self, nextpass):
215217
shutil.move(nextpass, self.in_path)
216218
return True
217219

218-
def _validate_output(self, bar):
220+
def _validate_output(self, bar: ProgressBar) -> Optional[ExecResult]:
219221
output_validators = self.problem.validators(validate.OutputValidator)
220222
if not output_validators:
221223
return None
@@ -225,6 +227,16 @@ def _validate_output(self, bar):
225227
args=self.testcase.testdata_yaml_validator_args(output_validators[0], bar),
226228
)
227229

230+
def _visualize_output(self, bar: ProgressBar) -> Optional[ExecResult]:
231+
output_validators = self.problem.validators(validate.OutputVisualizer)
232+
if not output_validators:
233+
return None
234+
return output_validators[0].run(
235+
self.testcase,
236+
self,
237+
args=self.testcase.testdata_yaml_validator_args(output_validators[0], bar),
238+
)
239+
228240

229241
class Submission(program.Program):
230242
def __init__(self, problem, path, skip_double_build_warning=False):

bin/validate.py

+67-6
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class InputValidator(Validator):
224224
def __init__(self, problem, path, **kwargs):
225225
super().__init__(problem, path, "input_validators", **kwargs)
226226

227-
validator_type: Final[str] = "input"
227+
validator_type: Final[str] = "input validator"
228228

229229
source_dirs: Final[list[str]] = ["input_validators", "input_format_validators"]
230230

@@ -284,7 +284,7 @@ class AnswerValidator(Validator):
284284
def __init__(self, problem, path, **kwargs):
285285
super().__init__(problem, path, "answer_validators", **kwargs)
286286

287-
validator_type: Final[str] = "answer"
287+
validator_type: Final[str] = "answer validator"
288288

289289
source_dirs: Final[list[str]] = ["answer_validators", "answer_format_validators"]
290290

@@ -335,7 +335,7 @@ class OutputValidator(Validator):
335335
def __init__(self, problem, path, **kwargs):
336336
super().__init__(problem, path, "output_validator", **kwargs)
337337

338-
validator_type: Final[str] = "output"
338+
validator_type: Final[str] = "output validator"
339339

340340
source_dir: Final[str] = "output_validator"
341341

@@ -353,7 +353,7 @@ def run(
353353
---------
354354
355355
mode: either a run.Run (namely, when validating submission output) or a Mode
356-
(namely, when validation a testcase)
356+
(namely, when validating a testcase)
357357
358358
Returns
359359
-------
@@ -363,7 +363,7 @@ def run(
363363
assert self.run_command is not None, "Validator should be built before running it"
364364

365365
if mode == Mode.INPUT:
366-
raise ValueError("OutputValidator do not support Mode.INPUT")
366+
raise ValueError("OutputValidator does not support Mode.INPUT")
367367

368368
in_path = testcase.in_path.resolve()
369369
ans_path = testcase.ans_path.resolve()
@@ -409,7 +409,68 @@ def run(
409409
return ret
410410

411411

412-
AnyValidator = InputValidator | AnswerValidator | OutputValidator
412+
class OutputVisualizer(Validator):
413+
"""
414+
Visualize the output of a submission
415+
416+
./visualizer input answer feedbackdir [arguments from problem.yaml] < output
417+
"""
418+
419+
def __init__(self, problem, path, **kwargs):
420+
super().__init__(problem, path, "output_visualizer", **kwargs)
421+
422+
validator_type: Final[str] = "output visualizer"
423+
424+
source_dir: Final[str] = "output_visualizer"
425+
426+
def run(
427+
self,
428+
testcase, # TODO #102: fix type errors after setting type to Testcase
429+
mode,
430+
constraints: Optional[ConstraintsDict] = None,
431+
args=None,
432+
) -> ExecResult:
433+
"""
434+
Run this validator on the given testcase.
435+
436+
Arguments
437+
---------
438+
439+
run: run.Run (namely, when visualizing submission output)
440+
441+
Returns
442+
-------
443+
The ExecResult
444+
"""
445+
446+
assert self.run_command is not None, "Validator should be built before running it"
447+
448+
in_path = testcase.in_path.resolve()
449+
ans_path = testcase.ans_path.resolve()
450+
run = mode # mode is actually a run
451+
path = run.out_path
452+
in_path = run.in_path
453+
454+
if self.language in Validator.FORMAT_VALIDATOR_LANGUAGES:
455+
raise ValueError("Invalid output validator language")
456+
457+
# Only get the output_validator_args
458+
_, _, arglist = self._run_helper(testcase, constraints, args)
459+
cwd = run.feedbackdir
460+
invocation = self.run_command + [in_path, ans_path, cwd]
461+
462+
with path.open() as file:
463+
ret = self._exec_helper(
464+
invocation + arglist,
465+
exec_code_map=validator_exec_code_map,
466+
stdin=file,
467+
cwd=cwd,
468+
)
469+
470+
return ret
471+
472+
473+
AnyValidator = InputValidator | AnswerValidator | OutputValidator | OutputVisualizer
413474

414475

415476
# Checks if byte is printable or whitespace

0 commit comments

Comments
 (0)