rbx.cp 0.5.34__py3-none-any.whl → 0.5.36__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbx/box/builder.py +10 -2
- rbx/box/contest/build_contest_statements.py +1 -1
- rbx/box/contest/main.py +2 -1
- rbx/box/creation.py +7 -12
- rbx/box/generators.py +102 -52
- rbx/box/generators_test.py +5 -1
- rbx/box/main.py +33 -16
- rbx/box/package.py +2 -2
- rbx/box/schema.py +3 -0
- rbx/box/solutions.py +1 -1
- rbx/box/solutions_test.py +5 -1
- rbx/box/statements/build_statements.py +1 -1
- rbx/box/testcase_utils.py +135 -0
- rbx/box/testcases/__init__.py +0 -0
- rbx/box/testcases/main.py +158 -0
- rbx/box/validators.py +1 -1
- rbx/config.py +29 -1
- {rbx_cp-0.5.34.dist-info → rbx_cp-0.5.36.dist-info}/METADATA +1 -1
- {rbx_cp-0.5.34.dist-info → rbx_cp-0.5.36.dist-info}/RECORD +22 -20
- rbx/box/testcases.py +0 -70
- {rbx_cp-0.5.34.dist-info → rbx_cp-0.5.36.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.34.dist-info → rbx_cp-0.5.36.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.34.dist-info → rbx_cp-0.5.36.dist-info}/entry_points.txt +0 -0
rbx/box/builder.py
CHANGED
@@ -3,7 +3,11 @@ from typing import Optional, Set
|
|
3
3
|
from rbx import console, utils
|
4
4
|
from rbx.box import environment, package
|
5
5
|
from rbx.box.environment import VerificationLevel
|
6
|
-
from rbx.box.generators import
|
6
|
+
from rbx.box.generators import (
|
7
|
+
extract_generation_testcases_from_groups,
|
8
|
+
generate_outputs_for_testcases,
|
9
|
+
generate_testcases,
|
10
|
+
)
|
7
11
|
from rbx.box.solutions import (
|
8
12
|
is_fast,
|
9
13
|
print_run_report,
|
@@ -61,7 +65,11 @@ def build(
|
|
61
65
|
keep=True,
|
62
66
|
) as s:
|
63
67
|
if output:
|
64
|
-
|
68
|
+
entries = [
|
69
|
+
entry.group_entry
|
70
|
+
for entry in extract_generation_testcases_from_groups(groups)
|
71
|
+
]
|
72
|
+
generate_outputs_for_testcases(entries, s)
|
65
73
|
|
66
74
|
console.console.print(
|
67
75
|
'[success]Problem built.[/success] '
|
@@ -31,7 +31,7 @@ from rbx.box.statements.joiners import (
|
|
31
31
|
StatementJoinerContext,
|
32
32
|
)
|
33
33
|
from rbx.box.statements.schema import Statement, StatementType
|
34
|
-
from rbx.box.
|
34
|
+
from rbx.box.testcase_utils import get_samples
|
35
35
|
|
36
36
|
|
37
37
|
@dataclasses.dataclass
|
rbx/box/contest/main.py
CHANGED
@@ -128,7 +128,8 @@ def edit():
|
|
128
128
|
@app.command('add, a', help='Add new problem to contest.')
|
129
129
|
@within_contest
|
130
130
|
def add(path: str, short_name: str, preset: Optional[str] = None):
|
131
|
-
|
131
|
+
problem_path = pathlib.Path(path)
|
132
|
+
name = problem_path.stem
|
132
133
|
utils.validate_field(ContestProblem, 'short_name', short_name)
|
133
134
|
utils.validate_field(Package, 'name', name)
|
134
135
|
|
rbx/box/creation.py
CHANGED
@@ -4,9 +4,8 @@ from typing import Annotated, Optional
|
|
4
4
|
|
5
5
|
import typer
|
6
6
|
|
7
|
-
from rbx import console
|
8
|
-
from rbx.box import presets
|
9
|
-
from rbx.box.contest.contest_package import find_contest_yaml
|
7
|
+
from rbx import console, utils
|
8
|
+
from rbx.box import package, presets
|
10
9
|
from rbx.box.presets.fetch import get_preset_fetch_info
|
11
10
|
|
12
11
|
|
@@ -27,15 +26,6 @@ def create(
|
|
27
26
|
] = None,
|
28
27
|
path: Optional[pathlib.Path] = None,
|
29
28
|
):
|
30
|
-
if find_contest_yaml() is not None:
|
31
|
-
console.console.print(
|
32
|
-
'[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
|
33
|
-
)
|
34
|
-
console.console.print(
|
35
|
-
'[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
|
36
|
-
)
|
37
|
-
raise typer.Exit(1)
|
38
|
-
|
39
29
|
preset = preset or 'default'
|
40
30
|
console.console.print(f'Creating new problem [item]{name}[/item]...')
|
41
31
|
|
@@ -80,4 +70,9 @@ def create(
|
|
80
70
|
for lock in dest_path.rglob('.preset-lock.yml'):
|
81
71
|
lock.unlink(missing_ok=True)
|
82
72
|
|
73
|
+
# Change problem name.
|
74
|
+
ru, problem = package.get_ruyaml(dest_path)
|
75
|
+
problem['name'] = name
|
76
|
+
utils.save_ruyaml(dest_path / 'problem.rbx.yml', ru, problem)
|
77
|
+
|
83
78
|
presets.generate_lock(preset, root=dest_path)
|
rbx/box/generators.py
CHANGED
@@ -3,13 +3,13 @@ import pathlib
|
|
3
3
|
import shlex
|
4
4
|
import shutil
|
5
5
|
from pathlib import PosixPath
|
6
|
-
from typing import Dict, List, Optional, Set
|
6
|
+
from typing import Dict, Iterable, List, Optional, Set, Tuple
|
7
7
|
|
8
8
|
import typer
|
9
9
|
from pydantic import BaseModel
|
10
10
|
|
11
11
|
from rbx import console
|
12
|
-
from rbx.box import checkers, package,
|
12
|
+
from rbx.box import checkers, package, testcase_utils, validators
|
13
13
|
from rbx.box.code import SanitizationLevel, compile_item, run_item
|
14
14
|
from rbx.box.environment import (
|
15
15
|
EnvironmentSandbox,
|
@@ -22,7 +22,7 @@ from rbx.box.schema import (
|
|
22
22
|
TestcaseSubgroup,
|
23
23
|
)
|
24
24
|
from rbx.box.stressing import generator_parser
|
25
|
-
from rbx.box.
|
25
|
+
from rbx.box.testcase_utils import TestcaseEntry, TestcasePattern, find_built_testcases
|
26
26
|
from rbx.grading.steps import (
|
27
27
|
DigestHolder,
|
28
28
|
DigestOrDest,
|
@@ -141,15 +141,20 @@ def _run_generator_script(testcase: TestcaseSubgroup) -> str:
|
|
141
141
|
return script
|
142
142
|
|
143
143
|
|
144
|
-
def _extract_script_lines(script: str):
|
144
|
+
def _extract_script_lines(script: str) -> Iterable[Tuple[str, str, int]]:
|
145
145
|
lines = script.splitlines()
|
146
|
-
for line in lines:
|
146
|
+
for i, line in enumerate(lines):
|
147
147
|
line = line.strip()
|
148
148
|
if not line:
|
149
149
|
continue
|
150
150
|
if line.startswith('#'):
|
151
151
|
continue
|
152
|
-
yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:])
|
152
|
+
yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:]), i + 1
|
153
|
+
|
154
|
+
|
155
|
+
class GeneratorScriptEntry(BaseModel):
|
156
|
+
path: pathlib.Path
|
157
|
+
line: int
|
153
158
|
|
154
159
|
|
155
160
|
class GenerationMetadata(BaseModel):
|
@@ -157,6 +162,7 @@ class GenerationMetadata(BaseModel):
|
|
157
162
|
|
158
163
|
copied_from: Optional[Testcase] = None
|
159
164
|
generator_call: Optional[GeneratorCall] = None
|
165
|
+
generator_script: Optional[GeneratorScriptEntry] = None
|
160
166
|
|
161
167
|
|
162
168
|
class GenerationTestcaseEntry(BaseModel):
|
@@ -280,7 +286,7 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
|
|
280
286
|
script = _run_generator_script(subgroup)
|
281
287
|
|
282
288
|
# Run each line from generator script.
|
283
|
-
for generator_name, args in _extract_script_lines(script):
|
289
|
+
for generator_name, args, line_number in _extract_script_lines(script):
|
284
290
|
call = GeneratorCall(name=generator_name, args=args)
|
285
291
|
visitor.visit(
|
286
292
|
GenerationTestcaseEntry(
|
@@ -288,6 +294,10 @@ def run_testcase_visitor(visitor: TestcaseVisitor):
|
|
288
294
|
subgroup_entry=_sub_entry(i),
|
289
295
|
metadata=GenerationMetadata(
|
290
296
|
generator_call=call,
|
297
|
+
generator_script=GeneratorScriptEntry(
|
298
|
+
path=subgroup.generatorScript.path,
|
299
|
+
line=line_number,
|
300
|
+
),
|
291
301
|
copied_to=_copied_to(i),
|
292
302
|
),
|
293
303
|
)
|
@@ -457,7 +467,7 @@ def generate_testcases(
|
|
457
467
|
else None,
|
458
468
|
)
|
459
469
|
|
460
|
-
|
470
|
+
testcase_utils.clear_built_testcases()
|
461
471
|
|
462
472
|
class BuildTestcaseVisitor(TestcaseGroupVisitor):
|
463
473
|
def visit(self, entry: GenerationTestcaseEntry):
|
@@ -487,6 +497,8 @@ def generate_output_for_testcase(
|
|
487
497
|
stderr_path: Optional[pathlib.Path] = None,
|
488
498
|
):
|
489
499
|
assert testcase.outputPath is not None
|
500
|
+
testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
|
501
|
+
testcase.outputPath.parent.mkdir(parents=True, exist_ok=True)
|
490
502
|
|
491
503
|
if testcase.outputPath.is_file():
|
492
504
|
# Output file was already copied over from manual tests.
|
@@ -540,8 +552,70 @@ def generate_output_for_testcase(
|
|
540
552
|
raise typer.Exit(1)
|
541
553
|
|
542
554
|
|
555
|
+
def extract_generation_testcases(
|
556
|
+
entries: List[TestcaseEntry],
|
557
|
+
) -> List[GenerationTestcaseEntry]:
|
558
|
+
# TODO: support subgroups.
|
559
|
+
groups = set(entry.group for entry in entries)
|
560
|
+
entry_keys = set(entry.key() for entry in entries)
|
561
|
+
|
562
|
+
res: List[GenerationTestcaseEntry] = []
|
563
|
+
|
564
|
+
class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
|
565
|
+
def should_visit_group(self, group_name: str) -> bool:
|
566
|
+
return group_name in groups
|
567
|
+
|
568
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
569
|
+
# TODO: support subgroups.
|
570
|
+
if entry.group_entry.key() not in entry_keys:
|
571
|
+
return
|
572
|
+
res.append(entry)
|
573
|
+
|
574
|
+
run_testcase_visitor(ExtractGenerationTestcasesVisitor())
|
575
|
+
return res
|
576
|
+
|
577
|
+
|
578
|
+
def extract_generation_testcases_from_groups(
|
579
|
+
groups: Optional[Set[str]] = None,
|
580
|
+
) -> List[GenerationTestcaseEntry]:
|
581
|
+
res: List[GenerationTestcaseEntry] = []
|
582
|
+
|
583
|
+
class ExtractGenerationTestcasesVisitor(TestcaseGroupVisitor):
|
584
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
585
|
+
res.append(entry)
|
586
|
+
|
587
|
+
run_testcase_visitor(ExtractGenerationTestcasesVisitor(groups))
|
588
|
+
return res
|
589
|
+
|
590
|
+
|
591
|
+
def extract_generation_testcases_from_patterns(
|
592
|
+
patterns: List[TestcasePattern],
|
593
|
+
) -> List[GenerationTestcaseEntry]:
|
594
|
+
res: List[GenerationTestcaseEntry] = []
|
595
|
+
|
596
|
+
class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
|
597
|
+
def should_visit_group(self, group_name: str) -> bool:
|
598
|
+
return any(pattern.intersecting_group(group_name) for pattern in patterns)
|
599
|
+
|
600
|
+
def should_visit_subgroup(self, subgroup_path: str) -> bool:
|
601
|
+
return any(
|
602
|
+
pattern.intersecting_group(subgroup_path) for pattern in patterns
|
603
|
+
)
|
604
|
+
|
605
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
606
|
+
if not any(
|
607
|
+
pattern.match(entry.group_entry) for pattern in patterns
|
608
|
+
) and not any(pattern.match(entry.subgroup_entry) for pattern in patterns):
|
609
|
+
return
|
610
|
+
res.append(entry)
|
611
|
+
|
612
|
+
run_testcase_visitor(ExtractGenerationTestcasesVisitor())
|
613
|
+
return res
|
614
|
+
|
615
|
+
|
543
616
|
def generate_outputs_for_testcases(
|
544
|
-
|
617
|
+
entries: List[TestcaseEntry],
|
618
|
+
progress: Optional[StatusProgress] = None,
|
545
619
|
):
|
546
620
|
def step():
|
547
621
|
if progress is not None:
|
@@ -563,50 +637,26 @@ def generate_outputs_for_testcases(
|
|
563
637
|
shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
|
564
638
|
gen_runs_dir.mkdir(parents=True, exist_ok=True)
|
565
639
|
|
566
|
-
|
567
|
-
def visit(self, entry: GenerationTestcaseEntry):
|
568
|
-
tc = entry.metadata.copied_to
|
569
|
-
if not tc.inputPath.is_file():
|
570
|
-
return
|
571
|
-
assert tc.outputPath is not None
|
640
|
+
generation_entries = extract_generation_testcases(entries)
|
572
641
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
)
|
579
|
-
raise typer.Exit(1)
|
642
|
+
for entry in generation_entries:
|
643
|
+
tc = entry.metadata.copied_to
|
644
|
+
if not tc.inputPath.is_file():
|
645
|
+
return
|
646
|
+
assert tc.outputPath is not None
|
580
647
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
648
|
+
if (
|
649
|
+
main_solution is None or solution_digest is None
|
650
|
+
) and not tc.outputPath.is_file():
|
651
|
+
console.console.print(
|
652
|
+
'[error]No main solution found to generate outputs for testcases.[/error]',
|
586
653
|
)
|
587
|
-
|
588
|
-
|
589
|
-
run_testcase_visitor(GenerateOutputsVisitor(groups))
|
590
|
-
|
591
|
-
|
592
|
-
def extract_generation_testcases(
|
593
|
-
entries: List[TestcaseEntry],
|
594
|
-
) -> List[GenerationTestcaseEntry]:
|
595
|
-
# TODO: support subgroups.
|
596
|
-
groups = set(entry.group for entry in entries)
|
597
|
-
entry_keys = set(entry.key() for entry in entries)
|
598
|
-
|
599
|
-
res: List[GenerationTestcaseEntry] = []
|
600
|
-
|
601
|
-
class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
|
602
|
-
def should_visit_group(self, group_name: str) -> bool:
|
603
|
-
return group_name in groups
|
604
|
-
|
605
|
-
def visit(self, entry: GenerationTestcaseEntry):
|
606
|
-
# TODO: support subgroups.
|
607
|
-
if entry.group_entry.key() not in entry_keys:
|
608
|
-
return
|
609
|
-
res.append(entry)
|
654
|
+
raise typer.Exit(1)
|
610
655
|
|
611
|
-
|
612
|
-
|
656
|
+
assert solution_digest is not None
|
657
|
+
generate_output_for_testcase(
|
658
|
+
solution_digest,
|
659
|
+
tc,
|
660
|
+
gen_runs_dir / 'main.stderr',
|
661
|
+
)
|
662
|
+
step()
|
rbx/box/generators_test.py
CHANGED
@@ -4,6 +4,7 @@ import pytest
|
|
4
4
|
|
5
5
|
from rbx.box import package
|
6
6
|
from rbx.box.generators import (
|
7
|
+
extract_generation_testcases_from_groups,
|
7
8
|
generate_outputs_for_testcases,
|
8
9
|
generate_testcases,
|
9
10
|
)
|
@@ -13,7 +14,10 @@ from rbx.testing_utils import print_directory_tree
|
|
13
14
|
@pytest.mark.test_pkg('box1')
|
14
15
|
def test_generator_works(pkg_from_testdata: pathlib.Path):
|
15
16
|
generate_testcases()
|
16
|
-
|
17
|
+
entries = [
|
18
|
+
entry.group_entry for entry in extract_generation_testcases_from_groups()
|
19
|
+
]
|
20
|
+
generate_outputs_for_testcases(entries)
|
17
21
|
|
18
22
|
# Debug when fail.
|
19
23
|
print_directory_tree(pkg_from_testdata)
|
rbx/box/main.py
CHANGED
@@ -37,8 +37,10 @@ from rbx.box import (
|
|
37
37
|
validators,
|
38
38
|
)
|
39
39
|
from rbx.box.contest import main as contest
|
40
|
+
from rbx.box.contest.contest_package import find_contest_yaml
|
40
41
|
from rbx.box.environment import VerificationLevel, get_environment_path
|
41
42
|
from rbx.box.packaging import main as packaging
|
43
|
+
from rbx.box.testcases import main as testcases
|
42
44
|
from rbx.box.solutions import (
|
43
45
|
estimate_time_limit,
|
44
46
|
get_exact_matching_solutions,
|
@@ -49,7 +51,7 @@ from rbx.box.solutions import (
|
|
49
51
|
run_solutions,
|
50
52
|
)
|
51
53
|
from rbx.box.statements import build_statements
|
52
|
-
from rbx.box.
|
54
|
+
from rbx.box.testcase_utils import TestcaseEntry
|
53
55
|
|
54
56
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
55
57
|
app.add_typer(
|
@@ -82,6 +84,12 @@ app.add_typer(
|
|
82
84
|
app.add_typer(
|
83
85
|
contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
|
84
86
|
)
|
87
|
+
app.add_typer(
|
88
|
+
testcases.app,
|
89
|
+
name='testcases, tc, t',
|
90
|
+
cls=annotations.AliasGroup,
|
91
|
+
help='Testcase management.',
|
92
|
+
)
|
85
93
|
|
86
94
|
|
87
95
|
@app.callback()
|
@@ -166,6 +174,21 @@ def run(
|
|
166
174
|
)
|
167
175
|
check = False
|
168
176
|
|
177
|
+
tracked_solutions = None
|
178
|
+
if outcome is not None:
|
179
|
+
tracked_solutions = {
|
180
|
+
str(solution.path)
|
181
|
+
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
182
|
+
}
|
183
|
+
if solution:
|
184
|
+
tracked_solutions = {solution}
|
185
|
+
|
186
|
+
if choice:
|
187
|
+
tracked_solutions = set(pick_solutions(tracked_solutions))
|
188
|
+
if not tracked_solutions:
|
189
|
+
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
190
|
+
raise typer.Exit(1)
|
191
|
+
|
169
192
|
if not builder.build(verification=verification, output=check):
|
170
193
|
return
|
171
194
|
|
@@ -195,21 +218,6 @@ def run(
|
|
195
218
|
'and the environment default time limit will be used instead.[/warning]'
|
196
219
|
)
|
197
220
|
|
198
|
-
tracked_solutions = None
|
199
|
-
if outcome is not None:
|
200
|
-
tracked_solutions = {
|
201
|
-
str(solution.path)
|
202
|
-
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
203
|
-
}
|
204
|
-
if solution:
|
205
|
-
tracked_solutions = {solution}
|
206
|
-
|
207
|
-
if choice:
|
208
|
-
tracked_solutions = set(pick_solutions(tracked_solutions))
|
209
|
-
if not tracked_solutions:
|
210
|
-
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
211
|
-
raise typer.Exit(1)
|
212
|
-
|
213
221
|
if sanitized and tracked_solutions is None:
|
214
222
|
console.console.print(
|
215
223
|
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
@@ -441,6 +449,15 @@ def create(
|
|
441
449
|
Optional[str], typer.Option(help='Preset to use when creating the problem.')
|
442
450
|
] = None,
|
443
451
|
):
|
452
|
+
if find_contest_yaml() is not None:
|
453
|
+
console.console.print(
|
454
|
+
'[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
|
455
|
+
)
|
456
|
+
console.console.print(
|
457
|
+
'[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
|
458
|
+
)
|
459
|
+
raise typer.Exit(1)
|
460
|
+
|
444
461
|
if preset is not None:
|
445
462
|
creation.create(name, preset=preset)
|
446
463
|
return
|
rbx/box/package.py
CHANGED
@@ -121,8 +121,8 @@ def save_package(
|
|
121
121
|
problem_yaml_path.write_text(utils.model_to_yaml(package))
|
122
122
|
|
123
123
|
|
124
|
-
def get_ruyaml() -> Tuple[ruyaml.YAML, ruyaml.Any]:
|
125
|
-
problem_yaml_path = find_problem_yaml()
|
124
|
+
def get_ruyaml(root: pathlib.Path = pathlib.Path()) -> Tuple[ruyaml.YAML, ruyaml.Any]:
|
125
|
+
problem_yaml_path = find_problem_yaml(root)
|
126
126
|
if problem_yaml_path is None:
|
127
127
|
console.console.print(
|
128
128
|
f'Problem not found in {pathlib.Path().absolute()}', style='error'
|
rbx/box/schema.py
CHANGED
@@ -192,6 +192,9 @@ class GeneratorCall(BaseModel):
|
|
192
192
|
default=None, description='The arguments to pass to the generator.'
|
193
193
|
)
|
194
194
|
|
195
|
+
def __str__(self) -> str:
|
196
|
+
return f'{self.name} {self.args}'
|
197
|
+
|
195
198
|
|
196
199
|
class TestcaseSubgroup(BaseModel):
|
197
200
|
model_config = ConfigDict(extra='forbid')
|
rbx/box/solutions.py
CHANGED
@@ -42,7 +42,7 @@ from rbx.box.schema import (
|
|
42
42
|
Testcase,
|
43
43
|
TestcaseGroup,
|
44
44
|
)
|
45
|
-
from rbx.box.
|
45
|
+
from rbx.box.testcase_utils import TestcaseEntry, find_built_testcases
|
46
46
|
from rbx.grading.steps import (
|
47
47
|
DigestOrDest,
|
48
48
|
DigestOrSource,
|
rbx/box/solutions_test.py
CHANGED
@@ -5,6 +5,7 @@ import pytest
|
|
5
5
|
|
6
6
|
from rbx.box.environment import VerificationLevel
|
7
7
|
from rbx.box.generators import (
|
8
|
+
extract_generation_testcases_from_groups,
|
8
9
|
generate_outputs_for_testcases,
|
9
10
|
generate_testcases,
|
10
11
|
)
|
@@ -18,7 +19,10 @@ from rbx.grading.steps import Outcome
|
|
18
19
|
@pytest.mark.test_pkg('box1')
|
19
20
|
def test_solutions(pkg_from_testdata: pathlib.Path):
|
20
21
|
generate_testcases()
|
21
|
-
|
22
|
+
entries = [
|
23
|
+
entry.group_entry for entry in extract_generation_testcases_from_groups()
|
24
|
+
]
|
25
|
+
generate_outputs_for_testcases(entries)
|
22
26
|
|
23
27
|
result = run_solutions(verification=VerificationLevel.FULL)
|
24
28
|
res = asyncio.run(convert_list_of_solution_evaluations_to_dict(result.items))
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shutil
|
3
|
+
from typing import List, Optional, Tuple
|
4
|
+
|
5
|
+
import typer
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
from rbx import console
|
9
|
+
from rbx.box import package
|
10
|
+
from rbx.box.package import get_build_testgroup_path, get_build_tests_path
|
11
|
+
from rbx.box.schema import Testcase, TestcaseGroup
|
12
|
+
|
13
|
+
|
14
|
+
class TestcaseEntry(BaseModel):
|
15
|
+
group: str
|
16
|
+
index: int
|
17
|
+
|
18
|
+
def key(self) -> Tuple[str, int]:
|
19
|
+
return self.group, self.index
|
20
|
+
|
21
|
+
def __str__(self) -> str:
|
22
|
+
return f'{self.group}/{self.index}'
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def parse(cls, spec: str) -> 'TestcaseEntry':
|
26
|
+
if spec.count('/') != 1:
|
27
|
+
console.console.print(
|
28
|
+
f'[error]Invalid testcase spec [item]{spec}[/item]. Format should be [item]<group>/<index>[/item].[/error]',
|
29
|
+
)
|
30
|
+
raise typer.Exit(1)
|
31
|
+
group, index = spec.split('/')
|
32
|
+
return TestcaseEntry(group=group.strip(), index=int(index))
|
33
|
+
|
34
|
+
|
35
|
+
class TestcasePattern(BaseModel):
|
36
|
+
group_prefix: List[str]
|
37
|
+
index: Optional[int] = None
|
38
|
+
|
39
|
+
def group(self) -> str:
|
40
|
+
return '/'.join(self.group_prefix)
|
41
|
+
|
42
|
+
def match(self, group_entry: TestcaseEntry) -> bool:
|
43
|
+
# TODO: support subgroups.
|
44
|
+
entry_parts = tuple(group_entry.group.split('/'))
|
45
|
+
if self.index is not None:
|
46
|
+
if self.index != group_entry.index:
|
47
|
+
return False
|
48
|
+
if tuple(self.group_prefix) != entry_parts:
|
49
|
+
return False
|
50
|
+
return True
|
51
|
+
|
52
|
+
if len(self.group_prefix) > len(entry_parts):
|
53
|
+
return False
|
54
|
+
|
55
|
+
return tuple(self.group_prefix) == entry_parts[: len(self.group_prefix)]
|
56
|
+
|
57
|
+
def with_no_index(self) -> 'TestcasePattern':
|
58
|
+
return self.model_copy(update={'index': None})
|
59
|
+
|
60
|
+
def intersecting_group(self, group: str) -> bool:
|
61
|
+
if self.with_no_index().match(TestcaseEntry(group=group, index=0)):
|
62
|
+
# If the group is inside the pattern, then it is a match.
|
63
|
+
return True
|
64
|
+
if TestcasePattern.parse(group).match(
|
65
|
+
TestcaseEntry(group=self.group(), index=0)
|
66
|
+
):
|
67
|
+
# If the group is a prefix of the pattern, then it is a match.
|
68
|
+
return True
|
69
|
+
return False
|
70
|
+
|
71
|
+
def __str__(self) -> str:
|
72
|
+
prefix = '/'.join(self.group_prefix)
|
73
|
+
if not prefix:
|
74
|
+
return '*'
|
75
|
+
if self.index is None:
|
76
|
+
return f'{prefix}/'
|
77
|
+
return f'{prefix}/{self.index}'
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
def parse(cls, spec: str) -> 'TestcasePattern':
|
81
|
+
spec = spec.strip()
|
82
|
+
if spec == '*':
|
83
|
+
return cls(group_prefix=[], index=None)
|
84
|
+
|
85
|
+
parts = spec.split('/')
|
86
|
+
if len(parts) <= 1:
|
87
|
+
return cls(group_prefix=parts, index=None)
|
88
|
+
|
89
|
+
if parts[-1].isdigit():
|
90
|
+
return cls(group_prefix=parts[:-1], index=int(parts[-1]))
|
91
|
+
|
92
|
+
return cls(group_prefix=parts, index=None)
|
93
|
+
|
94
|
+
|
95
|
+
class TestcaseData(BaseModel):
|
96
|
+
input: str
|
97
|
+
output: str
|
98
|
+
|
99
|
+
|
100
|
+
def find_built_testcases(group: TestcaseGroup) -> List[Testcase]:
|
101
|
+
inputs = find_built_testcase_inputs(group)
|
102
|
+
|
103
|
+
testcases = []
|
104
|
+
for input in inputs:
|
105
|
+
output = input.with_suffix('.out')
|
106
|
+
testcases.append(Testcase(inputPath=input, outputPath=output))
|
107
|
+
return testcases
|
108
|
+
|
109
|
+
|
110
|
+
def find_built_testcase_inputs(group: TestcaseGroup) -> List[pathlib.Path]:
|
111
|
+
testgroup_path = get_build_testgroup_path(group.name)
|
112
|
+
if not testgroup_path.is_dir():
|
113
|
+
console.console.print(
|
114
|
+
f'Testgroup {group.name} is not generated in build folder'
|
115
|
+
)
|
116
|
+
raise typer.Exit(1)
|
117
|
+
|
118
|
+
return sorted(testgroup_path.glob('*.in'))
|
119
|
+
|
120
|
+
|
121
|
+
def clear_built_testcases():
|
122
|
+
shutil.rmtree(str(get_build_tests_path()), ignore_errors=True)
|
123
|
+
|
124
|
+
|
125
|
+
def get_samples() -> List[Testcase]:
|
126
|
+
tcs = find_built_testcases(package.get_testgroup('samples'))
|
127
|
+
return [
|
128
|
+
Testcase(
|
129
|
+
inputPath=tc.inputPath.resolve(),
|
130
|
+
outputPath=tc.outputPath.resolve()
|
131
|
+
if tc.outputPath is not None and tc.outputPath.is_file()
|
132
|
+
else None,
|
133
|
+
)
|
134
|
+
for tc in tcs
|
135
|
+
]
|
File without changes
|
@@ -0,0 +1,158 @@
|
|
1
|
+
import pathlib
|
2
|
+
from typing import Annotated, List, Optional
|
3
|
+
|
4
|
+
import typer
|
5
|
+
|
6
|
+
from rbx import annotations, config, utils
|
7
|
+
from rbx.box import package
|
8
|
+
from rbx.box.generators import (
|
9
|
+
GenerationTestcaseEntry,
|
10
|
+
extract_generation_testcases,
|
11
|
+
extract_generation_testcases_from_patterns,
|
12
|
+
generate_outputs_for_testcases,
|
13
|
+
generate_standalone,
|
14
|
+
)
|
15
|
+
from rbx.box.testcase_utils import TestcaseEntry, TestcasePattern
|
16
|
+
from rbx.console import console
|
17
|
+
|
18
|
+
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
19
|
+
|
20
|
+
|
21
|
+
def _find_testcase(entry: TestcaseEntry) -> GenerationTestcaseEntry:
|
22
|
+
extracted = extract_generation_testcases([entry])
|
23
|
+
if not extracted:
|
24
|
+
console.print(f'[error]Testcase [item]{entry}[/item] not found.[/error]')
|
25
|
+
raise typer.Exit(1)
|
26
|
+
return extracted[0]
|
27
|
+
|
28
|
+
|
29
|
+
def _should_generate_output(entry: GenerationTestcaseEntry) -> bool:
|
30
|
+
return (
|
31
|
+
entry.metadata.copied_from is None
|
32
|
+
or entry.metadata.copied_from.outputPath is None
|
33
|
+
) and package.get_main_solution() is not None
|
34
|
+
|
35
|
+
|
36
|
+
def _generate_input_for_editing(
|
37
|
+
entry: GenerationTestcaseEntry,
|
38
|
+
output: bool = True,
|
39
|
+
progress: Optional[utils.StatusProgress] = None,
|
40
|
+
) -> pathlib.Path:
|
41
|
+
if (
|
42
|
+
output and _should_generate_output(entry)
|
43
|
+
) or entry.metadata.copied_from is None:
|
44
|
+
generate_standalone(
|
45
|
+
entry.metadata,
|
46
|
+
validate=False,
|
47
|
+
group_entry=entry.group_entry,
|
48
|
+
progress=progress,
|
49
|
+
)
|
50
|
+
if entry.metadata.copied_from is not None:
|
51
|
+
return entry.metadata.copied_from.inputPath
|
52
|
+
return entry.metadata.copied_to.inputPath
|
53
|
+
|
54
|
+
|
55
|
+
def _generate_output_for_editing(
|
56
|
+
entry: GenerationTestcaseEntry,
|
57
|
+
progress: Optional[utils.StatusProgress] = None,
|
58
|
+
) -> Optional[pathlib.Path]:
|
59
|
+
if (
|
60
|
+
entry.metadata.copied_from is not None
|
61
|
+
and entry.metadata.copied_from.outputPath is not None
|
62
|
+
):
|
63
|
+
return entry.metadata.copied_from.outputPath
|
64
|
+
if not _should_generate_output(entry):
|
65
|
+
return None
|
66
|
+
generate_outputs_for_testcases([entry.group_entry], progress=progress)
|
67
|
+
return entry.metadata.copied_to.outputPath
|
68
|
+
|
69
|
+
|
70
|
+
def _generate_for_editing(
|
71
|
+
entry: GenerationTestcaseEntry,
|
72
|
+
input: bool,
|
73
|
+
output: bool,
|
74
|
+
progress: Optional[utils.StatusProgress] = None,
|
75
|
+
) -> List[pathlib.Path]:
|
76
|
+
res = []
|
77
|
+
input_path = _generate_input_for_editing(entry, output=output, progress=progress)
|
78
|
+
if input:
|
79
|
+
res.append(input_path)
|
80
|
+
if output:
|
81
|
+
output_path = _generate_output_for_editing(entry, progress=progress)
|
82
|
+
if output_path is not None:
|
83
|
+
res.append(output_path)
|
84
|
+
return res
|
85
|
+
|
86
|
+
|
87
|
+
@app.command('view, v', help='View a testcase in your default editor.')
|
88
|
+
def view(
|
89
|
+
tc: Annotated[
|
90
|
+
str,
|
91
|
+
typer.Argument(help='Testcase to view. Format: [group]/[index].'),
|
92
|
+
],
|
93
|
+
input_only: bool = typer.Option(
|
94
|
+
False,
|
95
|
+
'--input',
|
96
|
+
'-i',
|
97
|
+
help='Whether to open only the input file in the editor.',
|
98
|
+
),
|
99
|
+
output_only: bool = typer.Option(
|
100
|
+
False,
|
101
|
+
'--output',
|
102
|
+
'-o',
|
103
|
+
help='Whether to open only the output file in the editor.',
|
104
|
+
),
|
105
|
+
):
|
106
|
+
if input_only and output_only:
|
107
|
+
console.print(
|
108
|
+
'[error]Flags --input and --output cannot be used together.[/error]'
|
109
|
+
)
|
110
|
+
raise typer.Exit(1)
|
111
|
+
|
112
|
+
entry = TestcaseEntry.parse(tc)
|
113
|
+
testcase = _find_testcase(entry)
|
114
|
+
|
115
|
+
with utils.StatusProgress('Preparing testcase...') as s:
|
116
|
+
items = _generate_for_editing(
|
117
|
+
testcase, input=not output_only, output=not input_only, progress=s
|
118
|
+
)
|
119
|
+
config.edit_multiple(items, readonly=True)
|
120
|
+
|
121
|
+
|
122
|
+
@app.command('info, i', help='Show information about testcases.')
|
123
|
+
def info(
|
124
|
+
pattern: Annotated[
|
125
|
+
Optional[str],
|
126
|
+
typer.Argument(
|
127
|
+
help='Testcases to detail, as a pattern. Might be a group, or a specific test in the format [group]/[index].'
|
128
|
+
),
|
129
|
+
] = None,
|
130
|
+
):
|
131
|
+
tc_pattern = TestcasePattern.parse(pattern or '*')
|
132
|
+
testcases = extract_generation_testcases_from_patterns([tc_pattern])
|
133
|
+
if not testcases:
|
134
|
+
console.print(
|
135
|
+
f'[error]No testcases found matching pattern [item]{pattern}[/item].[/error]'
|
136
|
+
)
|
137
|
+
raise typer.Exit(1)
|
138
|
+
|
139
|
+
for testcase in testcases:
|
140
|
+
console.print(f'[status]Identifier:[/status] {testcase.group_entry}')
|
141
|
+
if testcase.metadata.generator_call is not None:
|
142
|
+
console.print(
|
143
|
+
f'[status]Generator call:[/status] {testcase.metadata.generator_call}'
|
144
|
+
)
|
145
|
+
if testcase.metadata.copied_from is not None:
|
146
|
+
console.print(
|
147
|
+
f'[status]Input file:[/status] {testcase.metadata.copied_from.inputPath}'
|
148
|
+
)
|
149
|
+
if testcase.metadata.copied_from.outputPath is not None:
|
150
|
+
console.print(
|
151
|
+
f'[status]Output file:[/status] {testcase.metadata.copied_from.outputPath}'
|
152
|
+
)
|
153
|
+
|
154
|
+
if testcase.metadata.generator_script is not None:
|
155
|
+
console.print(
|
156
|
+
f'[status]Generator script:[/status] {testcase.metadata.generator_script.path}, line {testcase.metadata.generator_script.line}'
|
157
|
+
)
|
158
|
+
console.print()
|
rbx/box/validators.py
CHANGED
@@ -9,7 +9,7 @@ from rbx import console
|
|
9
9
|
from rbx.box import package
|
10
10
|
from rbx.box.code import SanitizationLevel, compile_item, run_item
|
11
11
|
from rbx.box.schema import CodeItem, Primitive
|
12
|
-
from rbx.box.
|
12
|
+
from rbx.box.testcase_utils import find_built_testcase_inputs
|
13
13
|
from rbx.grading.judge.sandbox import SandboxBase
|
14
14
|
from rbx.grading.steps import (
|
15
15
|
DigestHolder,
|
rbx/config.py
CHANGED
@@ -5,6 +5,7 @@ import os
|
|
5
5
|
import pathlib
|
6
6
|
import shutil
|
7
7
|
import subprocess
|
8
|
+
import tempfile
|
8
9
|
from typing import Any, Dict, List, Optional
|
9
10
|
|
10
11
|
import requests
|
@@ -213,13 +214,40 @@ def get_editor():
|
|
213
214
|
return get_config().editor or os.environ.get('EDITOR', None)
|
214
215
|
|
215
216
|
|
216
|
-
def
|
217
|
+
def is_vim_editor() -> bool:
|
218
|
+
editor = get_editor()
|
219
|
+
if editor is None:
|
220
|
+
return False
|
221
|
+
return editor.endswith('vim')
|
222
|
+
|
223
|
+
|
224
|
+
def open_editor(path: Any, *args):
|
217
225
|
editor = get_editor()
|
218
226
|
if editor is None:
|
219
227
|
raise Exception('No editor found. Please set the EDITOR environment variable.')
|
220
228
|
subprocess.run([editor, str(path), *[str(arg) for arg in args]])
|
221
229
|
|
222
230
|
|
231
|
+
def _readonly_copy(path: pathlib.Path) -> pathlib.Path:
|
232
|
+
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
|
233
|
+
shutil.copy(str(path), temp_file.name)
|
234
|
+
temp_file.close()
|
235
|
+
return pathlib.Path(temp_file.name)
|
236
|
+
|
237
|
+
|
238
|
+
def edit_multiple(paths: List[pathlib.Path], readonly: bool = False):
|
239
|
+
if is_vim_editor():
|
240
|
+
if readonly:
|
241
|
+
open_editor('-R', '-O', *paths)
|
242
|
+
else:
|
243
|
+
open_editor('-O', *paths)
|
244
|
+
return
|
245
|
+
|
246
|
+
if readonly:
|
247
|
+
paths = [_readonly_copy(path) for path in paths]
|
248
|
+
open_editor(*paths)
|
249
|
+
|
250
|
+
|
223
251
|
@functools.cache
|
224
252
|
def get_config() -> Config:
|
225
253
|
config_path = get_config_path()
|
@@ -2,29 +2,29 @@ rbx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
rbx/annotations.py,sha256=Z3jBUyZoXkrz34jko3Rft0bnMME6nWb0vsV5I3HlgR0,3064
|
3
3
|
rbx/autoenum.py,sha256=cusv8ClXRlDVvhZ8eDrtYcL_2peXlHugAey_ht8roXk,12025
|
4
4
|
rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
rbx/box/builder.py,sha256=
|
5
|
+
rbx/box/builder.py,sha256=HeJMjzp78tbJS_9rvMR0qJXC6cmXIi6F59xAruX8Rpk,3483
|
6
6
|
rbx/box/cd.py,sha256=9a_SOnzoJBXxxffp4Wbf3UKXIwKuN3Hvj7K6SocALwE,1194
|
7
7
|
rbx/box/checkers.py,sha256=VpgDzevOK7hrffG2zJGxquNiu-a9Fl3wquLn7xadcK0,6285
|
8
8
|
rbx/box/code.py,sha256=UFy7jOeTvxtIu9pdVUDv2-D6IW-beJGPC3uCanIKZh0,13412
|
9
9
|
rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
|
10
10
|
rbx/box/conftest.py,sha256=sEmciXSeDC-wmrZ1JSxbsUenKNP_VWW32mrCun2pY3I,1070
|
11
11
|
rbx/box/contest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
|
-
rbx/box/contest/build_contest_statements.py,sha256=
|
12
|
+
rbx/box/contest/build_contest_statements.py,sha256=H2MwmkiPO_cHUEenzfPxHuJ3XcwjHakGZwKojNJQt74,11380
|
13
13
|
rbx/box/contest/contest_package.py,sha256=OaUbpBtkhkgOPzJ1ccI_Vq4FMSaJvZm3gMOKfVY8oy4,3032
|
14
14
|
rbx/box/contest/contest_utils.py,sha256=TDE7I6YQJlu4dQd68wzOp019bNgqiT0RlM-LMQMjL9w,301
|
15
|
-
rbx/box/contest/main.py,sha256=
|
15
|
+
rbx/box/contest/main.py,sha256=oL-GbyLKdpMjIWiSuWTQgRhQ9hcb7DuNn0axkunx0io,7436
|
16
16
|
rbx/box/contest/schema.py,sha256=JMAig5WpaOahNgAHxA9vX4zYeVYDxpjKP_PFGvmmkE0,4954
|
17
17
|
rbx/box/contest/statements.py,sha256=Pe4uo1hxvEON8O11VAzsOP3DxUel0vmwiAmolh4ltEs,2910
|
18
|
-
rbx/box/creation.py,sha256=
|
18
|
+
rbx/box/creation.py,sha256=Evz7K6JoarD-4JJQsZsgoxU9FgCF9Z7-LfuroG4Cqls,2444
|
19
19
|
rbx/box/deferred.py,sha256=II3X9e87JCOZtmspnHh-n4PFqh-FsH_oc0XJHZ9ZYVQ,691
|
20
20
|
rbx/box/download.py,sha256=MFP-R26JiYGAP89I0TK-0fYc69Fsd20tsBqgtRCy5AE,2234
|
21
21
|
rbx/box/environment.py,sha256=47NtyuVC6zSQKAtQaXPEXvqcD-KJiuWRpWF8pYvcG4c,11158
|
22
22
|
rbx/box/extensions.py,sha256=Von8kIeXvNFTkGlMRMTvL2HIHPwlkuiMswr-ydbGV1w,519
|
23
23
|
rbx/box/formatting.py,sha256=3phFRHzqVXj4Ok1yDhCq6Clbw6KlqwJNpMhs--oTWFI,405
|
24
|
-
rbx/box/generators.py,sha256=
|
25
|
-
rbx/box/generators_test.py,sha256=
|
26
|
-
rbx/box/main.py,sha256=
|
27
|
-
rbx/box/package.py,sha256=
|
24
|
+
rbx/box/generators.py,sha256=OYAOhLQJMQFoQ2Tl-o2QO5sJL_HaWFPH9vnISr9rMno,22404
|
25
|
+
rbx/box/generators_test.py,sha256=WvS5GH8QMInAXvR2nyeEfgtx8FHIH1ZSqYGkyEa1sqE,1910
|
26
|
+
rbx/box/main.py,sha256=0lrPLJTGvatcGZCYgLRKKaRkWKEFmNKyA48Shm-uof8,24308
|
27
|
+
rbx/box/package.py,sha256=80SDHvSzfraCUYutMn_kwsFsmmrSZiaeRHhhrWGmIY4,12081
|
28
28
|
rbx/box/packaging/boca/extension.py,sha256=hQhcbocNfW2ESv5RalS1wf6uvOoOfOnR_gHvbXUbSzY,852
|
29
29
|
rbx/box/packaging/boca/packager.py,sha256=FOhSRg5K5Y4qNB0WyTR3DKgrpObf9I0JbyGpJHOtxpo,10673
|
30
30
|
rbx/box/packaging/contest_main.py,sha256=Hbxh7geNqrePs5tWhPgdg5W2qhaW5yoreK_VP0Sm19k,2727
|
@@ -39,13 +39,13 @@ rbx/box/presets/lock_schema.py,sha256=6sRPnyePOC8yy-5WcD5JRZdDJHf8loqbvpQ1IPiOU9
|
|
39
39
|
rbx/box/presets/schema.py,sha256=mZmSPkQsw7eQM0lQN6er1MO_LiW1ObwwAZFDK0F5fxE,1962
|
40
40
|
rbx/box/retries.py,sha256=z7cIh1QmLVUsTr3Attt_28dbwNg6KWTwpulcWCFwMPo,4667
|
41
41
|
rbx/box/sanitizers/warning_stack.py,sha256=RI97_GJgdjTKIXY_r0EKp5h0qQQSDSdNDh5K7zINrqs,2861
|
42
|
-
rbx/box/schema.py,sha256=
|
42
|
+
rbx/box/schema.py,sha256=n2CMfoDlS0GHqt7gT3GOv6ICA8_eZnjDildMnSY-wEs,14380
|
43
43
|
rbx/box/setter_config.py,sha256=ZM7_G2tbaixaFr0NvRaXkowwfxSWF2Gb4XHBsr2Prpc,4279
|
44
|
-
rbx/box/solutions.py,sha256=
|
45
|
-
rbx/box/solutions_test.py,sha256=
|
44
|
+
rbx/box/solutions.py,sha256=3lb0IJWjbF_RBaxr9fr6B9HYA-v-_cnkCx6n_Ps5lcg,43979
|
45
|
+
rbx/box/solutions_test.py,sha256=9l5yk6vLtCk-2XDK-5NNUbgjvWmwTUa9lsvLvQ3KgSI,1681
|
46
46
|
rbx/box/state.py,sha256=yTpjfASpnSXkRB3JiDNvCg5b9JNnNxuYT4uMcbdr59s,109
|
47
47
|
rbx/box/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
|
-
rbx/box/statements/build_statements.py,sha256=
|
48
|
+
rbx/box/statements/build_statements.py,sha256=qR6WxUNvSQTBs241qH-qDRbD8IoN_hKeZ2TwY7NXJBQ,12024
|
49
49
|
rbx/box/statements/builders.py,sha256=pKevMe3GwiXN_k0L2l155sY1rSLneSs0DLJ5T7WhxKQ,11152
|
50
50
|
rbx/box/statements/joiners.py,sha256=ZbxomnMjEFT8yf5WSWUB4tBa3DL3AhjGEuh8uqHyDdg,2837
|
51
51
|
rbx/box/statements/latex.py,sha256=LkcHwXjMFxbw--Gj9T1VkFKQFsXhY9dN7xZHpZycNW8,1346
|
@@ -55,17 +55,19 @@ rbx/box/stresses.py,sha256=ceFpkZVKBfKKVrKFjeARdub5VGKmU9JPZwj-FxcqYjQ,11771
|
|
55
55
|
rbx/box/stressing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
56
|
rbx/box/stressing/finder_parser.py,sha256=jXpYNa4FyugzmHi3r96Uv4rU1krRQJc5Ihr9jf1cvNo,11918
|
57
57
|
rbx/box/stressing/generator_parser.py,sha256=oHZryjR3YohgaSO9WEirQ7b2e-98WgZStF0N99W4Thw,7380
|
58
|
-
rbx/box/
|
58
|
+
rbx/box/testcase_utils.py,sha256=hlVvrmf7lp6DiRd2E-sFHVsvbctQujXCr7DrGi0040I,4035
|
59
|
+
rbx/box/testcases/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
|
+
rbx/box/testcases/main.py,sha256=6ywg3fSMW1hllHobg6Wcgw1pQczwjlJ3J10dgQtCdss,5212
|
59
61
|
rbx/box/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
62
|
rbx/box/ui/captured_log.py,sha256=ptICDPViVnz-_2NfrcB0SSBXNW5L74zI-vAZNN7kSok,11319
|
61
63
|
rbx/box/ui/css/app.tcss,sha256=apd5PkPEvl5jK3kE2qrxPyVED1VnvSsj08QQwzUPwEA,786
|
62
64
|
rbx/box/ui/main.py,sha256=b0rHcBF42W4AOCv7WhtiGf_rUnY0yxpqO5oj3wfR4R4,984
|
63
65
|
rbx/box/ui/run.py,sha256=wMEXrEFdQvMHz2hRKAFIithTnTtaL0kNQZu0jKmb8jI,7060
|
64
|
-
rbx/box/validators.py,sha256=
|
66
|
+
rbx/box/validators.py,sha256=RtXyuiBDourcBp__YEibWLpr4Wj7YcIYOpkR8Vk6oro,9028
|
65
67
|
rbx/box/validators_test.py,sha256=hriR6rD32Ouu64eKYYTPLZVvqMxXj7Q2h1l_JAefL7U,344
|
66
68
|
rbx/checker.py,sha256=pj1jO3my48ru-qugbER5onccANCjoR0-PaFe3H3VGEY,4118
|
67
69
|
rbx/clone.py,sha256=wpHyED0_7ST7LD3vj7HjXhzqEzlwh6dRQvKQVDYhGeU,6744
|
68
|
-
rbx/config.py,sha256=
|
70
|
+
rbx/config.py,sha256=78xKH0zddEF32uIbIs10snqvACx20DmzjQTCex7w95Y,8136
|
69
71
|
rbx/conftest.py,sha256=ouilbOIpvX8jTEdCAiWT85CbdBQKUUf41BjmDI82u-Y,967
|
70
72
|
rbx/console.py,sha256=X8EJy68OROgh6ao3ZcUjZm5Y56VFMzen58ywAuQ7pAU,990
|
71
73
|
rbx/create.py,sha256=ezUq9KiSA-88ASd8CtjWXw8UB4LCaQ3Gib3OgvsLK-Q,986
|
@@ -165,8 +167,8 @@ rbx/testdata/caching/executable.py,sha256=WKRHNf_fprFJd1Fq1ubmQtR3mZzTYVNwKPLWuZ
|
|
165
167
|
rbx/testdata/compatible,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
166
168
|
rbx/testing_utils.py,sha256=ZZLKMUHlZ4HwsuNY50jqSBJ9HhpnFdba7opjDsvXE1U,2084
|
167
169
|
rbx/utils.py,sha256=AITbkWpWtSp-x3Xept_aObfj_jPL7XL0JJoz5-F9Fp8,4671
|
168
|
-
rbx_cp-0.5.
|
169
|
-
rbx_cp-0.5.
|
170
|
-
rbx_cp-0.5.
|
171
|
-
rbx_cp-0.5.
|
172
|
-
rbx_cp-0.5.
|
170
|
+
rbx_cp-0.5.36.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
171
|
+
rbx_cp-0.5.36.dist-info/METADATA,sha256=5_8xG48aXtFz3-953WgYq5CUXpHu4snzLQ5iQKmbdhA,3263
|
172
|
+
rbx_cp-0.5.36.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
173
|
+
rbx_cp-0.5.36.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
|
174
|
+
rbx_cp-0.5.36.dist-info/RECORD,,
|
rbx/box/testcases.py
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
import shutil
|
3
|
-
from typing import List, Tuple
|
4
|
-
|
5
|
-
import typer
|
6
|
-
from pydantic import BaseModel
|
7
|
-
|
8
|
-
from rbx import console
|
9
|
-
from rbx.box import package
|
10
|
-
from rbx.box.package import get_build_testgroup_path, get_build_tests_path
|
11
|
-
from rbx.box.schema import Testcase, TestcaseGroup
|
12
|
-
|
13
|
-
|
14
|
-
class TestcaseEntry(BaseModel):
|
15
|
-
group: str
|
16
|
-
index: int
|
17
|
-
|
18
|
-
def key(self) -> Tuple[str, int]:
|
19
|
-
return self.group, self.index
|
20
|
-
|
21
|
-
def __str__(self) -> str:
|
22
|
-
return f'{self.group}/{self.index}'
|
23
|
-
|
24
|
-
@classmethod
|
25
|
-
def parse(cls, spec: str) -> 'TestcaseEntry':
|
26
|
-
group, index = spec.split('/')
|
27
|
-
return TestcaseEntry(group=group.strip(), index=int(index))
|
28
|
-
|
29
|
-
|
30
|
-
class TestcaseData(BaseModel):
|
31
|
-
input: str
|
32
|
-
output: str
|
33
|
-
|
34
|
-
|
35
|
-
def find_built_testcases(group: TestcaseGroup) -> List[Testcase]:
|
36
|
-
inputs = find_built_testcase_inputs(group)
|
37
|
-
|
38
|
-
testcases = []
|
39
|
-
for input in inputs:
|
40
|
-
output = input.with_suffix('.out')
|
41
|
-
testcases.append(Testcase(inputPath=input, outputPath=output))
|
42
|
-
return testcases
|
43
|
-
|
44
|
-
|
45
|
-
def find_built_testcase_inputs(group: TestcaseGroup) -> List[pathlib.Path]:
|
46
|
-
testgroup_path = get_build_testgroup_path(group.name)
|
47
|
-
if not testgroup_path.is_dir():
|
48
|
-
console.console.print(
|
49
|
-
f'Testgroup {group.name} is not generated in build folder'
|
50
|
-
)
|
51
|
-
raise typer.Exit(1)
|
52
|
-
|
53
|
-
return sorted(testgroup_path.glob('*.in'))
|
54
|
-
|
55
|
-
|
56
|
-
def clear_built_testcases():
|
57
|
-
shutil.rmtree(str(get_build_tests_path()), ignore_errors=True)
|
58
|
-
|
59
|
-
|
60
|
-
def get_samples() -> List[Testcase]:
|
61
|
-
tcs = find_built_testcases(package.get_testgroup('samples'))
|
62
|
-
return [
|
63
|
-
Testcase(
|
64
|
-
inputPath=tc.inputPath.resolve(),
|
65
|
-
outputPath=tc.outputPath.resolve()
|
66
|
-
if tc.outputPath is not None and tc.outputPath.is_file()
|
67
|
-
else None,
|
68
|
-
)
|
69
|
-
for tc in tcs
|
70
|
-
]
|
File without changes
|
File without changes
|
File without changes
|