Skip to content

Commit 2a8ef50

Browse files
committed
[validate] Add OutputVisualizer as special type of Validator
1 parent a83ca75 commit 2a8ef50

File tree

3 files changed

+92
-11
lines changed

3 files changed

+92
-11
lines changed

bin/problem.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,13 @@ def _validators(
912912
paths = [problem.path / validate.OutputValidator.source_dir]
913913
else:
914914
paths = [config.TOOLS_ROOT / "support" / "default_output_validator.cpp"]
915+
elif cls == validate.OutputVisualizer:
916+
# TODO: if not config.args.no_output_visualizer:
917+
paths = (
918+
[problem.path / validate.OutputVisualizer.source_dir]
919+
if (problem.path / validate.OutputVisualizer.source_dir).is_dir()
920+
else []
921+
)
915922
else:
916923
paths = list(glob(problem.path / cls.source_dir, "*"))
917924

@@ -946,7 +953,7 @@ def has_constraints_checking(f):
946953
)
947954
for path in paths
948955
]
949-
bar = ProgressBar(f"Building {cls.validator_type} validator", items=validators)
956+
bar = ProgressBar(f"Building {cls.validator_type}", items=validators)
950957

951958
def build_program(p):
952959
localbar = bar.start(p)
@@ -965,9 +972,10 @@ def prepare_run(problem):
965972
if not testcases:
966973
return False
967974

968-
# Pre build the output validator to prevent nested ProgressBars.
975+
# Pre build the output validator and visualizer to prevent nested ProgressBars.
969976
if not problem.validators(validate.OutputValidator):
970977
return False
978+
problem.validators(validate.OutputVisualizer)
971979

972980
submissions = problem.submissions()
973981
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
@@ -227,6 +229,16 @@ def _validate_output(self, bar):
227229
args=self.testcase.testdata_yaml_validator_args(output_validator, bar),
228230
)
229231

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

231243
class Submission(program.Program):
232244
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
Also supports checktestdata and viva files, with different invocation.
225225
"""
226226

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

229229
source_dir: Final[str] = "input_validators"
230230

@@ -284,7 +284,7 @@ class AnswerValidator(Validator):
284284
Also supports checktestdata and viva files, with different invocation.
285285
"""
286286

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

289289
source_dir: Final[str] = "answer_validators"
290290

@@ -335,7 +335,7 @@ class OutputValidator(Validator):
335335
./validator input answer feedbackdir [arguments from problem.yaml] < output
336336
"""
337337

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

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

@@ -356,7 +356,7 @@ def run(
356356
---------
357357
358358
mode: either a run.Run (namely, when validating submission output) or a Mode
359-
(namely, when validation a testcase)
359+
(namely, when validating a testcase)
360360
361361
Returns
362362
-------
@@ -366,7 +366,7 @@ def run(
366366
assert self.run_command is not None, "Validator should be built before running it"
367367

368368
if mode == Mode.INPUT:
369-
raise ValueError("OutputValidator do not support Mode.INPUT")
369+
raise ValueError("OutputValidator does not support Mode.INPUT")
370370

371371
in_path = testcase.in_path.resolve()
372372
ans_path = testcase.ans_path.resolve()
@@ -410,7 +410,68 @@ def run(
410410
return ret
411411

412412

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

415476

416477
# Checks if byte is printable or whitespace

0 commit comments

Comments
 (0)