Skip to content

Commit 86ed8be

Browse files
committed
[validate] Add OutputVisualizer as special type of Validator
1 parent 98fb997 commit 86ed8be

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
@@ -910,6 +910,13 @@ def _validators(
910910
paths = [problem.path / validate.OutputValidator.source_dir]
911911
else:
912912
paths = [config.TOOLS_ROOT / "support" / "default_output_validator.cpp"]
913+
elif cls == validate.OutputVisualizer:
914+
# TODO: if not config.args.no_output_visualizer:
915+
paths = (
916+
[problem.path / validate.OutputVisualizer.source_dir]
917+
if (problem.path / validate.OutputVisualizer.source_dir).is_dir()
918+
else []
919+
)
913920
else:
914921
paths = list(glob(problem.path / cls.source_dir, "*"))
915922

@@ -944,7 +951,7 @@ def has_constraints_checking(f):
944951
)
945952
for path in paths
946953
]
947-
bar = ProgressBar(f"Building {cls.validator_type} validator", items=validators)
954+
bar = ProgressBar(f"Building {cls.validator_type}", items=validators)
948955

949956
def build_program(p):
950957
localbar = bar.start(p)
@@ -963,9 +970,10 @@ def prepare_run(problem):
963970
if not testcases:
964971
return False
965972

966-
# Pre build the output validator to prevent nested ProgressBars.
973+
# Pre build the output validator and visualizer to prevent nested ProgressBars.
967974
if not problem.validators(validate.OutputValidator):
968975
return False
976+
problem.validators(validate.OutputVisualizer)
969977

970978
submissions = problem.submissions()
971979
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
@@ -227,7 +227,7 @@ class InputValidator(Validator):
227227
def __init__(self, problem, path, **kwargs):
228228
super().__init__(problem, path, "input_validators", **kwargs)
229229

230-
validator_type: Final[str] = "input"
230+
validator_type: Final[str] = "input validator"
231231

232232
source_dir: Final[str] = "input_validators"
233233

@@ -287,7 +287,7 @@ class AnswerValidator(Validator):
287287
def __init__(self, problem, path, **kwargs):
288288
super().__init__(problem, path, "answer_validators", **kwargs)
289289

290-
validator_type: Final[str] = "answer"
290+
validator_type: Final[str] = "answer validator"
291291

292292
source_dir: Final[str] = "answer_validators"
293293

@@ -338,7 +338,7 @@ class OutputValidator(Validator):
338338
def __init__(self, problem, path, **kwargs):
339339
super().__init__(problem, path, "output_validator", **kwargs)
340340

341-
validator_type: Final[str] = "output"
341+
validator_type: Final[str] = "output validator"
342342

343343
source_dir: Final[str] = "output_validator"
344344

@@ -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+
def __init__(self, problem, path, **kwargs):
421+
super().__init__(problem, path, "output_visualizer", **kwargs)
422+
423+
validator_type: Final[str] = "output visualizer"
424+
425+
source_dir: Final[str] = "output_visualizer"
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)