Skip to content

Commit 7168ad9

Browse files
committed
Introduce Test Case Configuration .yaml file, move .hint and .desc to there
1 parent 1a2f717 commit 7168ad9

File tree

16 files changed

+305
-196
lines changed

16 files changed

+305
-196
lines changed

bin/config.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@
7272
*KNOWN_TESTCASE_EXTENSIONS,
7373
*KNOWN_SAMPLE_TESTCASE_EXTENSIONS,
7474
".interaction",
75-
".hint",
76-
".desc",
77-
#'.args',
75+
".yaml",
7876
]
7977

8078
KNOWN_DATA_EXTENSIONS: Final[Sequence[str]] = [

bin/export.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def add_testcase(in_file: Path) -> None:
298298
ryaml_filter(limits, "time_limit")
299299
# validator_flags
300300
validator_flags = " ".join(
301-
problem.get_test_group_yaml(
301+
problem.get_test_case_yaml(
302302
problem.path / "data",
303303
OutputValidator.args_key,
304304
PrintBar("Getting validator_flags for legacy export"),

bin/generate.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,14 @@ def __init__(self, problem, key, name, yaml, parent):
394394

395395
class TestcaseRule(Rule):
396396
def __init__(
397-
self, problem: Problem, generator_config, key, name: str, yaml, parent, count_index
397+
self,
398+
problem: Problem,
399+
generator_config,
400+
key,
401+
name: str,
402+
yaml: dict[str, Any],
403+
parent,
404+
count_index,
398405
):
399406
assert is_testcase(yaml)
400407

@@ -558,11 +565,16 @@ def __init__(
558565
if self.copy.with_suffix(ext).is_file():
559566
hashes[ext] = hash_file_content(self.copy.with_suffix(ext))
560567

561-
# 3. hardcoded
568+
# 3. hardcoded strings (or, for the Test Case Configuration, a yaml mapping)
562569
for ext in config.KNOWN_TEXT_DATA_EXTENSIONS:
563570
if ext[1:] in yaml:
564571
value = yaml[ext[1:]]
565-
assert_type(ext, value, str)
572+
if ext == ".yaml":
573+
assert_type(ext, value, dict)
574+
value = write_yaml(value)
575+
assert value is not None
576+
else:
577+
assert_type(ext, value, str)
566578
if len(value) > 0 and value[-1] != "\n":
567579
value += "\n"
568580
self.hardcoded[ext] = value
@@ -1045,7 +1057,7 @@ def use_feedback_image(feedbackdir: Path, source: str) -> None:
10451057
use_feedback_image(feedbackdir, "validator")
10461058
return True
10471059

1048-
visualizer_args = testcase.test_group_yaml_args(visualizer, bar)
1060+
visualizer_args = testcase.test_case_yaml_args(visualizer, bar)
10491061
visualizer_hash = {
10501062
"visualizer_hash": visualizer.hash,
10511063
"visualizer_args": visualizer_args,

bin/interactive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def get_validator_command():
5656
run.testcase.ans_path.resolve(),
5757
run.feedbackdir.resolve(),
5858
]
59-
+ run.testcase.test_group_yaml_args(
59+
+ run.testcase.test_case_yaml_args(
6060
output_validator,
6161
bar or PrintBar("Run interactive test case"),
6262
)

bin/problem.py

Lines changed: 77 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def __init__(self, path: Path, tmpdir: Path, label: Optional[str] = None):
365365
self._program_callbacks = dict[Path, list[Callable[["Program"], None]]]()
366366
# Dictionary from path to parsed file contents.
367367
# TODO #102: Add type for test_group.yaml (typed Namespace?)
368-
self._test_group_yamls = dict[Path, dict[str, Any]]()
368+
self._test_case_yamls = dict[Path, dict[str, Any]]()
369369
self._test_group_lock = threading.Lock()
370370

371371
# The label for the problem: A, B, A1, A2, X, ...
@@ -460,83 +460,80 @@ def _read_settings(self):
460460
self.custom_output: bool = self.settings.custom_output
461461

462462
# TODO #102 move to a new TestGroup class
463-
def _parse_test_group_yaml(p, path, bar):
463+
def _parse_test_case_and_groups_yaml(p, path: Path, bar: BAR_TYPE):
464464
assert path.is_relative_to(p.path / "data")
465-
for dir in [path] + list(path.parents):
465+
for f in [path] + list(path.parents):
466466
# Do not go above the data directory.
467-
if dir == p.path:
467+
if f == p.path:
468468
return
469469

470-
f = dir / "test_group.yaml"
471-
if not f.is_file() or f in p._test_group_yamls:
472-
continue
470+
if f.suffix != ".yaml":
471+
f = f / "test_group.yaml"
473472
with p._test_group_lock:
474-
if f not in p._test_group_yamls:
475-
raw = substitute(
476-
f.read_text(),
477-
p.settings.constants,
478-
pattern=config.CONSTANT_SUBSTITUTE_REGEX,
479-
)
480-
p._test_group_yamls[f] = flags = parse_yaml(raw, path=f, plain=True)
473+
if not f.is_file() or f in p._test_case_yamls:
474+
continue
475+
raw = substitute(
476+
f.read_text(),
477+
p.settings.constants,
478+
pattern=config.CONSTANT_SUBSTITUTE_REGEX,
479+
)
480+
p._test_case_yamls[f] = flags = parse_yaml(raw, path=f, plain=True)
481481

482-
parse_deprecated_setting(
483-
flags, "output_validator_flags", validate.OutputValidator.args_key
484-
)
485-
parse_deprecated_setting(
486-
flags, "input_validator_flags", validate.InputValidator.args_key
487-
)
482+
parse_deprecated_setting(
483+
flags, "output_validator_flags", validate.OutputValidator.args_key
484+
)
485+
parse_deprecated_setting(
486+
flags, "input_validator_flags", validate.InputValidator.args_key
487+
)
488488

489-
# Verify test_group.yaml
490-
for k in flags:
491-
match k:
492-
case (
493-
validate.OutputValidator.args_key
494-
| validate.AnswerValidator.args_key
495-
| visualize.TestCaseVisualizer.args_key
496-
| visualize.OutputVisualizer.args_key
497-
):
498-
if not isinstance(flags[k], list):
499-
bar.error(
500-
f"{k} must be a list of strings",
501-
resume=True,
502-
print_item=False,
503-
)
504-
case validate.InputValidator.args_key:
505-
if not isinstance(flags[k], (list, dict)):
506-
bar.error(
507-
f"{k} must be list or map",
508-
resume=True,
509-
print_item=False,
510-
)
511-
if isinstance(flags[k], dict):
512-
input_validator_names = set(
513-
val.name for val in p.validators(validate.InputValidator)
514-
)
515-
for name in set(flags[k]) - input_validator_names:
516-
bar.warn(
517-
f"Unknown input validator {name}; expected {input_validator_names}",
518-
print_item=False,
519-
)
520-
case (
521-
"args"
522-
| "description"
523-
| "full_feedback"
524-
| "hint"
525-
| "scoring"
526-
| "static_validation"
527-
):
528-
bar.warn(
529-
f"{k} in test_group.yaml not implemented in BAPCtools",
530-
print_item=False,
489+
# Use variable kwargs so the type checker does not complain when passing them to a PrintBar (nothing happens in that case anyway)
490+
bar_kwargs = {"resume": True, "print_item": False}
491+
492+
# Verify test_group.yaml
493+
for k in flags:
494+
match k:
495+
case (
496+
validate.OutputValidator.args_key
497+
| validate.AnswerValidator.args_key
498+
| visualize.TestCaseVisualizer.args_key
499+
| visualize.OutputVisualizer.args_key
500+
):
501+
if not isinstance(flags[k], list):
502+
bar.error(
503+
f"{k} must be a list of strings",
504+
None,
505+
**bar_kwargs,
531506
)
532-
case _:
533-
path = f.relative_to(p.path / "data")
534-
bar.warn(f'Unknown key "{k}" in {path}', print_item=False)
535-
# Do not go above the data directory.
536-
if dir == p.path / "data":
537-
break
538-
539-
def get_test_group_yaml(
507+
case validate.InputValidator.args_key:
508+
if not isinstance(flags[k], (list, dict)):
509+
bar.error(
510+
f"{k} must be list or map",
511+
None,
512+
**bar_kwargs,
513+
)
514+
if isinstance(flags[k], dict):
515+
input_validator_names = set(
516+
val.name for val in p.validators(validate.InputValidator)
517+
)
518+
for name in set(flags[k]) - input_validator_names:
519+
bar.warn(
520+
f"Unknown input validator {name}; expected {input_validator_names}",
521+
None,
522+
**bar_kwargs,
523+
)
524+
case "description" | "hint":
525+
pass # We don't do anything with hint or description in BAPCtools, but no need to warn about this
526+
case "args" | "full_feedback" | "scoring" | "static_validation":
527+
bar.warn(
528+
f"{k} in test_group.yaml not implemented in BAPCtools",
529+
None,
530+
**bar_kwargs,
531+
)
532+
case _:
533+
path = f.relative_to(p.path / "data")
534+
bar.warn(f'Unknown key "{k}" in {path}', None, **bar_kwargs)
535+
536+
def get_test_case_yaml(
540537
p,
541538
path: Path,
542539
key: str,
@@ -574,19 +571,21 @@ def get_test_group_yaml(
574571
f"Only input validators support flags by validator name, got {key} and {name}"
575572
)
576573

577-
# parse and cache test_group.yaml
578-
p._parse_test_group_yaml(path, bar)
574+
# parse and cache <test_case>.yaml and test_group.yaml
575+
path = path.with_suffix(".yaml")
576+
p._parse_test_case_and_groups_yaml(path, bar)
579577

580578
# extract the flags
581-
for dir in [path] + list(path.parents):
579+
for f in [path] + list(path.parents):
582580
# Do not go above the data directory.
583-
if dir == p.path:
581+
if f == p.path:
584582
return []
585583

586-
f = dir / "test_group.yaml"
587-
if f not in p._test_group_yamls:
584+
if f.suffix != ".yaml":
585+
f = f / "test_group.yaml"
586+
if f not in p._test_case_yamls:
588587
continue
589-
flags = p._test_group_yamls[f]
588+
flags = p._test_case_yamls[f]
590589
if key in flags:
591590
args = flags[key]
592591
if key == validate.InputValidator.args_key:
@@ -1333,7 +1332,7 @@ def validate_valid_extra_data(p) -> bool:
13331332
if not p.validators(validate.OutputValidator, strict=True, print_warn=False):
13341333
return True
13351334

1336-
args = p.get_test_group_yaml(
1335+
args = p.get_test_case_yaml(
13371336
p.path / "data" / "valid_output",
13381337
"output_validator_args",
13391338
PrintBar("Generic Output Validation"),

bin/run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def _validate_output(self, bar: BAR_TYPE) -> Optional[ExecResult]:
228228
return output_validator.run(
229229
self.testcase,
230230
self,
231-
args=self.testcase.test_group_yaml_args(output_validator, bar),
231+
args=self.testcase.test_case_yaml_args(output_validator, bar),
232232
)
233233

234234
def _visualize_output(self, bar: BAR_TYPE) -> Optional[ExecResult]:
@@ -242,7 +242,7 @@ def _visualize_output(self, bar: BAR_TYPE) -> Optional[ExecResult]:
242242
self.testcase.ans_path.resolve(),
243243
self.out_path if not self.problem.interactive else None,
244244
self.feedbackdir,
245-
args=self.testcase.test_group_yaml_args(output_visualizer, bar),
245+
args=self.testcase.test_case_yaml_args(output_visualizer, bar),
246246
)
247247

248248

bin/testcase.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def __repr__(self) -> str:
116116
def with_suffix(self, ext: str) -> Path:
117117
return self.in_path.with_suffix(ext)
118118

119-
def test_group_yaml_args(
119+
def test_case_yaml_args(
120120
self,
121121
program: "validate.AnyValidator | visualize.AnyVisualizer",
122122
bar: BAR_TYPE,
@@ -131,9 +131,8 @@ def test_group_yaml_args(
131131
or ["--max_N", "50"] or even [""].
132132
"""
133133

134-
path = self.problem.path / "data" / self.short_path
135-
return self.problem.get_test_group_yaml(
136-
path,
134+
return self.problem.get_test_case_yaml(
135+
self.problem.path / "data" / self.short_path,
137136
type(program).args_key,
138137
bar,
139138
name=program.name if isinstance(program, validate.InputValidator) else None,
@@ -157,7 +156,7 @@ def validator_hashes(
157156
d = dict()
158157

159158
for validator in validators:
160-
flags = self.test_group_yaml_args(validator, bar)
159+
flags = self.test_case_yaml_args(validator, bar)
161160
flags_string = " ".join(flags)
162161
h = combine_hashes_dict(
163162
{
@@ -288,7 +287,7 @@ def _run_validators(
288287
if isinstance(validator, validate.OutputValidator) and mode == validate.Mode.ANSWER:
289288
args += ["case_sensitive", "space_change_sensitive"]
290289
name = f"{name} (ans)"
291-
flags = self.test_group_yaml_args(validator, bar)
290+
flags = self.test_case_yaml_args(validator, bar)
292291
flags = flags + args
293292

294293
ret = validator.run(self, mode=mode, constraints=constraints, args=flags)

0 commit comments

Comments
 (0)