rbx.cp 0.5.65__py3-none-any.whl → 0.5.67__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/checkers.py +4 -2
- rbx/box/cli.py +15 -10
- rbx/box/compile.py +3 -1
- rbx/box/formatting.py +5 -1
- rbx/box/generators.py +5 -5
- rbx/box/package.py +24 -3
- rbx/box/packaging/boca/upload.py +40 -1
- rbx/box/presets/__init__.py +2 -1
- rbx/box/remote.py +151 -0
- rbx/box/retries.py +3 -9
- rbx/box/schema.py +25 -10
- rbx/box/solutions.py +59 -36
- rbx/box/stresses.py +17 -6
- rbx/box/ui/css/app.tcss +36 -2
- rbx/box/ui/main.py +24 -0
- rbx/box/ui/screens/differ.py +29 -0
- rbx/box/ui/screens/rich_log_modal.py +29 -0
- rbx/box/ui/screens/run_explorer.py +20 -3
- rbx/box/ui/screens/run_test_explorer.py +20 -0
- rbx/box/ui/screens/selector.py +6 -4
- rbx/box/ui/screens/test_explorer.py +2 -13
- rbx/box/ui/utils/run_ui.py +13 -0
- rbx/box/ui/widgets/diff_box.py +38 -0
- rbx/box/unit.py +34 -2
- rbx/grading/steps.py +7 -0
- rbx/resources/checkers/noop.cpp +13 -0
- rbx/resources/packagers/boca/compile/java +10 -5
- rbx/resources/packagers/boca/compile/py2 +52 -44
- rbx/resources/packagers/boca/compile/py3 +52 -44
- rbx/resources/packagers/boca/interactive/java +2 -3
- rbx/resources/packagers/boca/run/java +2 -3
- rbx/test.py +1 -1
- {rbx_cp-0.5.65.dist-info → rbx_cp-0.5.67.dist-info}/METADATA +1 -1
- {rbx_cp-0.5.65.dist-info → rbx_cp-0.5.67.dist-info}/RECORD +37 -32
- {rbx_cp-0.5.65.dist-info → rbx_cp-0.5.67.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.65.dist-info → rbx_cp-0.5.67.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.65.dist-info → rbx_cp-0.5.67.dist-info}/entry_points.txt +0 -0
rbx/box/checkers.py
CHANGED
@@ -91,8 +91,10 @@ def _check_pre_output(run_log: Optional[RunLog]) -> CheckerResult:
|
|
91
91
|
|
92
92
|
if run_log.exitstatus in [SandboxBase.EXIT_SIGNAL, SandboxBase.EXIT_NONZERO_RETURN]:
|
93
93
|
return CheckerResult(outcome=Outcome.RUNTIME_ERROR)
|
94
|
-
if run_log.exitstatus
|
94
|
+
if run_log.exitstatus == SandboxBase.EXIT_TIMEOUT:
|
95
95
|
return CheckerResult(outcome=Outcome.TIME_LIMIT_EXCEEDED)
|
96
|
+
if run_log.exitstatus == SandboxBase.EXIT_TIMEOUT_WALL:
|
97
|
+
return CheckerResult(outcome=Outcome.IDLENESS_LIMIT_EXCEEDED)
|
96
98
|
if run_log.exitstatus == SandboxBase.EXIT_MEMORY_LIMIT_EXCEEDED:
|
97
99
|
return CheckerResult(outcome=Outcome.MEMORY_LIMIT_EXCEEDED)
|
98
100
|
if run_log.exitstatus == SandboxBase.EXIT_SANDBOX_ERROR:
|
@@ -103,7 +105,7 @@ def _check_pre_output(run_log: Optional[RunLog]) -> CheckerResult:
|
|
103
105
|
|
104
106
|
|
105
107
|
def _convert_tle(result: CheckerResult, run_log: Optional[RunLog]) -> CheckerResult:
|
106
|
-
if result.outcome
|
108
|
+
if result.outcome.is_slow():
|
107
109
|
# This already is a TLE outcome.
|
108
110
|
return result
|
109
111
|
is_sanitized = (
|
rbx/box/cli.py
CHANGED
@@ -32,7 +32,6 @@ from rbx.box.packaging import main as packaging
|
|
32
32
|
from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
|
33
33
|
from rbx.box.solutions import (
|
34
34
|
estimate_time_limit,
|
35
|
-
expand_solutions,
|
36
35
|
get_exact_matching_solutions,
|
37
36
|
get_matching_solutions,
|
38
37
|
pick_solutions,
|
@@ -137,6 +136,13 @@ def ui():
|
|
137
136
|
ui_pkg.start()
|
138
137
|
|
139
138
|
|
139
|
+
@app.command('diff', hidden=True)
|
140
|
+
def diff(path1: pathlib.Path, path2: pathlib.Path):
|
141
|
+
from rbx.box.ui import main as ui_pkg
|
142
|
+
|
143
|
+
ui_pkg.start_differ(path1, path2)
|
144
|
+
|
145
|
+
|
140
146
|
@app.command('serve', hidden=True)
|
141
147
|
def serve():
|
142
148
|
from textual_serve.server import Server
|
@@ -243,7 +249,7 @@ async def run(
|
|
243
249
|
tracked_solutions = set(
|
244
250
|
await pick_solutions(
|
245
251
|
tracked_solutions,
|
246
|
-
extra_solutions=
|
252
|
+
extra_solutions=solutions,
|
247
253
|
)
|
248
254
|
)
|
249
255
|
if not tracked_solutions:
|
@@ -479,7 +485,7 @@ async def irun(
|
|
479
485
|
tracked_solutions = set(
|
480
486
|
await pick_solutions(
|
481
487
|
tracked_solutions,
|
482
|
-
extra_solutions=
|
488
|
+
extra_solutions=solutions,
|
483
489
|
)
|
484
490
|
)
|
485
491
|
if not tracked_solutions:
|
@@ -546,18 +552,17 @@ def create(
|
|
546
552
|
@syncer.sync
|
547
553
|
async def stress(
|
548
554
|
name: Annotated[
|
549
|
-
str,
|
555
|
+
Optional[str],
|
550
556
|
typer.Argument(
|
551
|
-
help='Name of the stress test to run (specified in problem.rbx.yml)
|
552
|
-
'or the generator to run, in case -g is specified.'
|
557
|
+
help='Name of the stress test to run (specified in problem.rbx.yml).'
|
553
558
|
),
|
554
|
-
],
|
559
|
+
] = None,
|
555
560
|
generator_args: Annotated[
|
556
561
|
Optional[str],
|
557
562
|
typer.Option(
|
558
563
|
'--generator',
|
559
564
|
'-g',
|
560
|
-
help='
|
565
|
+
help='Generator call to use to generate a single test for execution.',
|
561
566
|
),
|
562
567
|
] = None,
|
563
568
|
finder: Annotated[
|
@@ -604,9 +609,9 @@ async def stress(
|
|
604
609
|
|
605
610
|
with utils.StatusProgress('Running stress...') as s:
|
606
611
|
report = await stresses.run_stress(
|
607
|
-
name,
|
608
612
|
timeout,
|
609
|
-
|
613
|
+
name=name,
|
614
|
+
generator_call=generator_args,
|
610
615
|
finder=finder,
|
611
616
|
findingsLimit=findings,
|
612
617
|
progress=s,
|
rbx/box/compile.py
CHANGED
@@ -3,7 +3,7 @@ import pathlib
|
|
3
3
|
import typer
|
4
4
|
|
5
5
|
from rbx import annotations, console
|
6
|
-
from rbx.box import code, package
|
6
|
+
from rbx.box import code, package, remote
|
7
7
|
from rbx.box.code import SanitizationLevel
|
8
8
|
from rbx.box.sanitizers import warning_stack
|
9
9
|
from rbx.box.schema import CodeItem
|
@@ -35,6 +35,8 @@ def _compile(item: CodeItem, sanitized: SanitizationLevel, warnings: bool):
|
|
35
35
|
def any(path: str, sanitized: bool = False, warnings: bool = False):
|
36
36
|
pkg = package.find_problem_package_or_die()
|
37
37
|
|
38
|
+
path = str(remote.expand_file(path))
|
39
|
+
|
38
40
|
solution = package.get_solution_or_nil(path)
|
39
41
|
if solution is not None:
|
40
42
|
_compile(
|
rbx/box/formatting.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
import os
|
2
2
|
import pathlib
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Any, Optional
|
4
4
|
|
5
5
|
from rbx.box import setter_config
|
6
6
|
|
7
7
|
|
8
|
+
def ref(text: Any) -> str:
|
9
|
+
return f'[item]{text}[/item]'
|
10
|
+
|
11
|
+
|
8
12
|
def href(url: os.PathLike[str], text: Optional[str] = None, style: str = 'item') -> str:
|
9
13
|
custom_text = False
|
10
14
|
if text is None:
|
rbx/box/generators.py
CHANGED
@@ -110,7 +110,10 @@ def get_all_built_testcases() -> Dict[str, List[Testcase]]:
|
|
110
110
|
|
111
111
|
|
112
112
|
def get_call_from_string(call_str: str) -> GeneratorCall:
|
113
|
-
|
113
|
+
try:
|
114
|
+
name, args = call_str.split(None, 1)
|
115
|
+
except ValueError:
|
116
|
+
return GeneratorCall(name=call_str, args='')
|
114
117
|
return GeneratorCall(name=name, args=args)
|
115
118
|
|
116
119
|
|
@@ -317,10 +320,7 @@ async def generate_output_for_testcase(
|
|
317
320
|
capture_pipes=True,
|
318
321
|
)
|
319
322
|
|
320
|
-
if (
|
321
|
-
eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
322
|
-
and eval.result.no_tle_outcome == Outcome.ACCEPTED
|
323
|
-
):
|
323
|
+
if eval.result.outcome.is_slow() and eval.result.no_tle_outcome == Outcome.ACCEPTED:
|
324
324
|
console.console.print(
|
325
325
|
f'[warning]Testcase [item]{testcase.inputPath}[/item] finished in TLE, but test was generated successfully.[/warning]'
|
326
326
|
)
|
rbx/box/package.py
CHANGED
@@ -21,6 +21,7 @@ from rbx.box.schema import (
|
|
21
21
|
Package,
|
22
22
|
Solution,
|
23
23
|
Stress,
|
24
|
+
TaskType,
|
24
25
|
TestcaseGroup,
|
25
26
|
TestcaseSubgroup,
|
26
27
|
)
|
@@ -32,6 +33,7 @@ from rbx.grading.judge.storage import FilesystemStorage, Storage
|
|
32
33
|
|
33
34
|
YAML_NAME = 'problem.rbx.yml'
|
34
35
|
_DEFAULT_CHECKER = 'wcmp.cpp'
|
36
|
+
_NOOP_CHECKER = 'noop.cpp'
|
35
37
|
TEMP_DIR = None
|
36
38
|
CACHE_STEP_VERSION = 1
|
37
39
|
|
@@ -150,6 +152,19 @@ def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
|
150
152
|
return cache_dir
|
151
153
|
|
152
154
|
|
155
|
+
@functools.cache
|
156
|
+
def get_problem_remote_dir(
|
157
|
+
platform: Optional[str] = None, root: pathlib.Path = pathlib.Path()
|
158
|
+
) -> pathlib.Path:
|
159
|
+
remote_dir = get_problem_cache_dir(root) / '.remote'
|
160
|
+
remote_dir.mkdir(parents=True, exist_ok=True)
|
161
|
+
|
162
|
+
if platform is not None:
|
163
|
+
remote_dir = remote_dir / platform
|
164
|
+
remote_dir.mkdir(parents=True, exist_ok=True)
|
165
|
+
return remote_dir
|
166
|
+
|
167
|
+
|
153
168
|
@functools.cache
|
154
169
|
def get_problem_storage_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
155
170
|
storage_dir = get_problem_cache_dir(root) / '.storage'
|
@@ -277,13 +292,19 @@ def get_validator_or_nil(root: pathlib.Path = pathlib.Path()) -> Optional[CodeIt
|
|
277
292
|
return package.validator
|
278
293
|
|
279
294
|
|
295
|
+
@functools.cache
|
296
|
+
def get_default_checker(root: pathlib.Path = pathlib.Path()) -> CodeItem:
|
297
|
+
package = find_problem_package_or_die(root)
|
298
|
+
if package.type == TaskType.COMMUNICATION:
|
299
|
+
return CodeItem(path=get_builtin_checker(_NOOP_CHECKER).absolute())
|
300
|
+
return CodeItem(path=get_builtin_checker(_DEFAULT_CHECKER).absolute())
|
301
|
+
|
302
|
+
|
280
303
|
@functools.cache
|
281
304
|
def get_checker(root: pathlib.Path = pathlib.Path()) -> CodeItem:
|
282
305
|
package = find_problem_package_or_die(root)
|
283
306
|
|
284
|
-
return package.checker or
|
285
|
-
path=get_builtin_checker(_DEFAULT_CHECKER).absolute()
|
286
|
-
)
|
307
|
+
return package.checker or get_default_checker(root)
|
287
308
|
|
288
309
|
|
289
310
|
@functools.cache
|
rbx/box/packaging/boca/upload.py
CHANGED
@@ -4,6 +4,7 @@ import hashlib
|
|
4
4
|
import os
|
5
5
|
import pathlib
|
6
6
|
import re
|
7
|
+
import shutil
|
7
8
|
import typing
|
8
9
|
from typing import Any, NoReturn, Optional, Tuple
|
9
10
|
|
@@ -248,13 +249,51 @@ class BocaUploader:
|
|
248
249
|
'[error]Persistent error while uploading problem to BOCA website.[/error]'
|
249
250
|
)
|
250
251
|
console.console.print(
|
251
|
-
'[warning]This might be caused by PHP max upload size limit (which usually defaults to
|
252
|
+
'[warning]This might be caused by PHP max upload size limit (which usually defaults to 2MB).[/warning]'
|
252
253
|
)
|
253
254
|
console.console.print(
|
254
255
|
'[warning]Check [item]https://www.php.net/manual/en/ini.core.php#ini.sect.file-uploads[/item] for more information.[/warning]'
|
255
256
|
)
|
256
257
|
raise typer.Exit(1)
|
257
258
|
|
259
|
+
def download_run(self, run_number: int, site_number: int, into_dir: pathlib.Path):
|
260
|
+
url = f'{self.base_url}/admin/runedit.php?runnumber={run_number}&runsitenumber={site_number}'
|
261
|
+
_, html = self.open(
|
262
|
+
url,
|
263
|
+
error_msg=f'Error while downloading BOCA run [item]{run_number}-{site_number}[/item]',
|
264
|
+
)
|
265
|
+
|
266
|
+
soup = BeautifulSoup(html, 'html.parser')
|
267
|
+
rows = soup.select('tr')
|
268
|
+
|
269
|
+
href: Optional[str] = None
|
270
|
+
filename: Optional[pathlib.Path] = None
|
271
|
+
|
272
|
+
for row in rows:
|
273
|
+
row_text = row.select('td')[0].text.strip().lower()
|
274
|
+
if row_text != "team's code:":
|
275
|
+
continue
|
276
|
+
link_col = row.select_one('td:nth-of-type(2) a:nth-of-type(1)')
|
277
|
+
if link_col is None:
|
278
|
+
continue
|
279
|
+
href = str(link_col.attrs['href'])
|
280
|
+
filename = pathlib.Path(link_col.text.strip())
|
281
|
+
break
|
282
|
+
|
283
|
+
if href is None or filename is None:
|
284
|
+
self.raw_error(
|
285
|
+
"Error while downloading run:\nNo link to team's code found."
|
286
|
+
)
|
287
|
+
|
288
|
+
link = self.br.find_link(url=href)
|
289
|
+
tmp_file, _ = self.br.retrieve(link.absolute_url)
|
290
|
+
if tmp_file is None:
|
291
|
+
self.raw_error('Error while downloading run:\nDownloaded file is None.')
|
292
|
+
final_path = into_dir / filename.with_stem(f'{run_number}-{site_number}')
|
293
|
+
final_path.parent.mkdir(parents=True, exist_ok=True)
|
294
|
+
shutil.move(tmp_file, final_path)
|
295
|
+
return final_path
|
296
|
+
|
258
297
|
|
259
298
|
@functools.lru_cache
|
260
299
|
def get_boca_uploader(
|
rbx/box/presets/__init__.py
CHANGED
@@ -3,7 +3,6 @@ import shutil
|
|
3
3
|
import tempfile
|
4
4
|
from typing import Annotated, Iterable, List, Optional, Sequence, Union
|
5
5
|
|
6
|
-
import questionary
|
7
6
|
import rich
|
8
7
|
import rich.prompt
|
9
8
|
import typer
|
@@ -498,6 +497,8 @@ def copy_local_preset(
|
|
498
497
|
if preset_remote_uri is None:
|
499
498
|
return
|
500
499
|
|
500
|
+
import questionary
|
501
|
+
|
501
502
|
add_submodule = questionary.confirm(
|
502
503
|
'The preset is installed from a remote Git repository. Do you want to add it as a submodule of your project?',
|
503
504
|
default=False,
|
rbx/box/remote.py
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
import pathlib
|
2
|
+
import re
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from typing import List, Optional, Tuple, Union
|
5
|
+
|
6
|
+
import typer
|
7
|
+
|
8
|
+
from rbx import console
|
9
|
+
from rbx.box import cd, package
|
10
|
+
from rbx.box.formatting import href, ref
|
11
|
+
|
12
|
+
PathLike = Union[str, pathlib.Path]
|
13
|
+
|
14
|
+
|
15
|
+
class Expander(ABC):
|
16
|
+
def get_remote_path(self, path: pathlib.Path) -> pathlib.Path:
|
17
|
+
return package.get_problem_remote_dir() / path
|
18
|
+
|
19
|
+
def cacheable_paths(self, path: pathlib.Path) -> List[pathlib.Path]:
|
20
|
+
return []
|
21
|
+
|
22
|
+
def cacheable_globs(self, path: pathlib.Path) -> List[str]:
|
23
|
+
return []
|
24
|
+
|
25
|
+
@abstractmethod
|
26
|
+
def expand(self, path: pathlib.Path) -> Optional[pathlib.Path]:
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
30
|
+
class BocaExpander(Expander):
|
31
|
+
BOCA_REGEX = re.compile(r'\@boca\/(\d+)(?:\-(\d+))?')
|
32
|
+
|
33
|
+
def get_match(self, path_str: str) -> Optional[Tuple[int, int]]:
|
34
|
+
match = self.BOCA_REGEX.match(path_str)
|
35
|
+
if match is None:
|
36
|
+
return None
|
37
|
+
run_number = int(match.group(1))
|
38
|
+
site_number = int(match.group(2)) if match.group(2) is not None else 1
|
39
|
+
return run_number, site_number
|
40
|
+
|
41
|
+
def get_boca_folder(self) -> pathlib.Path:
|
42
|
+
return self.get_remote_path(pathlib.Path('boca'))
|
43
|
+
|
44
|
+
def get_boca_path(self, run_number: int, site_number: int) -> pathlib.Path:
|
45
|
+
return self.get_boca_folder() / f'{run_number}-{site_number}'
|
46
|
+
|
47
|
+
def cacheable_globs(self, path: pathlib.Path) -> List[str]:
|
48
|
+
match = self.get_match(str(path))
|
49
|
+
if match is None:
|
50
|
+
return []
|
51
|
+
run_number, site_number = match
|
52
|
+
return [str(self.get_boca_path(run_number, site_number)) + '.*']
|
53
|
+
|
54
|
+
def expand(self, path: pathlib.Path) -> Optional[pathlib.Path]:
|
55
|
+
from rbx.box.packaging.boca import upload as boca_upload
|
56
|
+
|
57
|
+
match = self.get_match(str(path))
|
58
|
+
if match is None:
|
59
|
+
return None
|
60
|
+
run_number, site_number = match
|
61
|
+
|
62
|
+
boca_uploader = boca_upload.get_boca_uploader()
|
63
|
+
boca_uploader.login()
|
64
|
+
sol_path = boca_uploader.download_run(
|
65
|
+
run_number, site_number, self.get_boca_folder()
|
66
|
+
)
|
67
|
+
console.console.print(f'Downloaded {href(sol_path)} from BOCA...')
|
68
|
+
return sol_path
|
69
|
+
|
70
|
+
|
71
|
+
REGISTERED_EXPANDERS: List['Expander'] = [
|
72
|
+
BocaExpander(),
|
73
|
+
]
|
74
|
+
|
75
|
+
|
76
|
+
def _relative_to_pkg(path: pathlib.Path) -> pathlib.Path:
|
77
|
+
return path.resolve().relative_to(pathlib.Path.cwd())
|
78
|
+
|
79
|
+
|
80
|
+
def _try_cacheable_paths(
|
81
|
+
path: pathlib.Path, expander: Expander
|
82
|
+
) -> Optional[pathlib.Path]:
|
83
|
+
cached_paths = expander.cacheable_paths(path)
|
84
|
+
for cached_path in cached_paths:
|
85
|
+
if cached_path.exists():
|
86
|
+
return _relative_to_pkg(cached_path)
|
87
|
+
return None
|
88
|
+
|
89
|
+
|
90
|
+
def _try_cacheable_globs(
|
91
|
+
path: pathlib.Path, expander: Expander
|
92
|
+
) -> Optional[pathlib.Path]:
|
93
|
+
cached_globs = expander.cacheable_globs(path)
|
94
|
+
for cached_glob in cached_globs:
|
95
|
+
rel_glob = _relative_to_pkg(pathlib.Path(cached_glob))
|
96
|
+
globbed = list(pathlib.Path.cwd().glob(str(rel_glob)))
|
97
|
+
if not globbed:
|
98
|
+
continue
|
99
|
+
return _relative_to_pkg(globbed[0])
|
100
|
+
return None
|
101
|
+
|
102
|
+
|
103
|
+
def _try_cache(path: pathlib.Path, expander: Expander) -> Optional[pathlib.Path]:
|
104
|
+
cached = _try_cacheable_paths(path, expander)
|
105
|
+
if cached is not None:
|
106
|
+
return cached
|
107
|
+
return _try_cacheable_globs(path, expander)
|
108
|
+
|
109
|
+
|
110
|
+
def _expand_path(path: pathlib.Path) -> Optional[pathlib.Path]:
|
111
|
+
if not cd.is_problem_package():
|
112
|
+
console.console.print(
|
113
|
+
f'Skipping expansion of {ref(path)} because we are not in a problem package.'
|
114
|
+
)
|
115
|
+
raise typer.Exit(1)
|
116
|
+
|
117
|
+
for expander in REGISTERED_EXPANDERS:
|
118
|
+
cached = _try_cache(path, expander)
|
119
|
+
if cached is not None:
|
120
|
+
return cached
|
121
|
+
expanded = expander.expand(path)
|
122
|
+
if expanded is not None:
|
123
|
+
return _relative_to_pkg(expanded)
|
124
|
+
return None
|
125
|
+
|
126
|
+
|
127
|
+
def _expand_paths(paths: List[pathlib.Path]) -> List[pathlib.Path]:
|
128
|
+
res = []
|
129
|
+
for path in paths:
|
130
|
+
if not str(path).startswith('@'):
|
131
|
+
res.append(path)
|
132
|
+
continue
|
133
|
+
expanded = _expand_path(path)
|
134
|
+
if expanded is None:
|
135
|
+
continue
|
136
|
+
res.append(expanded)
|
137
|
+
return res
|
138
|
+
|
139
|
+
|
140
|
+
def expand_files(files: List[str]) -> List[pathlib.Path]:
|
141
|
+
return _expand_paths([pathlib.Path(file) for file in files])
|
142
|
+
|
143
|
+
|
144
|
+
def expand_file(file: str) -> pathlib.Path:
|
145
|
+
res = expand_files([file])
|
146
|
+
if len(res) != 1:
|
147
|
+
console.console.print(
|
148
|
+
f'Could not expand {ref(file)} because it is not a valid expansion.'
|
149
|
+
)
|
150
|
+
raise typer.Exit(1)
|
151
|
+
return res[0]
|
rbx/box/retries.py
CHANGED
@@ -18,10 +18,7 @@ def _both_accepted(eval_a: Evaluation, eval_b: Evaluation) -> bool:
|
|
18
18
|
|
19
19
|
|
20
20
|
def _any_tle(eval_a: Evaluation, eval_b: Evaluation) -> bool:
|
21
|
-
return (
|
22
|
-
eval_a.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
23
|
-
or eval_b.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
24
|
-
)
|
21
|
+
return eval_a.result.outcome.is_slow() or eval_b.result.outcome.is_slow()
|
25
22
|
|
26
23
|
|
27
24
|
def _get_faster(eval_a: Evaluation, eval_b: Evaluation) -> Evaluation:
|
@@ -130,13 +127,10 @@ class Retrier:
|
|
130
127
|
|
131
128
|
def should_repeat(self, eval: Evaluation) -> bool:
|
132
129
|
if self.is_stress:
|
133
|
-
if (
|
134
|
-
eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
|
135
|
-
and self.retries_for_stress > 0
|
136
|
-
):
|
130
|
+
if eval.result.outcome.is_slow() and self.retries_for_stress > 0:
|
137
131
|
self.retries_for_stress -= 1
|
138
132
|
return True
|
139
|
-
if eval.result.outcome
|
133
|
+
if eval.result.outcome.is_slow() and self.retries > 0:
|
140
134
|
self.retries -= 1
|
141
135
|
return True
|
142
136
|
if self.reps > 0:
|
rbx/box/schema.py
CHANGED
@@ -57,6 +57,9 @@ def expand_var(value: Primitive) -> Primitive:
|
|
57
57
|
|
58
58
|
|
59
59
|
class ExpectedOutcome(AutoEnum):
|
60
|
+
ANY = alias('any') # type: ignore
|
61
|
+
"""Expected outcome for any outcome."""
|
62
|
+
|
60
63
|
ACCEPTED = alias('accepted', 'ac', 'correct') # type: ignore
|
61
64
|
"""Expected outcome for correct solutions (AC)."""
|
62
65
|
|
@@ -97,6 +100,8 @@ class ExpectedOutcome(AutoEnum):
|
|
97
100
|
Especially useful for environments where TLE and RTE are indistinguishable."""
|
98
101
|
|
99
102
|
def style(self) -> str:
|
103
|
+
if self == ExpectedOutcome.ANY:
|
104
|
+
return 'orange'
|
100
105
|
if self == ExpectedOutcome.ACCEPTED:
|
101
106
|
return 'green'
|
102
107
|
if self == ExpectedOutcome.WRONG_ANSWER:
|
@@ -114,29 +119,39 @@ class ExpectedOutcome(AutoEnum):
|
|
114
119
|
def is_slow(self) -> bool:
|
115
120
|
return self in [ExpectedOutcome.TIME_LIMIT_EXCEEDED, ExpectedOutcome.TLE_OR_RTE]
|
116
121
|
|
122
|
+
def matches_tle_and_is_incorrect(self) -> bool:
|
123
|
+
return self.match(Outcome.TIME_LIMIT_EXCEEDED) and not self.match(
|
124
|
+
Outcome.ACCEPTED
|
125
|
+
)
|
126
|
+
|
117
127
|
def match(self, outcome: Outcome) -> bool:
|
128
|
+
if self == ExpectedOutcome.ANY:
|
129
|
+
return True
|
118
130
|
if self == ExpectedOutcome.ACCEPTED:
|
119
131
|
return outcome == Outcome.ACCEPTED
|
120
132
|
if self == ExpectedOutcome.ACCEPTED_OR_TLE:
|
121
|
-
return outcome in {Outcome.ACCEPTED
|
133
|
+
return outcome in {Outcome.ACCEPTED} or outcome.is_slow()
|
122
134
|
if self == ExpectedOutcome.WRONG_ANSWER:
|
123
135
|
return outcome == Outcome.WRONG_ANSWER
|
124
136
|
if self == ExpectedOutcome.INCORRECT:
|
125
|
-
return
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
137
|
+
return (
|
138
|
+
outcome
|
139
|
+
in {
|
140
|
+
Outcome.WRONG_ANSWER,
|
141
|
+
Outcome.RUNTIME_ERROR,
|
142
|
+
Outcome.MEMORY_LIMIT_EXCEEDED,
|
143
|
+
Outcome.OUTPUT_LIMIT_EXCEEDED,
|
144
|
+
}
|
145
|
+
or outcome.is_slow()
|
146
|
+
)
|
132
147
|
if self == ExpectedOutcome.RUNTIME_ERROR:
|
133
148
|
return outcome == Outcome.RUNTIME_ERROR
|
134
149
|
if self == ExpectedOutcome.TIME_LIMIT_EXCEEDED:
|
135
|
-
return outcome
|
150
|
+
return outcome.is_slow()
|
136
151
|
if self == ExpectedOutcome.MEMORY_LIMIT_EXCEEDED:
|
137
152
|
return outcome == Outcome.MEMORY_LIMIT_EXCEEDED
|
138
153
|
if self == ExpectedOutcome.TLE_OR_RTE:
|
139
|
-
return outcome in {Outcome.
|
154
|
+
return outcome in {Outcome.RUNTIME_ERROR} or outcome.is_slow()
|
140
155
|
if self == ExpectedOutcome.OUTPUT_LIMIT_EXCEEDED:
|
141
156
|
return outcome == Outcome.OUTPUT_LIMIT_EXCEEDED
|
142
157
|
return False
|