rbx.cp 0.7.0__py3-none-any.whl → 0.8.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/cli.py +79 -31
- rbx/box/code.py +131 -82
- rbx/box/global_package.py +74 -0
- rbx/box/package.py +6 -19
- rbx/box/remote.py +19 -0
- rbx/box/sanitizers/warning_stack.py +3 -3
- rbx/box/solutions.py +13 -7
- rbx/box/stats.py +10 -0
- rbx/box/stresses.py +45 -64
- rbx/box/stressing/finder_parser.py +11 -16
- rbx/box/tasks.py +33 -22
- 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 +6 -3
- rbx/grading/judge/sandboxes/timeit.py +1 -1
- rbx/grading/judge/storage.py +169 -35
- rbx/grading/profiling.py +126 -0
- rbx/grading/steps.py +44 -16
- rbx/grading/steps_with_caching.py +52 -26
- rbx/resources/presets/default/contest/.gitignore +2 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +25 -86
- rbx/resources/presets/default/contest/statement/icpc.sty +322 -0
- rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
- rbx/resources/presets/default/contest/statement/logo.png +0 -0
- rbx/resources/presets/default/contest/statement/template.rbx.tex +45 -36
- rbx/resources/presets/default/preset.rbx.yml +2 -2
- rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
- rbx/resources/presets/default/problem/statement/icpc.sty +322 -0
- rbx/resources/presets/default/problem/statement/template.rbx.tex +47 -79
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/METADATA +3 -1
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/RECORD +43 -36
- rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
- rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
- /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.8.0.dist-info}/LICENSE +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/WHEEL +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.8.0.dist-info}/entry_points.txt +0 -0
rbx/box/remote.py
CHANGED
@@ -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,6 +79,7 @@ class BocaExpander(Expander):
|
|
69
79
|
|
70
80
|
|
71
81
|
REGISTERED_EXPANDERS: List['Expander'] = [
|
82
|
+
MainExpander(),
|
72
83
|
BocaExpander(),
|
73
84
|
]
|
74
85
|
|
@@ -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 path.resolve().is_relative_to(remote_dir.resolve())
|
@@ -4,7 +4,7 @@ import shutil
|
|
4
4
|
|
5
5
|
from rbx import console
|
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:
|
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
|
@@ -245,7 +246,7 @@ async def convert_list_of_solution_evaluations_to_dict(
|
|
245
246
|
|
246
247
|
|
247
248
|
def _get_solutions_for_skeleton(
|
248
|
-
tracked_solutions: Optional[
|
249
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
249
250
|
verification: VerificationLevel = VerificationLevel.NONE,
|
250
251
|
) -> List[Solution]:
|
251
252
|
pkg = package.find_problem_package_or_die()
|
@@ -260,7 +261,7 @@ def _get_solutions_for_skeleton(
|
|
260
261
|
|
261
262
|
|
262
263
|
def _get_report_skeleton(
|
263
|
-
tracked_solutions: Optional[
|
264
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
264
265
|
verification: VerificationLevel = VerificationLevel.NONE,
|
265
266
|
timelimit_override: Optional[int] = None,
|
266
267
|
) -> SolutionReportSkeleton:
|
@@ -393,7 +394,7 @@ def print_best_output(output_files: List[pathlib.Path], empty_warning: bool = Fa
|
|
393
394
|
|
394
395
|
def run_solutions(
|
395
396
|
progress: Optional[StatusProgress] = None,
|
396
|
-
tracked_solutions: Optional[
|
397
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
397
398
|
verification: VerificationLevel = VerificationLevel.NONE,
|
398
399
|
check: bool = True,
|
399
400
|
timelimit_override: Optional[int] = None,
|
@@ -605,7 +606,7 @@ def _run_interactive_solutions(
|
|
605
606
|
|
606
607
|
|
607
608
|
def _get_interactive_skeleton(
|
608
|
-
tracked_solutions: Optional[
|
609
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
609
610
|
verification: VerificationLevel = VerificationLevel.NONE,
|
610
611
|
) -> SolutionReportSkeleton:
|
611
612
|
solutions = _get_solutions_for_skeleton(tracked_solutions, verification)
|
@@ -645,7 +646,7 @@ def _get_interactive_skeleton(
|
|
645
646
|
|
646
647
|
async def run_and_print_interactive_solutions(
|
647
648
|
progress: Optional[StatusProgress] = None,
|
648
|
-
tracked_solutions: Optional[
|
649
|
+
tracked_solutions: Optional[Iterable[str]] = None,
|
649
650
|
verification: VerificationLevel = VerificationLevel.NONE,
|
650
651
|
generator: Optional[GeneratorCall] = None,
|
651
652
|
testcase_entry: Optional[TestcaseEntry] = None,
|
@@ -739,7 +740,12 @@ def expand_solutions_with_source(sols: List[str]) -> List[Tuple[Solution, bool]]
|
|
739
740
|
path_sols = remote.expand_files(sols)
|
740
741
|
|
741
742
|
# Ensure sols exist.
|
742
|
-
|
743
|
+
for sol in path_sols:
|
744
|
+
if not sol.is_file():
|
745
|
+
console.console.print(
|
746
|
+
f'[error]Solution [item]{sol}[/item] could not be found.[/error]'
|
747
|
+
)
|
748
|
+
raise typer.Exit(1)
|
743
749
|
|
744
750
|
seen_sols = set()
|
745
751
|
res: List[Tuple[Solution, bool]] = []
|
@@ -762,7 +768,7 @@ def expand_solutions(sols: List[str]) -> List[Solution]:
|
|
762
768
|
|
763
769
|
|
764
770
|
async def pick_solutions(
|
765
|
-
tracked_solutions: Optional[
|
771
|
+
tracked_solutions: Optional[OrderedSet[str]],
|
766
772
|
extra_solutions: Optional[List[str]] = None,
|
767
773
|
) -> List[str]:
|
768
774
|
pkg = package.find_problem_package_or_die()
|
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
|
@@ -8,24 +10,19 @@ import typer
|
|
8
10
|
from pydantic import BaseModel
|
9
11
|
|
10
12
|
from rbx import console
|
11
|
-
from rbx.box import checkers, generators, package, validators
|
12
|
-
from rbx.box.code import SanitizationLevel, compile_item
|
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,
|
@@ -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
|
|
rbx/box/tasks.py
CHANGED
@@ -5,7 +5,8 @@ from rbx.box import checkers, package, state
|
|
5
5
|
from rbx.box.code import CommunicationItem, run_communication, run_item
|
6
6
|
from rbx.box.environment import EnvironmentSandbox, ExecutionConfig, VerificationLevel
|
7
7
|
from rbx.box.retries import Retrier, get_retrier_config
|
8
|
-
from rbx.box.schema import
|
8
|
+
from rbx.box.schema import CodeItem, Testcase
|
9
|
+
from rbx.grading import profiling
|
9
10
|
from rbx.grading.judge.sandbox import SandboxBase
|
10
11
|
from rbx.grading.limits import Limits
|
11
12
|
from rbx.grading.steps import (
|
@@ -39,7 +40,7 @@ def get_limits_for_language(
|
|
39
40
|
|
40
41
|
|
41
42
|
async def run_solution_on_testcase(
|
42
|
-
solution:
|
43
|
+
solution: CodeItem,
|
43
44
|
compiled_digest: str,
|
44
45
|
checker_digest: Optional[str],
|
45
46
|
testcase: Testcase,
|
@@ -52,6 +53,8 @@ async def run_solution_on_testcase(
|
|
52
53
|
use_timelimit: bool = True,
|
53
54
|
capture_pipes: bool = False,
|
54
55
|
nruns: int = 0,
|
56
|
+
filestem: Optional[str] = None,
|
57
|
+
is_stress: bool = False,
|
55
58
|
) -> Evaluation:
|
56
59
|
if interactor_digest is not None:
|
57
60
|
return await _run_communication_solution_on_testcase(
|
@@ -68,6 +71,8 @@ async def run_solution_on_testcase(
|
|
68
71
|
use_timelimit=use_timelimit,
|
69
72
|
capture_pipes=capture_pipes,
|
70
73
|
nruns=nruns,
|
74
|
+
filestem=filestem,
|
75
|
+
is_stress=is_stress,
|
71
76
|
)
|
72
77
|
|
73
78
|
async def run_fn(retry_index: int) -> Evaluation:
|
@@ -85,29 +90,32 @@ async def run_solution_on_testcase(
|
|
85
90
|
assert testcase.outputPath is not None
|
86
91
|
output_path = testcase.outputPath
|
87
92
|
else:
|
88
|
-
|
93
|
+
stem = filestem or testcase.inputPath.stem
|
94
|
+
output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
|
89
95
|
error_path = output_path.with_suffix('.err')
|
90
96
|
log_path = output_path.with_suffix('.log')
|
91
97
|
eval_path = output_path.with_suffix('.eval')
|
92
98
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
93
99
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
with profiling.PushContext('tasks.run_solution_on_testcase'):
|
101
|
+
run_log = await run_item(
|
102
|
+
solution,
|
103
|
+
DigestOrSource.create(compiled_digest),
|
104
|
+
stdin=DigestOrSource.create(testcase.inputPath),
|
105
|
+
stdout=DigestOrDest.create(output_path),
|
106
|
+
stderr=DigestOrDest.create(error_path),
|
107
|
+
extra_config=extra_config,
|
108
|
+
retry_index=retry_index,
|
109
|
+
)
|
103
110
|
|
104
111
|
if checker_digest is not None:
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
112
|
+
with profiling.PushContext('tasks.run_solution_on_testcase.check'):
|
113
|
+
checker_result = await checkers.check(
|
114
|
+
checker_digest,
|
115
|
+
run_log,
|
116
|
+
testcase,
|
117
|
+
program_output=output_path,
|
118
|
+
)
|
111
119
|
else:
|
112
120
|
checker_result = checkers.check_with_no_output(run_log)
|
113
121
|
|
@@ -134,7 +142,7 @@ async def run_solution_on_testcase(
|
|
134
142
|
if not use_retries:
|
135
143
|
return await run_fn(0)
|
136
144
|
|
137
|
-
retrier = Retrier(get_retrier_config(nruns))
|
145
|
+
retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
|
138
146
|
return await retrier.repeat(run_fn)
|
139
147
|
|
140
148
|
|
@@ -156,7 +164,7 @@ def _get_execution_config(
|
|
156
164
|
|
157
165
|
|
158
166
|
async def _run_communication_solution_on_testcase(
|
159
|
-
solution:
|
167
|
+
solution: CodeItem,
|
160
168
|
compiled_digest: str,
|
161
169
|
interactor_digest: str,
|
162
170
|
checker_digest: Optional[str],
|
@@ -169,6 +177,8 @@ async def _run_communication_solution_on_testcase(
|
|
169
177
|
use_timelimit: bool = True,
|
170
178
|
capture_pipes: bool = False,
|
171
179
|
nruns: int = 0,
|
180
|
+
filestem: Optional[str] = None,
|
181
|
+
is_stress: bool = False,
|
172
182
|
) -> Evaluation:
|
173
183
|
capture_pipes = capture_pipes or state.STATE.debug_logs
|
174
184
|
|
@@ -200,7 +210,8 @@ async def _run_communication_solution_on_testcase(
|
|
200
210
|
assert testcase.outputPath is not None
|
201
211
|
output_path = testcase.outputPath
|
202
212
|
else:
|
203
|
-
|
213
|
+
stem = filestem or testcase.inputPath.stem
|
214
|
+
output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
|
204
215
|
solution_error_path = output_path.with_suffix('.sol.err')
|
205
216
|
interactor_error_path = output_path.with_suffix('.int.err')
|
206
217
|
log_path = output_path.with_suffix('.log')
|
@@ -294,5 +305,5 @@ async def _run_communication_solution_on_testcase(
|
|
294
305
|
if not use_retries:
|
295
306
|
return await run_fn(0)
|
296
307
|
|
297
|
-
retrier = Retrier(get_retrier_config(nruns))
|
308
|
+
retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
|
298
309
|
return await retrier.repeat(run_fn)
|