rbx.cp 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbx/box/cd.py +2 -2
- rbx/box/cli.py +87 -33
- rbx/box/code.py +133 -84
- rbx/box/contest/build_contest_statements.py +2 -2
- rbx/box/contest/contest_package.py +1 -1
- rbx/box/contest/main.py +29 -2
- rbx/box/environment.py +140 -80
- rbx/box/formatting.py +2 -1
- rbx/box/global_package.py +74 -0
- rbx/box/package.py +11 -24
- rbx/box/packaging/__init__.py +0 -0
- rbx/box/packaging/boca/__init__.py +0 -0
- rbx/box/packaging/polygon/packager.py +3 -3
- rbx/box/presets/__init__.py +369 -53
- rbx/box/presets/lock_schema.py +42 -2
- rbx/box/presets/schema.py +4 -0
- rbx/box/remote.py +21 -2
- rbx/box/retries.py +3 -2
- rbx/box/sanitizers/warning_stack.py +5 -5
- rbx/box/solutions.py +37 -25
- rbx/box/statements/build_statements.py +6 -6
- rbx/box/statements/builders.py +1 -1
- rbx/box/stats.py +10 -0
- rbx/box/stresses.py +47 -66
- rbx/box/stressing/finder_parser.py +11 -16
- rbx/box/tasks.py +33 -22
- rbx/box/testcase_utils.py +3 -3
- rbx/box/tooling/boca/scraper.py +1 -1
- rbx/grading/caching.py +98 -47
- rbx/grading/debug_context.py +31 -0
- rbx/grading/grading_context.py +96 -0
- rbx/grading/judge/cacher.py +93 -21
- rbx/grading/judge/sandbox.py +8 -4
- rbx/grading/judge/sandboxes/isolate.py +3 -2
- rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
- rbx/grading/judge/sandboxes/timeit.py +1 -1
- rbx/grading/judge/storage.py +170 -35
- rbx/grading/profiling.py +126 -0
- rbx/grading/steps.py +46 -17
- rbx/grading/steps_with_caching.py +52 -26
- rbx/resources/envs/default.rbx.yml +2 -3
- rbx/resources/envs/isolate.rbx.yml +2 -3
- rbx/resources/presets/default/contest/.gitignore +6 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +24 -86
- rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
- rbx/resources/presets/default/contest/statement/logo.png +0 -0
- rbx/resources/presets/default/env.rbx.yml +67 -0
- rbx/resources/presets/default/preset.rbx.yml +6 -2
- rbx/resources/presets/default/problem/.gitignore +1 -1
- rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
- rbx/resources/presets/default/shared/contest_template.rbx.tex +57 -0
- rbx/resources/presets/default/shared/icpc.sty +322 -0
- rbx/resources/presets/default/shared/problem_template.rbx.tex +57 -0
- rbx/submitors/codeforces.py +3 -2
- rbx/test.py +1 -1
- rbx/utils.py +6 -1
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/METADATA +4 -1
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/RECORD +67 -58
- rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
- rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -42
- rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
- rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -89
- /rbx/resources/presets/default/problem/{gen.cpp → gens/gen.cpp} +0 -0
- /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/000.in +0 -0
- /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/001.in +0 -0
- /rbx/resources/presets/default/problem/{random.py → testplan/random.py} +0 -0
- /rbx/resources/presets/default/problem/{random.txt → testplan/random.txt} +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/LICENSE +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/WHEEL +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union
|
|
5
5
|
|
6
6
|
import typer
|
7
7
|
|
8
|
-
from rbx import console
|
8
|
+
from rbx import console, utils
|
9
9
|
from rbx.box import cd, package
|
10
10
|
from rbx.box.formatting import href, ref
|
11
11
|
|
@@ -27,6 +27,16 @@ class Expander(ABC):
|
|
27
27
|
pass
|
28
28
|
|
29
29
|
|
30
|
+
class MainExpander(Expander):
|
31
|
+
def expand(self, path: pathlib.Path) -> Optional[pathlib.Path]:
|
32
|
+
if str(path) != '@main':
|
33
|
+
return None
|
34
|
+
sol = package.get_main_solution()
|
35
|
+
if sol is None:
|
36
|
+
return None
|
37
|
+
return sol.path
|
38
|
+
|
39
|
+
|
30
40
|
class BocaExpander(Expander):
|
31
41
|
BOCA_REGEX = re.compile(r'\@boca\/(\d+)(?:\-(\d+))?')
|
32
42
|
|
@@ -69,12 +79,13 @@ class BocaExpander(Expander):
|
|
69
79
|
|
70
80
|
|
71
81
|
REGISTERED_EXPANDERS: List['Expander'] = [
|
82
|
+
MainExpander(),
|
72
83
|
BocaExpander(),
|
73
84
|
]
|
74
85
|
|
75
86
|
|
76
87
|
def _relative_to_pkg(path: pathlib.Path) -> pathlib.Path:
|
77
|
-
return
|
88
|
+
return utils.abspath(path).relative_to(pathlib.Path.cwd())
|
78
89
|
|
79
90
|
|
80
91
|
def _try_cacheable_paths(
|
@@ -132,6 +143,9 @@ def _expand_paths(paths: List[pathlib.Path]) -> List[pathlib.Path]:
|
|
132
143
|
continue
|
133
144
|
expanded = _expand_path(path)
|
134
145
|
if expanded is None:
|
146
|
+
console.console.print(
|
147
|
+
f'[warning]Remote solution [item]{path}[/item] could not be expanded. Skipping.[/warning]'
|
148
|
+
)
|
135
149
|
continue
|
136
150
|
res.append(expanded)
|
137
151
|
return res
|
@@ -149,3 +163,8 @@ def expand_file(file: str) -> pathlib.Path:
|
|
149
163
|
)
|
150
164
|
raise typer.Exit(1)
|
151
165
|
return res[0]
|
166
|
+
|
167
|
+
|
168
|
+
def is_path_remote(path: pathlib.Path) -> bool:
|
169
|
+
remote_dir = package.get_problem_remote_dir()
|
170
|
+
return utils.abspath(path).is_relative_to(utils.abspath(remote_dir))
|
rbx/box/retries.py
CHANGED
@@ -5,6 +5,7 @@ import tempfile
|
|
5
5
|
from contextlib import contextmanager
|
6
6
|
from typing import Awaitable, Callable, List, Optional
|
7
7
|
|
8
|
+
from rbx import utils
|
8
9
|
from rbx.box import package
|
9
10
|
from rbx.box.setter_config import RepeatsConfig, get_setter_config
|
10
11
|
from rbx.grading.steps import Evaluation, Outcome
|
@@ -59,8 +60,8 @@ class FileToRecover:
|
|
59
60
|
|
60
61
|
def _move_to_temp_dir(path: pathlib.Path, temp_dir: pathlib.Path) -> FileToRecover:
|
61
62
|
problem_path = package.find_problem()
|
62
|
-
path =
|
63
|
-
temp_dir =
|
63
|
+
path = utils.abspath(path)
|
64
|
+
temp_dir = utils.abspath(temp_dir)
|
64
65
|
relative = path.relative_to(problem_path)
|
65
66
|
|
66
67
|
temp_path = temp_dir / relative
|
@@ -2,9 +2,9 @@ import functools
|
|
2
2
|
import pathlib
|
3
3
|
import shutil
|
4
4
|
|
5
|
-
from rbx import console
|
5
|
+
from rbx import console, utils
|
6
6
|
from rbx.box.schema import CodeItem
|
7
|
-
from rbx.grading.judge.
|
7
|
+
from rbx.grading.judge.cacher import FileCacher
|
8
8
|
from rbx.grading.steps import GradingFileOutput
|
9
9
|
|
10
10
|
|
@@ -18,7 +18,7 @@ class WarningStack:
|
|
18
18
|
self.warnings.add(code.path)
|
19
19
|
|
20
20
|
def add_sanitizer_warning(
|
21
|
-
self,
|
21
|
+
self, cacher: FileCacher, code: CodeItem, reference: GradingFileOutput
|
22
22
|
):
|
23
23
|
if code.path in self.sanitizer_warnings:
|
24
24
|
return
|
@@ -26,7 +26,7 @@ class WarningStack:
|
|
26
26
|
code.path.with_suffix(code.path.suffix + '.log')
|
27
27
|
)
|
28
28
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
29
|
-
f = reference.get_file(
|
29
|
+
f = reference.get_file(cacher)
|
30
30
|
if f is None:
|
31
31
|
return
|
32
32
|
with dest_path.open('wb') as fout:
|
@@ -60,7 +60,7 @@ def _get_warning_runs_dir(root: pathlib.Path) -> pathlib.Path:
|
|
60
60
|
|
61
61
|
|
62
62
|
def get_warning_stack() -> WarningStack:
|
63
|
-
current_root = pathlib.Path.cwd()
|
63
|
+
current_root = utils.abspath(pathlib.Path.cwd())
|
64
64
|
return _get_warning_stack(current_root)
|
65
65
|
|
66
66
|
|
rbx/box/solutions.py
CHANGED
@@ -13,6 +13,7 @@ import rich.markup
|
|
13
13
|
import rich.table
|
14
14
|
import rich.text
|
15
15
|
import typer
|
16
|
+
from ordered_set import OrderedSet
|
16
17
|
from pydantic import BaseModel
|
17
18
|
|
18
19
|
from rbx import console, utils
|
@@ -53,6 +54,7 @@ from rbx.box.testcase_utils import (
|
|
53
54
|
parse_interaction,
|
54
55
|
print_interaction,
|
55
56
|
)
|
57
|
+
from rbx.grading import grading_context
|
56
58
|
from rbx.grading.limits import Limits
|
57
59
|
from rbx.grading.steps import (
|
58
60
|
Evaluation,
|
@@ -245,7 +247,7 @@ async def convert_list_of_solution_evaluations_to_dict(
|
|
245
247
|
|
246
248
|
|
247
249
|
def _get_solutions_for_skeleton(
|
248
|
-
tracked_solutions: Optional[
|
250
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
249
251
|
verification: VerificationLevel = VerificationLevel.NONE,
|
250
252
|
) -> List[Solution]:
|
251
253
|
pkg = package.find_problem_package_or_die()
|
@@ -260,7 +262,7 @@ def _get_solutions_for_skeleton(
|
|
260
262
|
|
261
263
|
|
262
264
|
def _get_report_skeleton(
|
263
|
-
tracked_solutions: Optional[
|
265
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
264
266
|
verification: VerificationLevel = VerificationLevel.NONE,
|
265
267
|
timelimit_override: Optional[int] = None,
|
266
268
|
) -> SolutionReportSkeleton:
|
@@ -393,7 +395,7 @@ def print_best_output(output_files: List[pathlib.Path], empty_warning: bool = Fa
|
|
393
395
|
|
394
396
|
def run_solutions(
|
395
397
|
progress: Optional[StatusProgress] = None,
|
396
|
-
tracked_solutions: Optional[
|
398
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
397
399
|
verification: VerificationLevel = VerificationLevel.NONE,
|
398
400
|
check: bool = True,
|
399
401
|
timelimit_override: Optional[int] = None,
|
@@ -497,7 +499,7 @@ async def _generate_testcase_interactively(
|
|
497
499
|
console.console.print(testcase.inputPath.read_text())
|
498
500
|
else:
|
499
501
|
console.console.print(
|
500
|
-
f'Input was written to [item]{testcase.inputPath
|
502
|
+
f'Input was written to [item]{utils.abspath(testcase.inputPath)}[/item]'
|
501
503
|
)
|
502
504
|
console.console.print()
|
503
505
|
|
@@ -605,7 +607,7 @@ def _run_interactive_solutions(
|
|
605
607
|
|
606
608
|
|
607
609
|
def _get_interactive_skeleton(
|
608
|
-
tracked_solutions: Optional[
|
610
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
609
611
|
verification: VerificationLevel = VerificationLevel.NONE,
|
610
612
|
) -> SolutionReportSkeleton:
|
611
613
|
solutions = _get_solutions_for_skeleton(tracked_solutions, verification)
|
@@ -645,7 +647,7 @@ def _get_interactive_skeleton(
|
|
645
647
|
|
646
648
|
async def run_and_print_interactive_solutions(
|
647
649
|
progress: Optional[StatusProgress] = None,
|
648
|
-
tracked_solutions: Optional[
|
650
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
649
651
|
verification: VerificationLevel = VerificationLevel.NONE,
|
650
652
|
generator: Optional[GeneratorCall] = None,
|
651
653
|
testcase_entry: Optional[TestcaseEntry] = None,
|
@@ -659,23 +661,28 @@ async def run_and_print_interactive_solutions(
|
|
659
661
|
tracked_solutions,
|
660
662
|
verification=verification,
|
661
663
|
)
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
664
|
+
|
665
|
+
should_cache = testcase_entry is not None
|
666
|
+
with grading_context.cache_level(
|
667
|
+
grading_context.CacheLevel.CACHE_COMPILATION, when=not should_cache
|
668
|
+
):
|
669
|
+
testcase = await _generate_testcase_interactively(
|
670
|
+
progress=progress,
|
671
|
+
generator=generator,
|
672
|
+
testcase_entry=testcase_entry,
|
673
|
+
check=check,
|
674
|
+
custom_output=custom_output,
|
675
|
+
sanitized=sanitized,
|
676
|
+
print=print,
|
677
|
+
)
|
678
|
+
items = _run_interactive_solutions(
|
679
|
+
testcase,
|
680
|
+
skeleton=skeleton,
|
681
|
+
progress=progress,
|
682
|
+
verification=verification,
|
683
|
+
check=check,
|
684
|
+
sanitized=sanitized,
|
685
|
+
)
|
679
686
|
|
680
687
|
for item in items:
|
681
688
|
sol = skeleton.find_solution_skeleton(item.solution)
|
@@ -739,7 +746,12 @@ def expand_solutions_with_source(sols: List[str]) -> List[Tuple[Solution, bool]]
|
|
739
746
|
path_sols = remote.expand_files(sols)
|
740
747
|
|
741
748
|
# Ensure sols exist.
|
742
|
-
|
749
|
+
for sol in path_sols:
|
750
|
+
if not sol.is_file():
|
751
|
+
console.console.print(
|
752
|
+
f'[error]Solution [item]{sol}[/item] could not be found.[/error]'
|
753
|
+
)
|
754
|
+
raise typer.Exit(1)
|
743
755
|
|
744
756
|
seen_sols = set()
|
745
757
|
res: List[Tuple[Solution, bool]] = []
|
@@ -762,7 +774,7 @@ def expand_solutions(sols: List[str]) -> List[Solution]:
|
|
762
774
|
|
763
775
|
|
764
776
|
async def pick_solutions(
|
765
|
-
tracked_solutions: Optional[
|
777
|
+
tracked_solutions: Optional[OrderedSet[str]],
|
766
778
|
extra_solutions: Optional[List[str]] = None,
|
767
779
|
) -> List[str]:
|
768
780
|
pkg = package.find_problem_package_or_die()
|
@@ -6,7 +6,7 @@ from typing import Annotated, Any, Dict, List, Optional, Tuple
|
|
6
6
|
import syncer
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from rbx import annotations, console
|
9
|
+
from rbx import annotations, console, utils
|
10
10
|
from rbx.box import environment, naming, package
|
11
11
|
from rbx.box.formatting import href
|
12
12
|
from rbx.box.schema import Package, expand_any_vars
|
@@ -46,7 +46,7 @@ def get_environment_languages_for_statement() -> List[StatementCodeLanguage]:
|
|
46
46
|
res.append(
|
47
47
|
StatementCodeLanguage(
|
48
48
|
id=language.name,
|
49
|
-
name=language.
|
49
|
+
name=language.readableName or language.name,
|
50
50
|
command=cmd or '',
|
51
51
|
)
|
52
52
|
)
|
@@ -173,7 +173,7 @@ def get_relative_assets(
|
|
173
173
|
relative_to: pathlib.Path,
|
174
174
|
assets: List[str],
|
175
175
|
) -> List[Tuple[pathlib.Path, pathlib.Path]]:
|
176
|
-
relative_to =
|
176
|
+
relative_to = utils.abspath(relative_to)
|
177
177
|
if not relative_to.is_dir():
|
178
178
|
relative_to = relative_to.parent
|
179
179
|
res = []
|
@@ -192,7 +192,7 @@ def get_relative_assets(
|
|
192
192
|
raise typer.Exit(1)
|
193
193
|
res.extend(get_relative_assets(relative_to, list(map(str, globbed))))
|
194
194
|
continue
|
195
|
-
if not
|
195
|
+
if not utils.abspath(relative_path).is_relative_to(relative_to):
|
196
196
|
console.console.print(
|
197
197
|
f'[error]Asset [item]{asset}[/item] is not relative to your statement.[/error]'
|
198
198
|
)
|
@@ -200,8 +200,8 @@ def get_relative_assets(
|
|
200
200
|
|
201
201
|
res.append(
|
202
202
|
(
|
203
|
-
|
204
|
-
|
203
|
+
utils.abspath(relative_path),
|
204
|
+
utils.abspath(relative_path).relative_to(relative_to),
|
205
205
|
)
|
206
206
|
)
|
207
207
|
|
rbx/box/statements/builders.py
CHANGED
@@ -319,7 +319,7 @@ class rbxTeXBuilder(StatementBuilder):
|
|
319
319
|
params = typing.cast(rbxToTeX, params)
|
320
320
|
if not params.template:
|
321
321
|
return []
|
322
|
-
return [((root / params.template)
|
322
|
+
return [(utils.abspath(root / params.template), params.template)]
|
323
323
|
|
324
324
|
def build(
|
325
325
|
self,
|
rbx/box/stats.py
CHANGED
@@ -78,6 +78,14 @@ def print_package_stats(root: pathlib.Path = pathlib.Path()) -> int:
|
|
78
78
|
return cache_size + build_size
|
79
79
|
|
80
80
|
|
81
|
+
def print_global_stats() -> int:
|
82
|
+
cache_size = get_cache_size()
|
83
|
+
console.console.print(
|
84
|
+
f'[status]Global cache size[/status]: [item]{get_formatted_memory(cache_size)}[/item]'
|
85
|
+
)
|
86
|
+
return cache_size
|
87
|
+
|
88
|
+
|
81
89
|
def print_reachable_package_stats(root: pathlib.Path = pathlib.Path()) -> None:
|
82
90
|
contest_packages, problem_packages = find_and_group_all_reachable_packages(root)
|
83
91
|
total_size = 0
|
@@ -87,6 +95,8 @@ def print_reachable_package_stats(root: pathlib.Path = pathlib.Path()) -> None:
|
|
87
95
|
for pkg in problem_packages:
|
88
96
|
total_size += print_package_stats(pkg)
|
89
97
|
console.console.print()
|
98
|
+
|
99
|
+
total_size += print_global_stats()
|
90
100
|
console.console.print(
|
91
101
|
f'[status]Total size[/status]: [item]{get_formatted_memory(total_size)}[/item]'
|
92
102
|
)
|
rbx/box/stresses.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shutil
|
1
3
|
import time
|
2
4
|
from shutil import rmtree
|
3
5
|
from typing import List, Optional
|
@@ -7,25 +9,20 @@ import syncer
|
|
7
9
|
import typer
|
8
10
|
from pydantic import BaseModel
|
9
11
|
|
10
|
-
from rbx import console
|
11
|
-
from rbx.box import checkers, generators, package, validators
|
12
|
-
from rbx.box.code import SanitizationLevel, compile_item
|
12
|
+
from rbx import console, utils
|
13
|
+
from rbx.box import checkers, generators, package, tasks, validators
|
14
|
+
from rbx.box.code import SanitizationLevel, compile_item
|
13
15
|
from rbx.box.generators import (
|
14
16
|
GenerationMetadata,
|
15
17
|
expand_generator_call,
|
16
18
|
generate_standalone,
|
17
19
|
)
|
18
|
-
from rbx.box.retries import Retrier
|
19
20
|
from rbx.box.schema import CodeItem, GeneratorCall, Stress, TaskType, Testcase
|
20
21
|
from rbx.box.solutions import compile_solutions, get_outcome_style_verdict
|
21
22
|
from rbx.box.stressing import finder_parser
|
22
23
|
from rbx.grading.steps import (
|
23
|
-
DigestOrDest,
|
24
|
-
DigestOrSource,
|
25
24
|
Evaluation,
|
26
25
|
Outcome,
|
27
|
-
TestcaseIO,
|
28
|
-
TestcaseLog,
|
29
26
|
)
|
30
27
|
from rbx.utils import StatusProgress
|
31
28
|
|
@@ -61,11 +58,6 @@ async def run_stress(
|
|
61
58
|
sanitized: bool = False,
|
62
59
|
) -> StressReport:
|
63
60
|
pkg = package.find_problem_package_or_die()
|
64
|
-
if pkg.type == TaskType.COMMUNICATION:
|
65
|
-
console.console.print(
|
66
|
-
'[error]Communication problems do not support stress testing.[/error]'
|
67
|
-
)
|
68
|
-
raise typer.Exit(1)
|
69
61
|
|
70
62
|
if finder:
|
71
63
|
if generator_call is None:
|
@@ -90,6 +82,8 @@ async def run_stress(
|
|
90
82
|
call = stress.generator
|
91
83
|
generator = package.get_generator(call.name)
|
92
84
|
|
85
|
+
if progress:
|
86
|
+
progress.update('Compiling generator...')
|
93
87
|
try:
|
94
88
|
generator_digest = compile_item(generator, sanitized=SanitizationLevel.PREFER)
|
95
89
|
except:
|
@@ -109,11 +103,18 @@ async def run_stress(
|
|
109
103
|
solutions_digest = compile_solutions(
|
110
104
|
tracked_solutions=set(str(solution.path) for solution in solutions),
|
111
105
|
sanitized=sanitized,
|
106
|
+
progress=progress,
|
112
107
|
)
|
113
108
|
if progress:
|
114
109
|
progress.update('Compiling finders...')
|
115
110
|
finders_digest = {str(finder.path): _compile_finder(finder) for finder in finders}
|
116
111
|
|
112
|
+
interactor_digest = None
|
113
|
+
if pkg.type == TaskType.COMMUNICATION:
|
114
|
+
interactor_digest = checkers.compile_interactor(progress=progress)
|
115
|
+
|
116
|
+
if progress:
|
117
|
+
progress.update('Compiling validator...')
|
117
118
|
compiled_validator = validators.compile_main_validator()
|
118
119
|
|
119
120
|
# Erase old stress directory
|
@@ -158,47 +159,40 @@ async def run_stress(
|
|
158
159
|
else None,
|
159
160
|
)
|
160
161
|
|
161
|
-
@async_lru.alru_cache
|
162
|
+
@async_lru.alru_cache(maxsize=None)
|
162
163
|
async def run_solution_fn(
|
163
164
|
solution: str,
|
164
|
-
|
165
|
+
checker_digest: Optional[str] = None,
|
165
166
|
input_path=input_path,
|
166
|
-
|
167
|
+
output_path: Optional[pathlib.Path] = None,
|
168
|
+
) -> Evaluation:
|
167
169
|
index = solution_indices[solution]
|
168
170
|
sol = solutions[index]
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
retry_index=retry_index,
|
179
|
-
)
|
180
|
-
|
181
|
-
return TestcaseLog(
|
182
|
-
**(run_log.model_dump() if run_log is not None else {}),
|
183
|
-
stdout_absolute_path=output_path.absolute(),
|
184
|
-
stderr_absolute_path=stderr_path.absolute(),
|
171
|
+
return await tasks.run_solution_on_testcase(
|
172
|
+
solutions[index],
|
173
|
+
compiled_digest=solutions_digest[sol.path],
|
174
|
+
checker_digest=checker_digest,
|
175
|
+
interactor_digest=interactor_digest,
|
176
|
+
testcase=Testcase(inputPath=input_path, outputPath=output_path),
|
177
|
+
output_dir=input_path.parent,
|
178
|
+
filestem=f'{index}',
|
179
|
+
is_stress=True,
|
185
180
|
)
|
186
181
|
|
187
182
|
# Get main solution output.
|
188
183
|
expected_output_path = empty_path
|
189
184
|
if needs_expected_output:
|
190
|
-
|
191
|
-
|
192
|
-
if main_checker_result.outcome != Outcome.ACCEPTED:
|
185
|
+
eval = await run_solution_fn(str(solutions[0].path))
|
186
|
+
if eval.result.outcome != Outcome.ACCEPTED:
|
193
187
|
console.console.print(
|
194
188
|
'[error]Error while generating main solution output.[/error]'
|
195
189
|
)
|
196
190
|
console.console.print(f'Input written at [item]{input_path}[/item]')
|
197
191
|
console.console.print(
|
198
|
-
f'Output written at [item]{
|
192
|
+
f'Output written at [item]{eval.log.stdout_absolute_path}[/item]'
|
199
193
|
)
|
200
194
|
console.console.print(
|
201
|
-
f'Stderr written at [item]{
|
195
|
+
f'Stderr written at [item]{eval.log.stderr_absolute_path}[/item]'
|
202
196
|
)
|
203
197
|
console.console.print()
|
204
198
|
console.console.print(
|
@@ -206,44 +200,31 @@ async def run_stress(
|
|
206
200
|
"use the two-way modifier in your finder expression (':2')."
|
207
201
|
)
|
208
202
|
raise typer.Exit(1)
|
209
|
-
|
203
|
+
if eval.log.stdout_absolute_path is not None:
|
204
|
+
expected_output_path = input_path.with_suffix('.ans')
|
205
|
+
shutil.copyfile(eval.log.stdout_absolute_path, expected_output_path)
|
206
|
+
else:
|
207
|
+
expected_output_path = None
|
210
208
|
|
211
|
-
@async_lru.alru_cache
|
209
|
+
@async_lru.alru_cache(maxsize=None)
|
212
210
|
async def run_solution_and_checker_fn(
|
213
211
|
call: finder_parser.FinderCall,
|
214
|
-
input_path=input_path,
|
215
212
|
expected_output_path=expected_output_path,
|
216
213
|
) -> finder_parser.FinderResult:
|
217
|
-
async def run_fn(
|
214
|
+
async def run_fn() -> Evaluation:
|
218
215
|
solution = call.solution
|
219
216
|
checker = call.checker
|
220
217
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
checker_result = await checkers.check(
|
229
|
-
checker_digest,
|
230
|
-
testcase_log,
|
231
|
-
Testcase(inputPath=input_path, outputPath=expected_output_path),
|
232
|
-
program_output=testcase_log.stdout_absolute_path,
|
233
|
-
)
|
234
|
-
|
235
|
-
return Evaluation(
|
236
|
-
result=checker_result,
|
237
|
-
testcase=TestcaseIO(
|
238
|
-
index=0,
|
239
|
-
input=input_path,
|
240
|
-
output=expected_output_path,
|
241
|
-
),
|
242
|
-
log=testcase_log,
|
218
|
+
checker_digest = (
|
219
|
+
finders_digest[checker.path] if checker is not None else None
|
220
|
+
)
|
221
|
+
return await run_solution_fn(
|
222
|
+
solution,
|
223
|
+
checker_digest=checker_digest,
|
224
|
+
output_path=expected_output_path,
|
243
225
|
)
|
244
226
|
|
245
|
-
|
246
|
-
eval = await retrier.repeat(run_fn)
|
227
|
+
eval = await run_fn()
|
247
228
|
|
248
229
|
return finder_parser.FinderResult(
|
249
230
|
solution=call.solution,
|
@@ -330,7 +311,7 @@ def print_stress_report(report: StressReport):
|
|
330
311
|
console.console.print(f'Found [item]{len(report.findings)}[/item] testcases.')
|
331
312
|
|
332
313
|
findings_dir = package.get_problem_runs_dir() / '.stress' / 'findings'
|
333
|
-
console.console.print(f'Findings: {
|
314
|
+
console.console.print(f'Findings: {utils.abspath(findings_dir)}')
|
334
315
|
console.console.print()
|
335
316
|
|
336
317
|
for i, finding in enumerate(report.findings):
|
@@ -10,6 +10,7 @@ import typer
|
|
10
10
|
from rbx import console
|
11
11
|
from rbx.box import package
|
12
12
|
from rbx.box.schema import CodeItem, ExpectedOutcome
|
13
|
+
from rbx.box.solutions import expand_solutions
|
13
14
|
from rbx.grading.steps import CheckerResult, Outcome, RunLog, TestcaseLog
|
14
15
|
|
15
16
|
LARK_GRAMMAR = r"""
|
@@ -26,11 +27,12 @@ negation: _NOT "(" disjunction ")"
|
|
26
27
|
// Expressions
|
27
28
|
logical: eval matcher expected_outcome -> matching
|
28
29
|
| eval equality (eval | outcome) -> equating
|
30
|
+
| eval -> eval_only
|
29
31
|
|
30
|
-
eval: "[" solution checking? "]"
|
32
|
+
eval: "[" solution checking? "]" | solution
|
31
33
|
|
32
34
|
// Eval
|
33
|
-
solution:
|
35
|
+
solution: _solution_filename | WILDCARD
|
34
36
|
checking: "ON"i (checking_mode? checker | ":nil")
|
35
37
|
checking_mode: MODE ":"
|
36
38
|
MODE: "2" | "3"
|
@@ -55,6 +57,7 @@ WILDCARD: "$"
|
|
55
57
|
|
56
58
|
// File name
|
57
59
|
_filename: FILENAME | "\"" FILENAME "\""
|
60
|
+
_solution_filename: _filename | "@" _filename
|
58
61
|
FILENAME: /[\/A-Za-z0-9\-_\.]/+
|
59
62
|
|
60
63
|
// Names (Variables)
|
@@ -217,20 +220,7 @@ def get_all_solutions(tree: lark.ParseTree) -> List[str]:
|
|
217
220
|
|
218
221
|
def get_all_solution_items(tree: lark.ParseTree) -> List[CodeItem]:
|
219
222
|
solution_names = get_all_solutions(tree)
|
220
|
-
res = []
|
221
|
-
|
222
|
-
for solution_name in solution_names:
|
223
|
-
found_solution = package.get_solution_or_nil(solution_name)
|
224
|
-
if found_solution is None:
|
225
|
-
res.append(
|
226
|
-
CodeItem(
|
227
|
-
path=pathlib.Path(solution_name),
|
228
|
-
language=None,
|
229
|
-
compilationFiles=None,
|
230
|
-
)
|
231
|
-
)
|
232
|
-
continue
|
233
|
-
res.append(found_solution)
|
223
|
+
res = typing.cast(List[CodeItem], expand_solutions(solution_names))
|
234
224
|
|
235
225
|
main_solution = package.get_main_solution()
|
236
226
|
if main_solution is None:
|
@@ -391,6 +381,11 @@ class FinderTreeRunner(lark.Transformer):
|
|
391
381
|
|
392
382
|
return FinderOutcome(truth_value=truth_value, results=results)
|
393
383
|
|
384
|
+
def eval_only(self, eval_result: FinderResult) -> FinderOutcome:
|
385
|
+
return self.matching(
|
386
|
+
eval_result, is_positive=True, expected_outcome=ExpectedOutcome.INCORRECT
|
387
|
+
)
|
388
|
+
|
394
389
|
def negation(self, value: FinderOutcome) -> FinderOutcome:
|
395
390
|
return dataclasses.replace(value, truth_value=not value.truth_value)
|
396
391
|
|