rbx.cp 0.5.34__tar.gz → 0.5.35__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.34 → rbx_cp-0.5.35}/PKG-INFO +1 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/pyproject.toml +1 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/builder.py +10 -2
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/build_contest_statements.py +1 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/generators.py +102 -52
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/generators_test.py +5 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/main.py +23 -16
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/schema.py +3 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/solutions.py +1 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/solutions_test.py +5 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/build_statements.py +1 -1
- rbx_cp-0.5.35/rbx/box/testcase_utils.py +135 -0
- rbx_cp-0.5.35/rbx/box/testcases/main.py +158 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/validators.py +1 -1
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/config.py +15 -1
- rbx_cp-0.5.35/rbx/testdata/compatible +0 -0
- rbx_cp-0.5.34/rbx/box/testcases.py +0 -70
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/LICENSE +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/README.md +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/annotations.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/autoenum.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/cd.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/checkers.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/code.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/compile.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/conftest.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/contest_package.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/contest_utils.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/main.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/contest/statements.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/creation.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/deferred.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/download.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/environment.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/extensions.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/formatting.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/package.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/boca/extension.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/boca/packager.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/contest_main.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/main.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/packager.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/polygon/packager.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/polygon/test.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/packaging/polygon/xml_schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/presets/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/presets/fetch.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/presets/lock_schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/presets/schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/retries.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/sanitizers/warning_stack.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/setter_config.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/state.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/builders.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/joiners.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/latex.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/latex_jinja.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/statements/schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/stresses.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/stressing/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/stressing/finder_parser.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/stressing/generator_parser.py +0 -0
- {rbx_cp-0.5.34/rbx/box/ui → rbx_cp-0.5.35/rbx/box/testcases}/__init__.py +0 -0
- {rbx_cp-0.5.34/rbx/grading → rbx_cp-0.5.35/rbx/box/ui}/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/ui/captured_log.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/ui/css/app.tcss +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/ui/main.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/ui/run.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/box/validators_test.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/checker.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/clone.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/conftest.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/console.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/create.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/edit.py +0 -0
- {rbx_cp-0.5.34/rbx/grading/judge → rbx_cp-0.5.35/rbx/grading}/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/caching.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/conftest.py +0 -0
- {rbx_cp-0.5.34/rbx/grading/judge/sandboxes → rbx_cp-0.5.35/rbx/grading/judge}/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/cacher.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/digester.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/sandbox.py +0 -0
- /rbx_cp-0.5.34/rbx/testdata/compatible → /rbx_cp-0.5.35/rbx/grading/judge/sandboxes/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/sandboxes/isolate.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/sandboxes/timeit.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/storage.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/test.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/judge/testiso.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/steps.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/steps_with_caching.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading/steps_with_caching_run_test.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/grading_utils.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/hydration.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/main.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/metadata.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/providers/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/providers/codeforces.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/providers/provider.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/checkers/boilerplate.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/default_config.json +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/default_setter_config.mac.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/default_setter_config.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/envs/default.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/envs/isolate.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/checker.sh +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compare +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/c +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/cc +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/java +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/kt +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/pas +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/py2 +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/compile/py3 +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/c +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/cc +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/java +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/kt +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/py2 +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/packagers/boca/run/py3 +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/preset.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/.gitignore +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/gen.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/random.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/random.txt +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/validator.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/resources/templates/template.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/run.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/schema.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/submit.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/submitors/__init__.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/submitors/codeforces.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/submitors/submitor.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/test.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testcase.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testcase_rendering.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/gen1.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/gen2.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/genScript.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/ole.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/problem.rbx.yml +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/re.sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/tests/1.in +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/tle.sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/validator.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/box1/wa.sol.cpp +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testdata/caching/executable.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/testing_utils.py +0 -0
- {rbx_cp-0.5.34 → rbx_cp-0.5.35}/rbx/utils.py +0 -0
@@ -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
|
@@ -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()
|
@@ -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)
|
@@ -39,6 +39,7 @@ from rbx.box import (
|
|
39
39
|
from rbx.box.contest import main as contest
|
40
40
|
from rbx.box.environment import VerificationLevel, get_environment_path
|
41
41
|
from rbx.box.packaging import main as packaging
|
42
|
+
from rbx.box.testcases import main as testcases
|
42
43
|
from rbx.box.solutions import (
|
43
44
|
estimate_time_limit,
|
44
45
|
get_exact_matching_solutions,
|
@@ -49,7 +50,7 @@ from rbx.box.solutions import (
|
|
49
50
|
run_solutions,
|
50
51
|
)
|
51
52
|
from rbx.box.statements import build_statements
|
52
|
-
from rbx.box.
|
53
|
+
from rbx.box.testcase_utils import TestcaseEntry
|
53
54
|
|
54
55
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
55
56
|
app.add_typer(
|
@@ -82,6 +83,12 @@ app.add_typer(
|
|
82
83
|
app.add_typer(
|
83
84
|
contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
|
84
85
|
)
|
86
|
+
app.add_typer(
|
87
|
+
testcases.app,
|
88
|
+
name='testcases, tc, t',
|
89
|
+
cls=annotations.AliasGroup,
|
90
|
+
help='Testcase management.',
|
91
|
+
)
|
85
92
|
|
86
93
|
|
87
94
|
@app.callback()
|
@@ -166,6 +173,21 @@ def run(
|
|
166
173
|
)
|
167
174
|
check = False
|
168
175
|
|
176
|
+
tracked_solutions = None
|
177
|
+
if outcome is not None:
|
178
|
+
tracked_solutions = {
|
179
|
+
str(solution.path)
|
180
|
+
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
181
|
+
}
|
182
|
+
if solution:
|
183
|
+
tracked_solutions = {solution}
|
184
|
+
|
185
|
+
if choice:
|
186
|
+
tracked_solutions = set(pick_solutions(tracked_solutions))
|
187
|
+
if not tracked_solutions:
|
188
|
+
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
189
|
+
raise typer.Exit(1)
|
190
|
+
|
169
191
|
if not builder.build(verification=verification, output=check):
|
170
192
|
return
|
171
193
|
|
@@ -195,21 +217,6 @@ def run(
|
|
195
217
|
'and the environment default time limit will be used instead.[/warning]'
|
196
218
|
)
|
197
219
|
|
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
220
|
if sanitized and tracked_solutions is None:
|
214
221
|
console.console.print(
|
215
222
|
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
@@ -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')
|
@@ -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,
|
@@ -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 self.index is None:
|
74
|
+
return f'{prefix}/'
|
75
|
+
return f'{prefix}/{self.index}'
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def parse(cls, spec: str) -> 'TestcasePattern':
|
79
|
+
parts = spec.split('/')
|
80
|
+
if not parts:
|
81
|
+
console.console.print(
|
82
|
+
f'[error]Invalid testcase pattern [item]{spec}[/item].[/error]',
|
83
|
+
)
|
84
|
+
raise typer.Exit(1)
|
85
|
+
|
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
|
+
]
|