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.
Files changed (164) hide show
  1. rbx/__init__.py +0 -0
  2. rbx/annotations.py +127 -0
  3. rbx/autoenum.py +333 -0
  4. rbx/box/__init__.py +0 -0
  5. rbx/box/builder.py +77 -0
  6. rbx/box/cd.py +37 -0
  7. rbx/box/checkers.py +134 -0
  8. rbx/box/code.py +185 -0
  9. rbx/box/compile.py +56 -0
  10. rbx/box/conftest.py +42 -0
  11. rbx/box/contest/__init__.py +0 -0
  12. rbx/box/contest/build_contest_statements.py +347 -0
  13. rbx/box/contest/contest_package.py +76 -0
  14. rbx/box/contest/contest_utils.py +20 -0
  15. rbx/box/contest/main.py +179 -0
  16. rbx/box/contest/schema.py +155 -0
  17. rbx/box/contest/statements.py +82 -0
  18. rbx/box/creation.py +72 -0
  19. rbx/box/download.py +64 -0
  20. rbx/box/environment.py +345 -0
  21. rbx/box/extensions.py +26 -0
  22. rbx/box/generators.py +478 -0
  23. rbx/box/generators_test.py +63 -0
  24. rbx/box/main.py +449 -0
  25. rbx/box/package.py +316 -0
  26. rbx/box/packaging/boca/extension.py +27 -0
  27. rbx/box/packaging/boca/packager.py +245 -0
  28. rbx/box/packaging/contest_main.py +82 -0
  29. rbx/box/packaging/main.py +68 -0
  30. rbx/box/packaging/packager.py +117 -0
  31. rbx/box/packaging/polygon/packager.py +320 -0
  32. rbx/box/packaging/polygon/test.py +81 -0
  33. rbx/box/packaging/polygon/xml_schema.py +106 -0
  34. rbx/box/presets/__init__.py +503 -0
  35. rbx/box/presets/fetch.py +70 -0
  36. rbx/box/presets/lock_schema.py +20 -0
  37. rbx/box/presets/schema.py +59 -0
  38. rbx/box/schema.py +394 -0
  39. rbx/box/solutions.py +792 -0
  40. rbx/box/solutions_test.py +41 -0
  41. rbx/box/statements/__init__.py +0 -0
  42. rbx/box/statements/build_statements.py +359 -0
  43. rbx/box/statements/builders.py +375 -0
  44. rbx/box/statements/joiners.py +113 -0
  45. rbx/box/statements/latex.py +47 -0
  46. rbx/box/statements/latex_jinja.py +214 -0
  47. rbx/box/statements/schema.py +138 -0
  48. rbx/box/stresses.py +292 -0
  49. rbx/box/stressing/__init__.py +0 -0
  50. rbx/box/stressing/finder_parser.py +359 -0
  51. rbx/box/stressing/generator_parser.py +258 -0
  52. rbx/box/testcases.py +54 -0
  53. rbx/box/ui/__init__.py +0 -0
  54. rbx/box/ui/captured_log.py +372 -0
  55. rbx/box/ui/css/app.tcss +48 -0
  56. rbx/box/ui/main.py +38 -0
  57. rbx/box/ui/run.py +209 -0
  58. rbx/box/validators.py +245 -0
  59. rbx/box/validators_test.py +15 -0
  60. rbx/checker.py +128 -0
  61. rbx/clone.py +197 -0
  62. rbx/config.py +271 -0
  63. rbx/conftest.py +38 -0
  64. rbx/console.py +27 -0
  65. rbx/create.py +37 -0
  66. rbx/edit.py +24 -0
  67. rbx/grading/__init__.py +0 -0
  68. rbx/grading/caching.py +356 -0
  69. rbx/grading/conftest.py +33 -0
  70. rbx/grading/judge/__init__.py +0 -0
  71. rbx/grading/judge/cacher.py +503 -0
  72. rbx/grading/judge/digester.py +35 -0
  73. rbx/grading/judge/sandbox.py +748 -0
  74. rbx/grading/judge/sandboxes/__init__.py +0 -0
  75. rbx/grading/judge/sandboxes/isolate.py +683 -0
  76. rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
  77. rbx/grading/judge/sandboxes/timeit.py +217 -0
  78. rbx/grading/judge/storage.py +284 -0
  79. rbx/grading/judge/test.py +38 -0
  80. rbx/grading/judge/testiso.py +54 -0
  81. rbx/grading/steps.py +522 -0
  82. rbx/grading/steps_with_caching.py +59 -0
  83. rbx/grading/steps_with_caching_run_test.py +429 -0
  84. rbx/grading_utils.py +148 -0
  85. rbx/hydration.py +101 -0
  86. rbx/main.py +122 -0
  87. rbx/metadata.py +105 -0
  88. rbx/providers/__init__.py +43 -0
  89. rbx/providers/codeforces.py +73 -0
  90. rbx/providers/provider.py +26 -0
  91. rbx/resources/checkers/boilerplate.cpp +20 -0
  92. rbx/resources/default_config.json +48 -0
  93. rbx/resources/envs/default.rbx.yml +37 -0
  94. rbx/resources/envs/isolate.rbx.yml +37 -0
  95. rbx/resources/packagers/boca/checker.sh +43 -0
  96. rbx/resources/packagers/boca/compare +53 -0
  97. rbx/resources/packagers/boca/compile/c +172 -0
  98. rbx/resources/packagers/boca/compile/cc +173 -0
  99. rbx/resources/packagers/boca/compile/cpp +172 -0
  100. rbx/resources/packagers/boca/compile/java +194 -0
  101. rbx/resources/packagers/boca/compile/kt +155 -0
  102. rbx/resources/packagers/boca/compile/pas +172 -0
  103. rbx/resources/packagers/boca/compile/py2 +173 -0
  104. rbx/resources/packagers/boca/compile/py3 +173 -0
  105. rbx/resources/packagers/boca/run/c +128 -0
  106. rbx/resources/packagers/boca/run/cc +128 -0
  107. rbx/resources/packagers/boca/run/cpp +128 -0
  108. rbx/resources/packagers/boca/run/java +194 -0
  109. rbx/resources/packagers/boca/run/kt +159 -0
  110. rbx/resources/packagers/boca/run/py2 +166 -0
  111. rbx/resources/packagers/boca/run/py3 +166 -0
  112. rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
  113. rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
  114. rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
  115. rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
  116. rbx/resources/presets/default/preset.rbx.yml +12 -0
  117. rbx/resources/presets/default/problem/.gitignore +6 -0
  118. rbx/resources/presets/default/problem/gen.cpp +9 -0
  119. rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
  120. rbx/resources/presets/default/problem/random.py +3 -0
  121. rbx/resources/presets/default/problem/random.txt +2 -0
  122. rbx/resources/presets/default/problem/sols/main.cpp +9 -0
  123. rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
  124. rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
  125. rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
  126. rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  127. rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
  128. rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
  129. rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
  130. rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
  131. rbx/resources/presets/default/problem/validator.cpp +16 -0
  132. rbx/resources/presets/default/problem/wcmp.cpp +34 -0
  133. rbx/resources/templates/template.cpp +19 -0
  134. rbx/run.py +45 -0
  135. rbx/schema.py +64 -0
  136. rbx/submit.py +61 -0
  137. rbx/submitors/__init__.py +18 -0
  138. rbx/submitors/codeforces.py +120 -0
  139. rbx/submitors/submitor.py +25 -0
  140. rbx/test.py +347 -0
  141. rbx/testcase.py +70 -0
  142. rbx/testcase_rendering.py +79 -0
  143. rbx/testdata/box1/gen1.cpp +7 -0
  144. rbx/testdata/box1/gen2.cpp +9 -0
  145. rbx/testdata/box1/genScript.py +2 -0
  146. rbx/testdata/box1/hard-tle.sol.cpp +26 -0
  147. rbx/testdata/box1/ole.cpp +17 -0
  148. rbx/testdata/box1/problem.rbx.yml +39 -0
  149. rbx/testdata/box1/re.sol.cpp +23 -0
  150. rbx/testdata/box1/sol.cpp +22 -0
  151. rbx/testdata/box1/tests/1.in +1 -0
  152. rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
  153. rbx/testdata/box1/tle.sol.cpp +35 -0
  154. rbx/testdata/box1/validator.cpp +11 -0
  155. rbx/testdata/box1/wa.sol.cpp +22 -0
  156. rbx/testdata/caching/executable.py +1 -0
  157. rbx/testdata/compatible +0 -0
  158. rbx/testing_utils.py +65 -0
  159. rbx/utils.py +162 -0
  160. rbx_cp-0.5.0.dist-info/LICENSE +201 -0
  161. rbx_cp-0.5.0.dist-info/METADATA +89 -0
  162. rbx_cp-0.5.0.dist-info/RECORD +164 -0
  163. rbx_cp-0.5.0.dist-info/WHEEL +4 -0
  164. 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'