rbx.cp 0.5.0__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/__init__.py +0 -0
- rbx/annotations.py +127 -0
- rbx/autoenum.py +333 -0
- rbx/box/__init__.py +0 -0
- rbx/box/builder.py +77 -0
- rbx/box/cd.py +37 -0
- rbx/box/checkers.py +134 -0
- rbx/box/code.py +185 -0
- rbx/box/compile.py +56 -0
- rbx/box/conftest.py +42 -0
- rbx/box/contest/__init__.py +0 -0
- rbx/box/contest/build_contest_statements.py +347 -0
- rbx/box/contest/contest_package.py +76 -0
- rbx/box/contest/contest_utils.py +20 -0
- rbx/box/contest/main.py +179 -0
- rbx/box/contest/schema.py +155 -0
- rbx/box/contest/statements.py +82 -0
- rbx/box/creation.py +72 -0
- rbx/box/download.py +64 -0
- rbx/box/environment.py +345 -0
- rbx/box/extensions.py +26 -0
- rbx/box/generators.py +478 -0
- rbx/box/generators_test.py +63 -0
- rbx/box/main.py +449 -0
- rbx/box/package.py +316 -0
- rbx/box/packaging/boca/extension.py +27 -0
- rbx/box/packaging/boca/packager.py +245 -0
- rbx/box/packaging/contest_main.py +82 -0
- rbx/box/packaging/main.py +68 -0
- rbx/box/packaging/packager.py +117 -0
- rbx/box/packaging/polygon/packager.py +320 -0
- rbx/box/packaging/polygon/test.py +81 -0
- rbx/box/packaging/polygon/xml_schema.py +106 -0
- rbx/box/presets/__init__.py +503 -0
- rbx/box/presets/fetch.py +70 -0
- rbx/box/presets/lock_schema.py +20 -0
- rbx/box/presets/schema.py +59 -0
- rbx/box/schema.py +394 -0
- rbx/box/solutions.py +792 -0
- rbx/box/solutions_test.py +41 -0
- rbx/box/statements/__init__.py +0 -0
- rbx/box/statements/build_statements.py +359 -0
- rbx/box/statements/builders.py +375 -0
- rbx/box/statements/joiners.py +113 -0
- rbx/box/statements/latex.py +47 -0
- rbx/box/statements/latex_jinja.py +214 -0
- rbx/box/statements/schema.py +138 -0
- rbx/box/stresses.py +292 -0
- rbx/box/stressing/__init__.py +0 -0
- rbx/box/stressing/finder_parser.py +359 -0
- rbx/box/stressing/generator_parser.py +258 -0
- rbx/box/testcases.py +54 -0
- rbx/box/ui/__init__.py +0 -0
- rbx/box/ui/captured_log.py +372 -0
- rbx/box/ui/css/app.tcss +48 -0
- rbx/box/ui/main.py +38 -0
- rbx/box/ui/run.py +209 -0
- rbx/box/validators.py +245 -0
- rbx/box/validators_test.py +15 -0
- rbx/checker.py +128 -0
- rbx/clone.py +197 -0
- rbx/config.py +271 -0
- rbx/conftest.py +38 -0
- rbx/console.py +27 -0
- rbx/create.py +37 -0
- rbx/edit.py +24 -0
- rbx/grading/__init__.py +0 -0
- rbx/grading/caching.py +356 -0
- rbx/grading/conftest.py +33 -0
- rbx/grading/judge/__init__.py +0 -0
- rbx/grading/judge/cacher.py +503 -0
- rbx/grading/judge/digester.py +35 -0
- rbx/grading/judge/sandbox.py +748 -0
- rbx/grading/judge/sandboxes/__init__.py +0 -0
- rbx/grading/judge/sandboxes/isolate.py +683 -0
- rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
- rbx/grading/judge/sandboxes/timeit.py +217 -0
- rbx/grading/judge/storage.py +284 -0
- rbx/grading/judge/test.py +38 -0
- rbx/grading/judge/testiso.py +54 -0
- rbx/grading/steps.py +522 -0
- rbx/grading/steps_with_caching.py +59 -0
- rbx/grading/steps_with_caching_run_test.py +429 -0
- rbx/grading_utils.py +148 -0
- rbx/hydration.py +101 -0
- rbx/main.py +122 -0
- rbx/metadata.py +105 -0
- rbx/providers/__init__.py +43 -0
- rbx/providers/codeforces.py +73 -0
- rbx/providers/provider.py +26 -0
- rbx/resources/checkers/boilerplate.cpp +20 -0
- rbx/resources/default_config.json +48 -0
- rbx/resources/envs/default.rbx.yml +37 -0
- rbx/resources/envs/isolate.rbx.yml +37 -0
- rbx/resources/packagers/boca/checker.sh +43 -0
- rbx/resources/packagers/boca/compare +53 -0
- rbx/resources/packagers/boca/compile/c +172 -0
- rbx/resources/packagers/boca/compile/cc +173 -0
- rbx/resources/packagers/boca/compile/cpp +172 -0
- rbx/resources/packagers/boca/compile/java +194 -0
- rbx/resources/packagers/boca/compile/kt +155 -0
- rbx/resources/packagers/boca/compile/pas +172 -0
- rbx/resources/packagers/boca/compile/py2 +173 -0
- rbx/resources/packagers/boca/compile/py3 +173 -0
- rbx/resources/packagers/boca/run/c +128 -0
- rbx/resources/packagers/boca/run/cc +128 -0
- rbx/resources/packagers/boca/run/cpp +128 -0
- rbx/resources/packagers/boca/run/java +194 -0
- rbx/resources/packagers/boca/run/kt +159 -0
- rbx/resources/packagers/boca/run/py2 +166 -0
- rbx/resources/packagers/boca/run/py3 +166 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
- rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
- rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
- rbx/resources/presets/default/preset.rbx.yml +12 -0
- rbx/resources/presets/default/problem/.gitignore +6 -0
- rbx/resources/presets/default/problem/gen.cpp +9 -0
- rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
- rbx/resources/presets/default/problem/random.py +3 -0
- rbx/resources/presets/default/problem/random.txt +2 -0
- rbx/resources/presets/default/problem/sols/main.cpp +9 -0
- rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
- rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
- rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
- rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
- rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
- rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
- rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
- rbx/resources/presets/default/problem/validator.cpp +16 -0
- rbx/resources/presets/default/problem/wcmp.cpp +34 -0
- rbx/resources/templates/template.cpp +19 -0
- rbx/run.py +45 -0
- rbx/schema.py +64 -0
- rbx/submit.py +61 -0
- rbx/submitors/__init__.py +18 -0
- rbx/submitors/codeforces.py +120 -0
- rbx/submitors/submitor.py +25 -0
- rbx/test.py +347 -0
- rbx/testcase.py +70 -0
- rbx/testcase_rendering.py +79 -0
- rbx/testdata/box1/gen1.cpp +7 -0
- rbx/testdata/box1/gen2.cpp +9 -0
- rbx/testdata/box1/genScript.py +2 -0
- rbx/testdata/box1/hard-tle.sol.cpp +26 -0
- rbx/testdata/box1/ole.cpp +17 -0
- rbx/testdata/box1/problem.rbx.yml +39 -0
- rbx/testdata/box1/re.sol.cpp +23 -0
- rbx/testdata/box1/sol.cpp +22 -0
- rbx/testdata/box1/tests/1.in +1 -0
- rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
- rbx/testdata/box1/tle.sol.cpp +35 -0
- rbx/testdata/box1/validator.cpp +11 -0
- rbx/testdata/box1/wa.sol.cpp +22 -0
- rbx/testdata/caching/executable.py +1 -0
- rbx/testdata/compatible +0 -0
- rbx/testing_utils.py +65 -0
- rbx/utils.py +162 -0
- rbx_cp-0.5.0.dist-info/LICENSE +201 -0
- rbx_cp-0.5.0.dist-info/METADATA +89 -0
- rbx_cp-0.5.0.dist-info/RECORD +164 -0
- rbx_cp-0.5.0.dist-info/WHEEL +4 -0
- rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
rbx/box/generators.py
ADDED
@@ -0,0 +1,478 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shlex
|
3
|
+
import shutil
|
4
|
+
from pathlib import PosixPath
|
5
|
+
from typing import Callable, Dict, List, Optional, Set
|
6
|
+
|
7
|
+
import typer
|
8
|
+
|
9
|
+
from rbx import console
|
10
|
+
from rbx.box import checkers, package, testcases, validators
|
11
|
+
from rbx.box.code import compile_item, run_item
|
12
|
+
from rbx.box.environment import (
|
13
|
+
EnvironmentSandbox,
|
14
|
+
ExecutionConfig,
|
15
|
+
)
|
16
|
+
from rbx.box.schema import (
|
17
|
+
CodeItem,
|
18
|
+
Generator,
|
19
|
+
GeneratorCall,
|
20
|
+
Testcase,
|
21
|
+
TestcaseSubgroup,
|
22
|
+
)
|
23
|
+
from rbx.box.stressing import generator_parser
|
24
|
+
from rbx.box.testcases import find_built_testcases
|
25
|
+
from rbx.grading.judge.cacher import FileCacher
|
26
|
+
from rbx.grading.steps import (
|
27
|
+
DigestHolder,
|
28
|
+
DigestOrDest,
|
29
|
+
DigestOrSource,
|
30
|
+
)
|
31
|
+
from rbx.utils import StatusProgress
|
32
|
+
|
33
|
+
|
34
|
+
def _compile_generator(generator: CodeItem) -> str:
|
35
|
+
return compile_item(generator)
|
36
|
+
|
37
|
+
|
38
|
+
def _get_group_input(
|
39
|
+
group_path: pathlib.Path, subgroup_prefix: str, i: int
|
40
|
+
) -> pathlib.Path:
|
41
|
+
return group_path / f'{subgroup_prefix}{i:03d}.in'
|
42
|
+
|
43
|
+
|
44
|
+
def _get_group_output(
|
45
|
+
group_path: pathlib.Path, subgroup_prefix: str, i: int
|
46
|
+
) -> pathlib.Path:
|
47
|
+
return group_path / f'{subgroup_prefix}{i:03d}.out'
|
48
|
+
|
49
|
+
|
50
|
+
def _copy_testcase_over(
|
51
|
+
testcase: Testcase, group_path: pathlib.Path, subgroup_prefix: str, i: int
|
52
|
+
):
|
53
|
+
shutil.copy(
|
54
|
+
str(testcase.inputPath),
|
55
|
+
_get_group_input(group_path, subgroup_prefix, i),
|
56
|
+
)
|
57
|
+
if testcase.outputPath is not None and testcase.outputPath.is_file():
|
58
|
+
shutil.copy(
|
59
|
+
str(testcase.outputPath),
|
60
|
+
_get_group_output(group_path, subgroup_prefix, i),
|
61
|
+
)
|
62
|
+
|
63
|
+
|
64
|
+
def _run_generator(
|
65
|
+
generator: Generator,
|
66
|
+
args: Optional[str],
|
67
|
+
compiled_digest: str,
|
68
|
+
group_path: pathlib.Path,
|
69
|
+
subgroup_prefix: str,
|
70
|
+
i: int = 0,
|
71
|
+
):
|
72
|
+
generation_stderr = DigestHolder()
|
73
|
+
run_log = run_item(
|
74
|
+
generator,
|
75
|
+
DigestOrSource.create(compiled_digest),
|
76
|
+
stdout=DigestOrDest.create(_get_group_input(group_path, subgroup_prefix, i)),
|
77
|
+
stderr=DigestOrDest.create(generation_stderr),
|
78
|
+
extra_args=args or None,
|
79
|
+
)
|
80
|
+
|
81
|
+
if not run_log or run_log.exitcode != 0:
|
82
|
+
console.console.print(
|
83
|
+
f'[error]Failed generating test {i} from group path {group_path}[/error]',
|
84
|
+
)
|
85
|
+
if generation_stderr.value is not None:
|
86
|
+
console.console.print('[error]Stderr:[/error]')
|
87
|
+
console.console.print(
|
88
|
+
package.get_digest_as_string(generation_stderr.value) or ''
|
89
|
+
)
|
90
|
+
raise typer.Exit(1)
|
91
|
+
|
92
|
+
|
93
|
+
def get_all_built_testcases() -> Dict[str, List[Testcase]]:
|
94
|
+
pkg = package.find_problem_package_or_die()
|
95
|
+
res = {group.name: find_built_testcases(group) for group in pkg.testcases}
|
96
|
+
return res
|
97
|
+
|
98
|
+
|
99
|
+
def get_call_from_string(call_str: str) -> GeneratorCall:
|
100
|
+
name, args = call_str.split(None, 1)
|
101
|
+
return GeneratorCall(name=name, args=args)
|
102
|
+
|
103
|
+
|
104
|
+
def generate_output_for_testcase(
|
105
|
+
main_solution_digest: str,
|
106
|
+
testcase: Testcase,
|
107
|
+
stderr_path: Optional[pathlib.Path] = None,
|
108
|
+
):
|
109
|
+
assert testcase.outputPath is not None
|
110
|
+
pkg = package.find_problem_package_or_die()
|
111
|
+
main_solution = package.get_main_solution()
|
112
|
+
if main_solution is None:
|
113
|
+
return
|
114
|
+
|
115
|
+
timelimit = pkg.timelimit_for_language(main_solution.language)
|
116
|
+
sandbox = EnvironmentSandbox()
|
117
|
+
sandbox.timeLimit = timelimit * 2
|
118
|
+
sandbox.wallTimeLimit = timelimit * 2
|
119
|
+
sandbox.memoryLimit = pkg.memorylimit_for_language(main_solution.language)
|
120
|
+
sandbox.fileSizeLimit = pkg.outputLimit
|
121
|
+
extra_config = ExecutionConfig(sandbox=sandbox)
|
122
|
+
|
123
|
+
try:
|
124
|
+
run_log = run_item(
|
125
|
+
main_solution,
|
126
|
+
DigestOrSource.create(main_solution_digest),
|
127
|
+
stdin=DigestOrSource.create(testcase.inputPath),
|
128
|
+
stdout=DigestOrDest.create(testcase.outputPath),
|
129
|
+
stderr=DigestOrDest.create(stderr_path)
|
130
|
+
if stderr_path is not None
|
131
|
+
else None,
|
132
|
+
extra_config=extra_config,
|
133
|
+
)
|
134
|
+
except:
|
135
|
+
console.console.print(
|
136
|
+
'[error]Failed running main solution to generate testcase.[/error]'
|
137
|
+
)
|
138
|
+
raise
|
139
|
+
|
140
|
+
if run_log is None or run_log.exitcode != 0:
|
141
|
+
console.console.print(
|
142
|
+
f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
|
143
|
+
)
|
144
|
+
if run_log is not None:
|
145
|
+
console.console.print(
|
146
|
+
f'[error]Main solution exited with code [item]{-run_log.exitcode}[/item][/error]',
|
147
|
+
)
|
148
|
+
checker_result = checkers.check_with_no_output(run_log)
|
149
|
+
console.console.print(
|
150
|
+
f'[warning]Time: [item]{run_log.time:.2f}s[/item][/warning]',
|
151
|
+
)
|
152
|
+
console.console.print(
|
153
|
+
f'[warning]Verdict: [item]{checker_result.outcome.value}[/item][/warning]',
|
154
|
+
)
|
155
|
+
console.console.print(
|
156
|
+
f'[warning]Message: [info]{checker_result.message}[/info][/warning]',
|
157
|
+
)
|
158
|
+
console.console.print(
|
159
|
+
f'Input written at [item]{testcase.inputPath}[/item].'
|
160
|
+
)
|
161
|
+
console.console.print(
|
162
|
+
f'Output written at [item]{testcase.outputPath}[/item].'
|
163
|
+
)
|
164
|
+
console.console.print(f'Stderr written at [item]{stderr_path}[/item].')
|
165
|
+
raise typer.Exit(1)
|
166
|
+
|
167
|
+
|
168
|
+
def generate_outputs_for_testcases(
|
169
|
+
progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
|
170
|
+
):
|
171
|
+
def step():
|
172
|
+
if progress is not None:
|
173
|
+
progress.step()
|
174
|
+
|
175
|
+
pkg = package.find_problem_package_or_die()
|
176
|
+
|
177
|
+
built_testcases = get_all_built_testcases()
|
178
|
+
main_solution = package.get_main_solution()
|
179
|
+
solution_digest: Optional[str] = None
|
180
|
+
|
181
|
+
if main_solution is not None:
|
182
|
+
if progress:
|
183
|
+
progress.update('Compiling main solution...')
|
184
|
+
try:
|
185
|
+
solution_digest = compile_item(main_solution)
|
186
|
+
except:
|
187
|
+
console.console.print('[error]Failed compiling main solution.[/error]')
|
188
|
+
raise
|
189
|
+
|
190
|
+
gen_runs_dir = package.get_problem_runs_dir() / '.gen'
|
191
|
+
shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
|
192
|
+
gen_runs_dir.mkdir(parents=True, exist_ok=True)
|
193
|
+
|
194
|
+
for group in pkg.testcases:
|
195
|
+
if groups is not None and group.name not in groups:
|
196
|
+
continue
|
197
|
+
group_testcases = built_testcases[group.name]
|
198
|
+
|
199
|
+
for testcase in group_testcases:
|
200
|
+
stderr_path = gen_runs_dir / 'main.stderr'
|
201
|
+
|
202
|
+
assert testcase.outputPath is not None
|
203
|
+
if main_solution is None or solution_digest is None:
|
204
|
+
console.console.print(
|
205
|
+
'[error]No main solution found to generate outputs for testcases.[/error]',
|
206
|
+
)
|
207
|
+
raise typer.Exit(1)
|
208
|
+
|
209
|
+
generate_output_for_testcase(solution_digest, testcase, stderr_path)
|
210
|
+
step()
|
211
|
+
|
212
|
+
|
213
|
+
def _run_generator_script(testcase: TestcaseSubgroup, cacher: FileCacher) -> str:
|
214
|
+
assert testcase.generatorScript is not None
|
215
|
+
script_digest = DigestHolder()
|
216
|
+
if testcase.generatorScript.path.suffix == '.txt':
|
217
|
+
script_digest.value = cacher.put_file_from_path(testcase.generatorScript.path)
|
218
|
+
else:
|
219
|
+
try:
|
220
|
+
compiled_digest = compile_item(testcase.generatorScript)
|
221
|
+
except:
|
222
|
+
console.console.print(
|
223
|
+
f'[error]Failed compiling generator script for group [item]{testcase.name}[/item].[/error]'
|
224
|
+
)
|
225
|
+
raise
|
226
|
+
|
227
|
+
run_stderr = DigestHolder()
|
228
|
+
run_log = run_item(
|
229
|
+
testcase.generatorScript,
|
230
|
+
DigestOrSource.create(compiled_digest),
|
231
|
+
stdout=DigestOrDest.create(script_digest),
|
232
|
+
stderr=DigestOrDest.create(run_stderr),
|
233
|
+
)
|
234
|
+
|
235
|
+
if run_log is None or run_log.exitcode != 0:
|
236
|
+
console.console.print(
|
237
|
+
f'Could not run generator script for group {testcase.name}'
|
238
|
+
)
|
239
|
+
if run_log is not None:
|
240
|
+
console.console.print(
|
241
|
+
f'[error]Script exited with code [item]{-run_log.exitcode}[/item][/error]',
|
242
|
+
)
|
243
|
+
if run_stderr.value is not None:
|
244
|
+
console.console.print('[error]Stderr:[/error]')
|
245
|
+
console.console.print(
|
246
|
+
package.get_digest_as_string(run_stderr.value) or ''
|
247
|
+
)
|
248
|
+
raise typer.Exit(1)
|
249
|
+
|
250
|
+
assert script_digest.value
|
251
|
+
script = cacher.get_file_content(script_digest.value).decode()
|
252
|
+
return script
|
253
|
+
|
254
|
+
|
255
|
+
def _extract_script_lines(script: str):
|
256
|
+
lines = script.splitlines()
|
257
|
+
for line in lines:
|
258
|
+
line = line.strip()
|
259
|
+
if not line:
|
260
|
+
continue
|
261
|
+
if line.startswith('#'):
|
262
|
+
continue
|
263
|
+
yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:])
|
264
|
+
|
265
|
+
|
266
|
+
def _get_necessary_generators(groups: Set[str], cacher: FileCacher) -> Set[str]:
|
267
|
+
pkg = package.find_problem_package_or_die()
|
268
|
+
existing_generators = set(generator.name for generator in pkg.generators)
|
269
|
+
|
270
|
+
necessary_generators = set()
|
271
|
+
for group in pkg.testcases:
|
272
|
+
if groups is not None and group.name not in groups:
|
273
|
+
continue
|
274
|
+
|
275
|
+
for generator_call in group.generators:
|
276
|
+
necessary_generators.add(generator_call.name)
|
277
|
+
|
278
|
+
if group.generatorScript is not None:
|
279
|
+
script = _run_generator_script(group, cacher)
|
280
|
+
for generator_name, _ in _extract_script_lines(script):
|
281
|
+
necessary_generators.add(generator_name)
|
282
|
+
|
283
|
+
return existing_generators.intersection(necessary_generators)
|
284
|
+
|
285
|
+
|
286
|
+
def compile_generators(
|
287
|
+
progress: Optional[StatusProgress] = None,
|
288
|
+
tracked_generators: Optional[Set[str]] = None,
|
289
|
+
) -> Dict[str, str]:
|
290
|
+
def update_status(text: str):
|
291
|
+
if progress is not None:
|
292
|
+
progress.update(text)
|
293
|
+
|
294
|
+
pkg = package.find_problem_package_or_die()
|
295
|
+
|
296
|
+
generator_to_compiled_digest = {}
|
297
|
+
|
298
|
+
for generator in pkg.generators:
|
299
|
+
if tracked_generators is not None and generator.name not in tracked_generators:
|
300
|
+
continue
|
301
|
+
update_status(f'Compiling generator [item]{generator.name}[/item]')
|
302
|
+
try:
|
303
|
+
generator_to_compiled_digest[generator.name] = _compile_generator(generator)
|
304
|
+
except:
|
305
|
+
console.console.print(
|
306
|
+
f'[error]Failed compiling generator [item]{generator.name}[/item].[/error]'
|
307
|
+
)
|
308
|
+
raise
|
309
|
+
|
310
|
+
return generator_to_compiled_digest
|
311
|
+
|
312
|
+
|
313
|
+
def generate_standalone(
|
314
|
+
call: GeneratorCall,
|
315
|
+
output: pathlib.Path,
|
316
|
+
validate: bool = True,
|
317
|
+
generator_digest: Optional[str] = None,
|
318
|
+
validator_digest: Optional[str] = None,
|
319
|
+
) -> GeneratorCall:
|
320
|
+
# Generator args parser
|
321
|
+
parsed_args = generator_parser.parse(call.args or '')
|
322
|
+
vars = package.find_problem_package_or_die().expanded_vars
|
323
|
+
generator_for_args = generator_parser.Generator(vars)
|
324
|
+
expanded_args_str = generator_for_args.generate(parsed_args)
|
325
|
+
|
326
|
+
generation_stderr = DigestHolder()
|
327
|
+
|
328
|
+
# Get generator item
|
329
|
+
generator = package.get_generator(call.name)
|
330
|
+
if generator_digest is None:
|
331
|
+
generator_digest = compile_item(generator)
|
332
|
+
|
333
|
+
generation_log = run_item(
|
334
|
+
generator,
|
335
|
+
DigestOrSource.create(generator_digest),
|
336
|
+
stdout=DigestOrDest.create(output),
|
337
|
+
stderr=DigestOrDest.create(generation_stderr),
|
338
|
+
extra_args=expanded_args_str or None,
|
339
|
+
)
|
340
|
+
if not generation_log or generation_log.exitcode != 0:
|
341
|
+
console.console.print(
|
342
|
+
f'[error]Failed generating test using generator call [info]{call.name} {expanded_args_str}[/info].[/error]',
|
343
|
+
)
|
344
|
+
if generation_stderr.value is not None:
|
345
|
+
console.console.print('[error]Stderr:[/error]')
|
346
|
+
console.console.print(
|
347
|
+
package.get_digest_as_string(generation_stderr.value) or ''
|
348
|
+
)
|
349
|
+
|
350
|
+
raise typer.Exit(1)
|
351
|
+
|
352
|
+
validator = package.get_validator()
|
353
|
+
# Run validator, if it is available.
|
354
|
+
if validator is not None and validate:
|
355
|
+
if validator_digest is None:
|
356
|
+
validator_digest = compile_item(validator)
|
357
|
+
ok, message, *_ = validators.validate_test(output, validator, validator_digest)
|
358
|
+
if not ok:
|
359
|
+
console.console.print(
|
360
|
+
f'[error]Failed validating testcase generated by call [info]{call.name} {expanded_args_str}[/info].[/error]'
|
361
|
+
)
|
362
|
+
console.console.print(f'[error]Message:[/error] {message}')
|
363
|
+
console.console.print(f'Testcase written at [item]{output}[/item]')
|
364
|
+
raise typer.Exit(1)
|
365
|
+
|
366
|
+
return call.model_copy(update={'args': expanded_args_str})
|
367
|
+
|
368
|
+
|
369
|
+
def _generate_testcases_for_subgroup(
|
370
|
+
subgroup: TestcaseSubgroup,
|
371
|
+
group_path: pathlib.Path,
|
372
|
+
subgroup_prefix: str,
|
373
|
+
compiled_generators: Dict[str, str],
|
374
|
+
step: Callable,
|
375
|
+
):
|
376
|
+
cacher = package.get_file_cacher()
|
377
|
+
|
378
|
+
group_path.mkdir(parents=True, exist_ok=True)
|
379
|
+
|
380
|
+
i = 0
|
381
|
+
# Individual testcases.
|
382
|
+
for tc in subgroup.testcases or []:
|
383
|
+
_copy_testcase_over(tc, group_path, subgroup_prefix, i)
|
384
|
+
i += 1
|
385
|
+
step()
|
386
|
+
|
387
|
+
# Glob testcases.
|
388
|
+
if subgroup.testcaseGlob:
|
389
|
+
matched_inputs = sorted(PosixPath().glob(subgroup.testcaseGlob))
|
390
|
+
|
391
|
+
for input_path in matched_inputs:
|
392
|
+
if not input_path.is_file() or input_path.suffix != '.in':
|
393
|
+
continue
|
394
|
+
output_path = input_path.parent / f'{input_path.stem}.out'
|
395
|
+
tc = Testcase(inputPath=input_path, outputPath=output_path)
|
396
|
+
_copy_testcase_over(tc, group_path, subgroup_prefix, i)
|
397
|
+
i += 1
|
398
|
+
step()
|
399
|
+
|
400
|
+
# Run single generators.
|
401
|
+
for generator_call in subgroup.generators:
|
402
|
+
generator = package.get_generator(generator_call.name)
|
403
|
+
if generator.name not in compiled_generators:
|
404
|
+
console.console.print(f'Generator {generator.name} not compiled')
|
405
|
+
raise typer.Exit(1)
|
406
|
+
|
407
|
+
_run_generator(
|
408
|
+
generator,
|
409
|
+
generator_call.args,
|
410
|
+
compiled_generators[generator.name],
|
411
|
+
group_path,
|
412
|
+
subgroup_prefix,
|
413
|
+
i,
|
414
|
+
)
|
415
|
+
i += 1
|
416
|
+
step()
|
417
|
+
|
418
|
+
# Run generator script.
|
419
|
+
if subgroup.generatorScript is not None:
|
420
|
+
script = _run_generator_script(subgroup, cacher)
|
421
|
+
|
422
|
+
# Run each line from generator script.
|
423
|
+
for generator_name, args in _extract_script_lines(script):
|
424
|
+
generator = package.get_generator(generator_name)
|
425
|
+
if generator.name not in compiled_generators:
|
426
|
+
console.console.print(f'Generator {generator.name} not compiled')
|
427
|
+
raise typer.Exit(1)
|
428
|
+
|
429
|
+
_run_generator(
|
430
|
+
generator,
|
431
|
+
args,
|
432
|
+
compiled_generators[generator.name],
|
433
|
+
group_path,
|
434
|
+
subgroup_prefix,
|
435
|
+
i,
|
436
|
+
)
|
437
|
+
i += 1
|
438
|
+
step()
|
439
|
+
|
440
|
+
|
441
|
+
def generate_testcases(
|
442
|
+
progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
|
443
|
+
):
|
444
|
+
def step():
|
445
|
+
if progress is not None:
|
446
|
+
progress.step()
|
447
|
+
|
448
|
+
pkg = package.find_problem_package_or_die()
|
449
|
+
cacher = package.get_file_cacher()
|
450
|
+
|
451
|
+
compiled_generators = compile_generators(
|
452
|
+
progress=progress,
|
453
|
+
tracked_generators=_get_necessary_generators(groups, cacher)
|
454
|
+
if groups is not None
|
455
|
+
else None,
|
456
|
+
)
|
457
|
+
|
458
|
+
testcases.clear_built_testcases()
|
459
|
+
|
460
|
+
for testcase in pkg.testcases:
|
461
|
+
if groups is not None and testcase.name not in groups:
|
462
|
+
continue
|
463
|
+
group_path = package.get_build_testgroup_path(testcase.name)
|
464
|
+
|
465
|
+
if not testcase.subgroups:
|
466
|
+
# Testcase group is itself a test subgroup.
|
467
|
+
_generate_testcases_for_subgroup(
|
468
|
+
testcase, group_path, '', compiled_generators, step
|
469
|
+
)
|
470
|
+
continue
|
471
|
+
|
472
|
+
renamed_testcase = testcase.model_copy(update={'name': 'main'})
|
473
|
+
subgroups = [renamed_testcase] + testcase.subgroups
|
474
|
+
for i, subgroup in enumerate(subgroups):
|
475
|
+
# Test subgroups were specified, use them.
|
476
|
+
_generate_testcases_for_subgroup(
|
477
|
+
subgroup, group_path, f'{i}-{subgroup.name}-', compiled_generators, step
|
478
|
+
)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import pathlib
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
from rbx.box import package
|
6
|
+
from rbx.box.generators import (
|
7
|
+
generate_outputs_for_testcases,
|
8
|
+
generate_testcases,
|
9
|
+
)
|
10
|
+
from rbx.testing_utils import print_directory_tree
|
11
|
+
|
12
|
+
|
13
|
+
@pytest.mark.test_pkg('box1')
|
14
|
+
def test_generator_works(pkg_from_testdata: pathlib.Path):
|
15
|
+
generate_testcases()
|
16
|
+
generate_outputs_for_testcases()
|
17
|
+
|
18
|
+
# Debug when fail.
|
19
|
+
print_directory_tree(pkg_from_testdata)
|
20
|
+
|
21
|
+
assert (
|
22
|
+
package.get_build_testgroup_path('gen1') / '0-main-000.in'
|
23
|
+
).read_text() == '777\n'
|
24
|
+
assert (
|
25
|
+
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
26
|
+
).read_text() == '123\n'
|
27
|
+
assert (
|
28
|
+
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
29
|
+
).read_text() == '424242\n'
|
30
|
+
assert (
|
31
|
+
package.get_build_testgroup_path('gen1') / '2-genScript-000.in'
|
32
|
+
).read_text() == '25\n'
|
33
|
+
|
34
|
+
|
35
|
+
@pytest.mark.test_pkg('box1')
|
36
|
+
def test_generator_cache_works(
|
37
|
+
pkg_from_testdata: pathlib.Path,
|
38
|
+
):
|
39
|
+
# Run the first time.
|
40
|
+
generate_testcases()
|
41
|
+
assert (
|
42
|
+
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
43
|
+
).read_text() == '123\n'
|
44
|
+
assert (
|
45
|
+
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
46
|
+
).read_text() == '424242\n'
|
47
|
+
|
48
|
+
# Change the generator `gen1`, but keep `gen2` as is.
|
49
|
+
gen_path = pkg_from_testdata / 'gen1.cpp'
|
50
|
+
gen_path.write_text(gen_path.read_text().replace('123', '4567'))
|
51
|
+
|
52
|
+
# Run the second time.
|
53
|
+
generate_testcases()
|
54
|
+
|
55
|
+
# Debug when fail.
|
56
|
+
print_directory_tree(pkg_from_testdata)
|
57
|
+
|
58
|
+
assert (
|
59
|
+
package.get_build_testgroup_path('gen1') / '1-gen-000.in'
|
60
|
+
).read_text() == '4567\n'
|
61
|
+
assert (
|
62
|
+
package.get_build_testgroup_path('gen1') / '1-gen-001.in'
|
63
|
+
).read_text() == '424242\n'
|