rbx.cp 0.5.28__tar.gz → 0.5.29__tar.gz
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_cp-0.5.28 → rbx_cp-0.5.29}/PKG-INFO +1 -4
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/README.md +0 -3
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/pyproject.toml +1 -1
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/builder.py +5 -5
- rbx_cp-0.5.29/rbx/box/generators.py +610 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/main.py +10 -7
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/schema.py +15 -6
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/solutions.py +150 -50
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/stresses.py +11 -4
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/testcases.py +17 -1
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/utils.py +8 -2
- rbx_cp-0.5.28/rbx/box/generators.py +0 -483
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/LICENSE +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/annotations.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/autoenum.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/cd.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/checkers.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/code.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/compile.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/conftest.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/build_contest_statements.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/contest_package.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/contest_utils.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/main.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/contest/statements.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/creation.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/deferred.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/download.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/environment.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/extensions.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/generators_test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/package.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/boca/extension.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/boca/packager.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/contest_main.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/main.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/packager.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/polygon/packager.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/polygon/test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/packaging/polygon/xml_schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/presets/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/presets/fetch.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/presets/lock_schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/presets/schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/retries.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/sanitizers/warning_stack.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/setter_config.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/solutions_test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/build_statements.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/builders.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/joiners.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/latex.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/latex_jinja.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/statements/schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/stressing/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/stressing/finder_parser.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/stressing/generator_parser.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/ui/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/ui/captured_log.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/ui/css/app.tcss +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/ui/main.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/ui/run.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/validators.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/box/validators_test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/checker.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/clone.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/config.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/conftest.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/console.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/create.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/edit.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/caching.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/conftest.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/cacher.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/digester.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/sandbox.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/sandboxes/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/sandboxes/isolate.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/sandboxes/timeit.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/storage.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/judge/testiso.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/steps.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/steps_with_caching.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading/steps_with_caching_run_test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/grading_utils.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/hydration.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/main.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/metadata.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/providers/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/providers/codeforces.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/providers/provider.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/checkers/boilerplate.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/default_config.json +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/default_setter_config.mac.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/default_setter_config.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/envs/default.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/envs/isolate.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/checker.sh +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compare +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/c +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/cc +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/java +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/kt +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/pas +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/py2 +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/compile/py3 +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/c +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/cc +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/java +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/kt +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/py2 +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/packagers/boca/run/py3 +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/preset.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/.gitignore +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/gen.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/random.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/random.txt +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/validator.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/resources/templates/template.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/run.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/schema.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/submit.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/submitors/__init__.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/submitors/codeforces.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/submitors/submitor.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/test.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testcase.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testcase_rendering.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/gen1.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/gen2.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/genScript.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/ole.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/problem.rbx.yml +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/re.sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/tests/1.in +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/tle.sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/validator.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/box1/wa.sol.cpp +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/caching/executable.py +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testdata/compatible +0 -0
- {rbx_cp-0.5.28 → rbx_cp-0.5.29}/rbx/testing_utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: rbx.cp
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.29
|
4
4
|
Summary:
|
5
5
|
Author: Roberto Sales
|
6
6
|
Requires-Python: >=3.9,<4.0
|
@@ -32,9 +32,6 @@ Requires-Dist: textual (>=0.79.1,<0.80.0)
|
|
32
32
|
Requires-Dist: typer (>=0.15.1,<0.16.0)
|
33
33
|
Description-Content-Type: text/markdown
|
34
34
|
|
35
|
-
<p align="center">
|
36
|
-
<img src="docs/rbx_transparent.png" width="240px">
|
37
|
-
</p>
|
38
35
|
<p align="center">
|
39
36
|
<em>The go-to CLI tool for competitive programmers and setters.</em>
|
40
37
|
</p>
|
@@ -49,11 +49,11 @@ def build(
|
|
49
49
|
infos = validate_testcases(s, groups=groups)
|
50
50
|
print_validation_report(infos)
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
if has_validation_errors(infos):
|
53
|
+
console.console.print(
|
54
|
+
'[error]Validation failed, check the report above.[/error]'
|
55
|
+
)
|
56
|
+
return False
|
57
57
|
|
58
58
|
with utils.StatusProgress(
|
59
59
|
'Building outputs for testcases...',
|
@@ -0,0 +1,610 @@
|
|
1
|
+
import abc
|
2
|
+
import pathlib
|
3
|
+
import shlex
|
4
|
+
import shutil
|
5
|
+
from pathlib import PosixPath
|
6
|
+
from typing import Dict, List, Optional, Set
|
7
|
+
|
8
|
+
import typer
|
9
|
+
from pydantic import BaseModel
|
10
|
+
|
11
|
+
from rbx import console
|
12
|
+
from rbx.box import checkers, package, testcases, validators
|
13
|
+
from rbx.box.code import SanitizationLevel, compile_item, run_item
|
14
|
+
from rbx.box.environment import (
|
15
|
+
EnvironmentSandbox,
|
16
|
+
ExecutionConfig,
|
17
|
+
)
|
18
|
+
from rbx.box.schema import (
|
19
|
+
CodeItem,
|
20
|
+
GeneratorCall,
|
21
|
+
Testcase,
|
22
|
+
TestcaseSubgroup,
|
23
|
+
)
|
24
|
+
from rbx.box.stressing import generator_parser
|
25
|
+
from rbx.box.testcases import TestcaseEntry, find_built_testcases
|
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, sanitized=SanitizationLevel.PREFER)
|
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 _fill_output_for_testcase(testcase: Testcase) -> Testcase:
|
51
|
+
res = testcase.model_copy()
|
52
|
+
if res.outputPath is not None:
|
53
|
+
return res
|
54
|
+
output_path = res.inputPath.with_suffix('.out')
|
55
|
+
if output_path.is_file():
|
56
|
+
res.outputPath = output_path
|
57
|
+
return res
|
58
|
+
|
59
|
+
|
60
|
+
def _copy_testcase_over(
|
61
|
+
testcase: Testcase,
|
62
|
+
dest: Testcase,
|
63
|
+
):
|
64
|
+
testcase = _fill_output_for_testcase(testcase)
|
65
|
+
dest.inputPath.parent.mkdir(parents=True, exist_ok=True)
|
66
|
+
shutil.copy(
|
67
|
+
str(testcase.inputPath),
|
68
|
+
str(dest.inputPath),
|
69
|
+
)
|
70
|
+
if (
|
71
|
+
testcase.outputPath is not None
|
72
|
+
and testcase.outputPath.is_file()
|
73
|
+
and dest.outputPath is not None
|
74
|
+
):
|
75
|
+
dest.outputPath.parent.mkdir(parents=True, exist_ok=True)
|
76
|
+
shutil.copy(
|
77
|
+
str(testcase.outputPath),
|
78
|
+
str(dest.outputPath),
|
79
|
+
)
|
80
|
+
|
81
|
+
|
82
|
+
def get_all_built_testcases() -> Dict[str, List[Testcase]]:
|
83
|
+
pkg = package.find_problem_package_or_die()
|
84
|
+
res = {group.name: find_built_testcases(group) for group in pkg.testcases}
|
85
|
+
return res
|
86
|
+
|
87
|
+
|
88
|
+
def get_call_from_string(call_str: str) -> GeneratorCall:
|
89
|
+
name, args = call_str.split(None, 1)
|
90
|
+
return GeneratorCall(name=name, args=args)
|
91
|
+
|
92
|
+
|
93
|
+
def _run_generator_script(testcase: TestcaseSubgroup) -> str:
|
94
|
+
assert testcase.generatorScript is not None
|
95
|
+
|
96
|
+
cacher = package.get_file_cacher()
|
97
|
+
|
98
|
+
if not testcase.generatorScript.path.is_file():
|
99
|
+
console.console.print(
|
100
|
+
f'[error]Generator script not found: [item]{testcase.generatorScript.path}[/item][/error]'
|
101
|
+
)
|
102
|
+
raise typer.Exit(1)
|
103
|
+
|
104
|
+
script_digest = DigestHolder()
|
105
|
+
if testcase.generatorScript.path.suffix == '.txt':
|
106
|
+
script_digest.value = cacher.put_file_from_path(testcase.generatorScript.path)
|
107
|
+
else:
|
108
|
+
try:
|
109
|
+
compiled_digest = compile_item(testcase.generatorScript)
|
110
|
+
except:
|
111
|
+
console.console.print(
|
112
|
+
f'[error]Failed compiling generator script for group [item]{testcase.name}[/item].[/error]'
|
113
|
+
)
|
114
|
+
raise
|
115
|
+
|
116
|
+
run_stderr = DigestHolder()
|
117
|
+
run_log = run_item(
|
118
|
+
testcase.generatorScript,
|
119
|
+
DigestOrSource.create(compiled_digest),
|
120
|
+
stdout=DigestOrDest.create(script_digest),
|
121
|
+
stderr=DigestOrDest.create(run_stderr),
|
122
|
+
)
|
123
|
+
|
124
|
+
if run_log is None or run_log.exitcode != 0:
|
125
|
+
console.console.print(
|
126
|
+
f'Could not run generator script for group {testcase.name}'
|
127
|
+
)
|
128
|
+
if run_log is not None:
|
129
|
+
console.console.print(
|
130
|
+
f'[error]Summary:[/error] {run_log.get_summary()}'
|
131
|
+
)
|
132
|
+
if run_stderr.value is not None:
|
133
|
+
console.console.print('[error]Stderr:[/error]')
|
134
|
+
console.console.print(
|
135
|
+
package.get_digest_as_string(run_stderr.value) or ''
|
136
|
+
)
|
137
|
+
raise typer.Exit(1)
|
138
|
+
|
139
|
+
assert script_digest.value
|
140
|
+
script = cacher.get_file_content(script_digest.value).decode()
|
141
|
+
return script
|
142
|
+
|
143
|
+
|
144
|
+
def _extract_script_lines(script: str):
|
145
|
+
lines = script.splitlines()
|
146
|
+
for line in lines:
|
147
|
+
line = line.strip()
|
148
|
+
if not line:
|
149
|
+
continue
|
150
|
+
if line.startswith('#'):
|
151
|
+
continue
|
152
|
+
yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:])
|
153
|
+
|
154
|
+
|
155
|
+
class GenerationMetadata(BaseModel):
|
156
|
+
copied_to: Testcase
|
157
|
+
|
158
|
+
copied_from: Optional[Testcase] = None
|
159
|
+
generator_call: Optional[GeneratorCall] = None
|
160
|
+
|
161
|
+
|
162
|
+
class GenerationTestcaseEntry(BaseModel):
|
163
|
+
group_entry: TestcaseEntry
|
164
|
+
subgroup_entry: TestcaseEntry
|
165
|
+
|
166
|
+
metadata: GenerationMetadata
|
167
|
+
|
168
|
+
|
169
|
+
class TestcaseVisitor(abc.ABC):
|
170
|
+
@abc.abstractmethod
|
171
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
172
|
+
pass
|
173
|
+
|
174
|
+
def should_visit_group(self, group_name: str) -> bool:
|
175
|
+
return True
|
176
|
+
|
177
|
+
def should_visit_subgroup(self, subgroup_path: str) -> bool:
|
178
|
+
return True
|
179
|
+
|
180
|
+
def should_visit_generator_scripts(
|
181
|
+
self, group_name: str, subgroup_path: str
|
182
|
+
) -> bool:
|
183
|
+
return True
|
184
|
+
|
185
|
+
|
186
|
+
class TestcaseGroupVisitor(TestcaseVisitor):
|
187
|
+
def __init__(self, groups: Optional[Set[str]] = None):
|
188
|
+
self.groups = groups
|
189
|
+
|
190
|
+
def should_visit_group(self, group_name: str) -> bool:
|
191
|
+
return self.groups is None or group_name in self.groups
|
192
|
+
|
193
|
+
|
194
|
+
def run_testcase_visitor(visitor: TestcaseVisitor):
|
195
|
+
pkg = package.find_problem_package_or_die()
|
196
|
+
|
197
|
+
def _explore_subgroup(
|
198
|
+
subgroup: TestcaseSubgroup, subgroup_index: int, prefix: List[str]
|
199
|
+
):
|
200
|
+
assert prefix and len(prefix) >= 1 and len(prefix) <= 2
|
201
|
+
group_path = prefix[0]
|
202
|
+
subgroup_path = '/'.join(prefix)
|
203
|
+
if not visitor.should_visit_subgroup(subgroup_path):
|
204
|
+
return
|
205
|
+
|
206
|
+
def _entry(i: int) -> TestcaseEntry:
|
207
|
+
return TestcaseEntry(group=group_path, index=i)
|
208
|
+
|
209
|
+
def _sub_entry(i: int) -> TestcaseEntry:
|
210
|
+
return TestcaseEntry(group=subgroup_path, index=i)
|
211
|
+
|
212
|
+
def _copied_to(i: int) -> Testcase:
|
213
|
+
group_fs_path = package.get_build_testgroup_path(group_path)
|
214
|
+
group_prefix = ''
|
215
|
+
if len(prefix) == 2:
|
216
|
+
group_prefix = f'{subgroup_index}-{prefix[1]}-'
|
217
|
+
return Testcase(
|
218
|
+
inputPath=_get_group_input(group_fs_path, group_prefix, i),
|
219
|
+
outputPath=_get_group_output(group_fs_path, group_prefix, i),
|
220
|
+
)
|
221
|
+
|
222
|
+
# Go through testcases.
|
223
|
+
i = 0
|
224
|
+
# Individual testcases.
|
225
|
+
for tc in subgroup.testcases or []:
|
226
|
+
visitor.visit(
|
227
|
+
GenerationTestcaseEntry(
|
228
|
+
group_entry=_entry(i),
|
229
|
+
subgroup_entry=_sub_entry(i),
|
230
|
+
metadata=GenerationMetadata(
|
231
|
+
copied_from=_fill_output_for_testcase(tc),
|
232
|
+
copied_to=_copied_to(i),
|
233
|
+
),
|
234
|
+
)
|
235
|
+
)
|
236
|
+
i += 1
|
237
|
+
|
238
|
+
# Glob testcases.
|
239
|
+
if subgroup.testcaseGlob:
|
240
|
+
matched_inputs = sorted(PosixPath().glob(subgroup.testcaseGlob))
|
241
|
+
|
242
|
+
for input_path in matched_inputs:
|
243
|
+
if not input_path.is_file() or input_path.suffix != '.in':
|
244
|
+
continue
|
245
|
+
|
246
|
+
tc = Testcase(inputPath=input_path)
|
247
|
+
visitor.visit(
|
248
|
+
GenerationTestcaseEntry(
|
249
|
+
group_entry=_entry(i),
|
250
|
+
subgroup_entry=_sub_entry(i),
|
251
|
+
metadata=GenerationMetadata(
|
252
|
+
copied_from=_fill_output_for_testcase(tc),
|
253
|
+
copied_to=_copied_to(i),
|
254
|
+
),
|
255
|
+
)
|
256
|
+
)
|
257
|
+
i += 1
|
258
|
+
|
259
|
+
# Single generators.
|
260
|
+
for generator_call in subgroup.generators:
|
261
|
+
visitor.visit(
|
262
|
+
GenerationTestcaseEntry(
|
263
|
+
group_entry=_entry(i),
|
264
|
+
subgroup_entry=_sub_entry(i),
|
265
|
+
metadata=GenerationMetadata(
|
266
|
+
generator_call=generator_call,
|
267
|
+
copied_to=_copied_to(i),
|
268
|
+
),
|
269
|
+
)
|
270
|
+
)
|
271
|
+
i += 1
|
272
|
+
|
273
|
+
if not visitor.should_visit_generator_scripts(group_path, subgroup_path):
|
274
|
+
return
|
275
|
+
|
276
|
+
# Run generator script.
|
277
|
+
if subgroup.generatorScript is not None:
|
278
|
+
script = _run_generator_script(subgroup)
|
279
|
+
|
280
|
+
# Run each line from generator script.
|
281
|
+
for generator_name, args in _extract_script_lines(script):
|
282
|
+
call = GeneratorCall(name=generator_name, args=args)
|
283
|
+
visitor.visit(
|
284
|
+
GenerationTestcaseEntry(
|
285
|
+
group_entry=_entry(i),
|
286
|
+
subgroup_entry=_sub_entry(i),
|
287
|
+
metadata=GenerationMetadata(
|
288
|
+
generator_call=call,
|
289
|
+
copied_to=_copied_to(i),
|
290
|
+
),
|
291
|
+
)
|
292
|
+
)
|
293
|
+
i += 1
|
294
|
+
|
295
|
+
for group in pkg.testcases:
|
296
|
+
if not visitor.should_visit_group(group.name):
|
297
|
+
continue
|
298
|
+
|
299
|
+
_explore_subgroup(group, 0, [group.name])
|
300
|
+
|
301
|
+
for i, subgroup in enumerate(group.subgroups):
|
302
|
+
_explore_subgroup(subgroup, i, [group.name, subgroup.name])
|
303
|
+
|
304
|
+
|
305
|
+
def _get_necessary_generators_for_groups(
|
306
|
+
groups: Optional[Set[str]] = None,
|
307
|
+
) -> Set[str]:
|
308
|
+
pkg = package.find_problem_package_or_die()
|
309
|
+
existing_generators = set(generator.name for generator in pkg.generators)
|
310
|
+
necessary_generators = set()
|
311
|
+
|
312
|
+
class NecessaryGeneratorsVisitor(TestcaseGroupVisitor):
|
313
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
314
|
+
if entry.metadata.generator_call is not None:
|
315
|
+
necessary_generators.add(entry.metadata.generator_call.name)
|
316
|
+
|
317
|
+
run_testcase_visitor(NecessaryGeneratorsVisitor(groups))
|
318
|
+
|
319
|
+
return existing_generators.intersection(necessary_generators)
|
320
|
+
|
321
|
+
|
322
|
+
def compile_generators(
|
323
|
+
progress: Optional[StatusProgress] = None,
|
324
|
+
tracked_generators: Optional[Set[str]] = None,
|
325
|
+
) -> Dict[str, str]:
|
326
|
+
def update_status(text: str):
|
327
|
+
if progress is not None:
|
328
|
+
progress.update(text)
|
329
|
+
|
330
|
+
pkg = package.find_problem_package_or_die()
|
331
|
+
|
332
|
+
generator_to_compiled_digest = {}
|
333
|
+
|
334
|
+
for generator in pkg.generators:
|
335
|
+
if tracked_generators is not None and generator.name not in tracked_generators:
|
336
|
+
continue
|
337
|
+
update_status(f'Compiling generator [item]{generator.name}[/item]')
|
338
|
+
try:
|
339
|
+
generator_to_compiled_digest[generator.name] = _compile_generator(generator)
|
340
|
+
except:
|
341
|
+
console.console.print(
|
342
|
+
f'[error]Failed compiling generator [item]{generator.name}[/item].[/error]'
|
343
|
+
)
|
344
|
+
raise
|
345
|
+
|
346
|
+
return generator_to_compiled_digest
|
347
|
+
|
348
|
+
|
349
|
+
def expand_generator_call(call: GeneratorCall) -> GeneratorCall:
|
350
|
+
vars = package.find_problem_package_or_die().expanded_vars
|
351
|
+
generator_for_args = generator_parser.Generator(vars)
|
352
|
+
parsed_args = generator_parser.parse(call.args or '')
|
353
|
+
return call.model_copy(update={'args': generator_for_args.generate(parsed_args)})
|
354
|
+
|
355
|
+
|
356
|
+
def generate_standalone(
|
357
|
+
spec: GenerationMetadata,
|
358
|
+
validate: bool = True,
|
359
|
+
group_entry: Optional[TestcaseEntry] = None,
|
360
|
+
generator_digest: Optional[str] = None,
|
361
|
+
validator_digest: Optional[str] = None,
|
362
|
+
progress: Optional[StatusProgress] = None,
|
363
|
+
):
|
364
|
+
def _print_error_header(text: Optional[str] = None):
|
365
|
+
prefix = 'Failed generating test'
|
366
|
+
if group_entry is not None:
|
367
|
+
prefix += (
|
368
|
+
f' [item]{group_entry.group}[/item]/[item]{group_entry.index}[/item]'
|
369
|
+
)
|
370
|
+
suffix = '.'
|
371
|
+
if text:
|
372
|
+
suffix = f': {text}'
|
373
|
+
if spec.generator_call is not None:
|
374
|
+
console.console.print(
|
375
|
+
f'[error]{prefix} using generator call [info]{spec.generator_call.name} {spec.generator_call.args}[/info]{suffix}[/error]'
|
376
|
+
)
|
377
|
+
else:
|
378
|
+
console.console.print(f'[error]{prefix}{suffix}[/error]')
|
379
|
+
|
380
|
+
if spec.generator_call is not None:
|
381
|
+
call = spec.generator_call
|
382
|
+
|
383
|
+
generation_stderr = DigestHolder()
|
384
|
+
|
385
|
+
# Get generator item
|
386
|
+
generator = package.get_generator(call.name)
|
387
|
+
if generator_digest is None:
|
388
|
+
if progress:
|
389
|
+
progress.update(f'Compiling generator {generator.name}...')
|
390
|
+
generator_digest = _compile_generator(generator)
|
391
|
+
|
392
|
+
if progress:
|
393
|
+
progress.update(
|
394
|
+
f'Generating testcase [status]{generator.name} {call.args}[/status]...'
|
395
|
+
)
|
396
|
+
generation_log = run_item(
|
397
|
+
generator,
|
398
|
+
DigestOrSource.create(generator_digest),
|
399
|
+
stdout=DigestOrDest.create(spec.copied_to.inputPath),
|
400
|
+
stderr=DigestOrDest.create(generation_stderr),
|
401
|
+
extra_args=call.args or None,
|
402
|
+
)
|
403
|
+
if not generation_log or generation_log.exitcode != 0:
|
404
|
+
_print_error_header()
|
405
|
+
if generation_log is not None:
|
406
|
+
console.console.print(
|
407
|
+
f'[error]Summary:[/error] {generation_log.get_summary()}'
|
408
|
+
)
|
409
|
+
if generation_stderr.value is not None:
|
410
|
+
console.console.print('[error]Stderr:[/error]')
|
411
|
+
console.console.print(
|
412
|
+
package.get_digest_as_string(generation_stderr.value) or ''
|
413
|
+
)
|
414
|
+
|
415
|
+
raise typer.Exit(1)
|
416
|
+
elif spec.copied_from is not None:
|
417
|
+
_copy_testcase_over(spec.copied_from, spec.copied_to)
|
418
|
+
|
419
|
+
validator = package.get_validator_or_nil()
|
420
|
+
# Run validator, if it is available.
|
421
|
+
if validator is not None and validate:
|
422
|
+
if validator_digest is None:
|
423
|
+
if progress:
|
424
|
+
progress.update('Compiling validator...')
|
425
|
+
validator_tp = validators.compile_main_validator()
|
426
|
+
assert validator_tp is not None
|
427
|
+
_, validator_digest = validator_tp
|
428
|
+
if progress:
|
429
|
+
progress.update('Validating test...')
|
430
|
+
ok, message, *_ = validators.validate_test(
|
431
|
+
spec.copied_to.inputPath,
|
432
|
+
validator,
|
433
|
+
validator_digest,
|
434
|
+
)
|
435
|
+
if not ok:
|
436
|
+
_print_error_header('Failed validating testcase.')
|
437
|
+
console.console.print(f'[error]Message:[/error] {message}')
|
438
|
+
console.console.print(
|
439
|
+
f'Testcase written at [item]{spec.copied_to.inputPath}[/item]'
|
440
|
+
)
|
441
|
+
raise typer.Exit(1)
|
442
|
+
|
443
|
+
|
444
|
+
def generate_testcases(
|
445
|
+
progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
|
446
|
+
):
|
447
|
+
def step():
|
448
|
+
if progress is not None:
|
449
|
+
progress.step()
|
450
|
+
|
451
|
+
compiled_generators = compile_generators(
|
452
|
+
progress=progress,
|
453
|
+
tracked_generators=_get_necessary_generators_for_groups(groups)
|
454
|
+
if groups is not None
|
455
|
+
else None,
|
456
|
+
)
|
457
|
+
|
458
|
+
testcases.clear_built_testcases()
|
459
|
+
|
460
|
+
class BuildTestcaseVisitor(TestcaseGroupVisitor):
|
461
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
462
|
+
if entry.metadata.copied_from is not None:
|
463
|
+
_copy_testcase_over(
|
464
|
+
entry.metadata.copied_from,
|
465
|
+
entry.metadata.copied_to,
|
466
|
+
)
|
467
|
+
|
468
|
+
if entry.metadata.generator_call is not None:
|
469
|
+
generate_standalone(
|
470
|
+
entry.metadata,
|
471
|
+
group_entry=entry.group_entry,
|
472
|
+
validate=False,
|
473
|
+
generator_digest=compiled_generators[
|
474
|
+
entry.metadata.generator_call.name
|
475
|
+
],
|
476
|
+
)
|
477
|
+
step()
|
478
|
+
|
479
|
+
run_testcase_visitor(BuildTestcaseVisitor(groups))
|
480
|
+
|
481
|
+
|
482
|
+
def generate_output_for_testcase(
|
483
|
+
main_solution_digest: str,
|
484
|
+
testcase: Testcase,
|
485
|
+
stderr_path: Optional[pathlib.Path] = None,
|
486
|
+
):
|
487
|
+
assert testcase.outputPath is not None
|
488
|
+
|
489
|
+
if testcase.outputPath.is_file():
|
490
|
+
# Output file was already copied over from manual tests.
|
491
|
+
return
|
492
|
+
|
493
|
+
pkg = package.find_problem_package_or_die()
|
494
|
+
main_solution = package.get_main_solution()
|
495
|
+
if main_solution is None:
|
496
|
+
return
|
497
|
+
|
498
|
+
# Obey no limits when generating testcases.
|
499
|
+
sandbox = EnvironmentSandbox()
|
500
|
+
sandbox.fileSizeLimit = pkg.outputLimit
|
501
|
+
extra_config = ExecutionConfig(sandbox=sandbox)
|
502
|
+
|
503
|
+
try:
|
504
|
+
run_log = run_item(
|
505
|
+
main_solution,
|
506
|
+
DigestOrSource.create(main_solution_digest),
|
507
|
+
stdin=DigestOrSource.create(testcase.inputPath),
|
508
|
+
stdout=DigestOrDest.create(testcase.outputPath),
|
509
|
+
stderr=DigestOrDest.create(stderr_path)
|
510
|
+
if stderr_path is not None
|
511
|
+
else None,
|
512
|
+
extra_config=extra_config,
|
513
|
+
)
|
514
|
+
except:
|
515
|
+
console.console.print(
|
516
|
+
'[error]Failed running main solution to generate testcase.[/error]'
|
517
|
+
)
|
518
|
+
raise
|
519
|
+
|
520
|
+
if run_log is None or run_log.exitcode != 0:
|
521
|
+
console.console.print(
|
522
|
+
f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
|
523
|
+
)
|
524
|
+
if run_log is not None:
|
525
|
+
console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
|
526
|
+
checker_result = checkers.check_with_no_output(run_log)
|
527
|
+
console.console.print(
|
528
|
+
f'[warning]Verdict: [item]{checker_result.outcome.value}[/item][/warning]',
|
529
|
+
)
|
530
|
+
console.console.print(
|
531
|
+
f'[warning]Message: [info]{checker_result.message}[/info][/warning]',
|
532
|
+
)
|
533
|
+
console.console.print(f'Input written at [item]{testcase.inputPath}[/item]')
|
534
|
+
console.console.print(
|
535
|
+
f'Output written at [item]{testcase.outputPath}[/item]'
|
536
|
+
)
|
537
|
+
console.console.print(f'Stderr written at [item]{stderr_path}[/item]')
|
538
|
+
raise typer.Exit(1)
|
539
|
+
|
540
|
+
|
541
|
+
def generate_outputs_for_testcases(
|
542
|
+
progress: Optional[StatusProgress] = None, groups: Optional[Set[str]] = None
|
543
|
+
):
|
544
|
+
def step():
|
545
|
+
if progress is not None:
|
546
|
+
progress.step()
|
547
|
+
|
548
|
+
main_solution = package.get_main_solution()
|
549
|
+
solution_digest: Optional[str] = None
|
550
|
+
|
551
|
+
if main_solution is not None:
|
552
|
+
if progress:
|
553
|
+
progress.update('Compiling main solution...')
|
554
|
+
try:
|
555
|
+
solution_digest = compile_item(main_solution)
|
556
|
+
except:
|
557
|
+
console.console.print('[error]Failed compiling main solution.[/error]')
|
558
|
+
raise
|
559
|
+
|
560
|
+
gen_runs_dir = package.get_problem_runs_dir() / '.gen'
|
561
|
+
shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
|
562
|
+
gen_runs_dir.mkdir(parents=True, exist_ok=True)
|
563
|
+
|
564
|
+
class GenerateOutputsVisitor(TestcaseGroupVisitor):
|
565
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
566
|
+
tc = entry.metadata.copied_to
|
567
|
+
if not tc.inputPath.is_file():
|
568
|
+
return
|
569
|
+
assert tc.outputPath is not None
|
570
|
+
|
571
|
+
if (
|
572
|
+
main_solution is None or solution_digest is None
|
573
|
+
) and not tc.outputPath.is_file():
|
574
|
+
console.console.print(
|
575
|
+
'[error]No main solution found to generate outputs for testcases.[/error]',
|
576
|
+
)
|
577
|
+
raise typer.Exit(1)
|
578
|
+
|
579
|
+
assert solution_digest is not None
|
580
|
+
generate_output_for_testcase(
|
581
|
+
solution_digest,
|
582
|
+
tc,
|
583
|
+
gen_runs_dir / 'main.stderr',
|
584
|
+
)
|
585
|
+
step()
|
586
|
+
|
587
|
+
run_testcase_visitor(GenerateOutputsVisitor(groups))
|
588
|
+
|
589
|
+
|
590
|
+
def extract_generation_testcases(
|
591
|
+
entries: List[TestcaseEntry],
|
592
|
+
) -> List[GenerationTestcaseEntry]:
|
593
|
+
# TODO: support subgroups.
|
594
|
+
groups = set(entry.group for entry in entries)
|
595
|
+
entry_keys = set(entry.key() for entry in entries)
|
596
|
+
|
597
|
+
res: List[GenerationTestcaseEntry] = []
|
598
|
+
|
599
|
+
class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
|
600
|
+
def should_visit_group(self, group_name: str) -> bool:
|
601
|
+
return group_name in groups
|
602
|
+
|
603
|
+
def visit(self, entry: GenerationTestcaseEntry):
|
604
|
+
# TODO: support subgroups.
|
605
|
+
if entry.group_entry.key() not in entry_keys:
|
606
|
+
return
|
607
|
+
res.append(entry)
|
608
|
+
|
609
|
+
run_testcase_visitor(ExtractGenerationTestcasesVisitor())
|
610
|
+
return res
|
@@ -48,6 +48,7 @@ from rbx.box.solutions import (
|
|
48
48
|
run_solutions,
|
49
49
|
)
|
50
50
|
from rbx.box.statements import build_statements
|
51
|
+
from rbx.box.testcases import TestcaseEntry
|
51
52
|
|
52
53
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
53
54
|
app.add_typer(
|
@@ -343,6 +344,14 @@ def irun(
|
|
343
344
|
'-g',
|
344
345
|
help='Generator call to use to generate a single test for execution.',
|
345
346
|
),
|
347
|
+
testcase: Optional[str] = typer.Option(
|
348
|
+
None,
|
349
|
+
'--testcase',
|
350
|
+
'--test',
|
351
|
+
'-tc',
|
352
|
+
'-t',
|
353
|
+
help='Testcase to run, in the format "[group]/[index]". If not specified, will run interactively.',
|
354
|
+
),
|
346
355
|
print: bool = typer.Option(
|
347
356
|
False, '--print', '-p', help='Whether to print outputs to terminal.'
|
348
357
|
),
|
@@ -370,13 +379,6 @@ def irun(
|
|
370
379
|
)
|
371
380
|
return
|
372
381
|
|
373
|
-
main_solution = package.get_main_solution()
|
374
|
-
if check and main_solution is None:
|
375
|
-
console.console.print(
|
376
|
-
'[warning]No main solution found, running without checkers.[/warning]'
|
377
|
-
)
|
378
|
-
check = False
|
379
|
-
|
380
382
|
tracked_solutions = None
|
381
383
|
if outcome is not None:
|
382
384
|
tracked_solutions = {
|
@@ -411,6 +413,7 @@ def irun(
|
|
411
413
|
generator=generators.get_call_from_string(generator)
|
412
414
|
if generator is not None
|
413
415
|
else None,
|
416
|
+
testcase_entry=TestcaseEntry.parse(testcase) if testcase else None,
|
414
417
|
print=print,
|
415
418
|
sanitized=sanitized,
|
416
419
|
)
|