rbx.cp 0.13.3__py3-none-any.whl → 0.13.5__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/annotations.py +5 -5
- rbx/box/checkers.py +26 -22
- rbx/box/cli.py +0 -4
- rbx/box/code.py +27 -80
- rbx/box/contest/build_contest_statements.py +16 -3
- rbx/box/contest/schema.py +1 -2
- rbx/box/environment.py +16 -6
- rbx/box/fields.py +25 -1
- rbx/box/generators.py +31 -5
- rbx/box/global_package.py +6 -2
- rbx/box/header.py +31 -11
- rbx/box/package.py +3 -15
- rbx/box/presets/__init__.py +2 -2
- rbx/box/schema.py +4 -25
- rbx/box/setter_config.py +11 -0
- rbx/box/solutions.py +12 -4
- rbx/box/statements/build_statements.py +5 -1
- rbx/box/statements/builders.py +7 -7
- rbx/box/statements/schema.py +11 -2
- rbx/box/tasks.py +9 -4
- rbx/box/testcase_utils.py +2 -0
- rbx/box/testing/__init__.py +0 -0
- rbx/box/testing/testing_package.py +246 -0
- rbx/box/testing/testing_preset.py +36 -0
- rbx/box/testing/testing_shared.py +81 -0
- rbx/box/ui/screens/run_explorer.py +0 -8
- rbx/box/ui/utils/run_ui.py +7 -3
- rbx/box/ui/widgets/test_output_box.py +1 -1
- rbx/box/validators.py +5 -2
- rbx/grading/caching.py +67 -16
- rbx/grading/judge/program.py +268 -0
- rbx/grading/judge/sandbox.py +30 -193
- rbx/grading/judge/sandboxes/stupid_sandbox.py +232 -241
- rbx/grading/judge/sandboxes/tee.py +31 -0
- rbx/grading/steps.py +87 -199
- rbx/grading/steps_with_caching.py +15 -6
- rbx/resources/presets/default/problem/problem.rbx.yml +0 -2
- rbx/resources/presets/default/shared/contest_template.rbx.tex +1 -1
- rbx/resources/presets/default/shared/problem_template.rbx.tex +5 -1
- rbx/resources/templates/rbx.h +43 -2
- rbx/testing_utils.py +8 -1
- rbx/utils.py +59 -1
- {rbx_cp-0.13.3.dist-info → rbx_cp-0.13.5.dist-info}/METADATA +2 -1
- {rbx_cp-0.13.3.dist-info → rbx_cp-0.13.5.dist-info}/RECORD +47 -67
- rbx/box/conftest.py +0 -42
- rbx/box/generators_test.py +0 -67
- rbx/box/lazy_importing_test.py +0 -25
- rbx/box/solutions_test.py +0 -47
- rbx/box/validators_test.py +0 -15
- rbx/checker.py +0 -128
- rbx/clone.py +0 -197
- rbx/conftest.py +0 -38
- rbx/create.py +0 -37
- rbx/edit.py +0 -24
- rbx/grading/conftest.py +0 -33
- rbx/grading/judge/sandboxes/isolate.py +0 -695
- rbx/grading/judge/testiso.py +0 -54
- rbx/grading/steps_with_caching_run_test.py +0 -707
- rbx/grading_utils.py +0 -148
- rbx/hydration.py +0 -101
- rbx/main.py +0 -118
- rbx/metadata.py +0 -105
- rbx/resources/envs/isolate.rbx.yml +0 -36
- rbx/resources/presets/default/problem/sols/slow.cpp +0 -15
- rbx/run.py +0 -45
- rbx/schema.py +0 -64
- rbx/submit.py +0 -61
- rbx/test.py +0 -349
- rbx/testcase.py +0 -70
- rbx/testcase_rendering.py +0 -79
- {rbx_cp-0.13.3.dist-info → rbx_cp-0.13.5.dist-info}/LICENSE +0 -0
- {rbx_cp-0.13.3.dist-info → rbx_cp-0.13.5.dist-info}/WHEEL +0 -0
- {rbx_cp-0.13.3.dist-info → rbx_cp-0.13.5.dist-info}/entry_points.txt +0 -0
rbx/annotations.py
CHANGED
@@ -7,7 +7,7 @@ import typer
|
|
7
7
|
import typer.core
|
8
8
|
from typing_extensions import Annotated
|
9
9
|
|
10
|
-
from rbx import config
|
10
|
+
from rbx import config
|
11
11
|
from rbx.config import get_config
|
12
12
|
|
13
13
|
|
@@ -21,10 +21,10 @@ def _get_language_default():
|
|
21
21
|
|
22
22
|
def _get_problem_options():
|
23
23
|
options = set()
|
24
|
-
all_problems = metadata.find_problems()
|
25
|
-
for problem in all_problems:
|
26
|
-
|
27
|
-
|
24
|
+
# all_problems = metadata.find_problems()
|
25
|
+
# for problem in all_problems:
|
26
|
+
# options.add(problem.code)
|
27
|
+
# options.update(problem.aliases)
|
28
28
|
return sorted(options)
|
29
29
|
|
30
30
|
|
rbx/box/checkers.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
import pathlib
|
2
|
-
import signal
|
3
2
|
from typing import List, Optional
|
4
3
|
|
5
4
|
import typer
|
6
5
|
|
7
6
|
from rbx import console
|
8
|
-
from rbx.box import package
|
9
|
-
from rbx.box.code import SanitizationLevel, compile_item, run_item
|
7
|
+
from rbx.box import code, package
|
10
8
|
from rbx.box.schema import Testcase
|
11
9
|
from rbx.grading.judge.sandbox import SandboxBase
|
12
10
|
from rbx.grading.steps import (
|
@@ -28,7 +26,7 @@ def compile_checker(progress: Optional[StatusProgress] = None) -> str:
|
|
28
26
|
progress.update('Compiling checker...')
|
29
27
|
|
30
28
|
try:
|
31
|
-
digest = compile_item(checker, sanitized=SanitizationLevel.PREFER)
|
29
|
+
digest = code.compile_item(checker, sanitized=code.SanitizationLevel.PREFER)
|
32
30
|
except Exception as e:
|
33
31
|
console.console.print('[error]Failed compiling checker[/error]')
|
34
32
|
raise typer.Exit(1) from e
|
@@ -46,7 +44,7 @@ def compile_interactor(progress: Optional[StatusProgress] = None) -> str:
|
|
46
44
|
progress.update('Compiling interactor...')
|
47
45
|
|
48
46
|
try:
|
49
|
-
digest = compile_item(interactor, sanitized=SanitizationLevel.PREFER)
|
47
|
+
digest = code.compile_item(interactor, sanitized=code.SanitizationLevel.PREFER)
|
50
48
|
except Exception as e:
|
51
49
|
console.console.print('[error]Failed compiling interactor.[/error]')
|
52
50
|
raise typer.Exit(1) from e
|
@@ -164,6 +162,14 @@ def process_checker_run_log(
|
|
164
162
|
|
165
163
|
if checker_run_log is None:
|
166
164
|
return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
|
165
|
+
if checker_run_log.exitstatus not in [
|
166
|
+
SandboxBase.EXIT_OK,
|
167
|
+
SandboxBase.EXIT_NONZERO_RETURN,
|
168
|
+
]:
|
169
|
+
return CheckerResult(
|
170
|
+
outcome=Outcome.JUDGE_FAILED,
|
171
|
+
message=f'checker failed with exit status {checker_run_log.exitstatus}: {message}',
|
172
|
+
)
|
167
173
|
if not _is_checker_exitcode(checker_run_log.exitcode):
|
168
174
|
return CheckerResult(
|
169
175
|
outcome=Outcome.JUDGE_FAILED,
|
@@ -196,13 +202,17 @@ async def _check(
|
|
196
202
|
if result.outcome != Outcome.ACCEPTED:
|
197
203
|
return _convert_tle(result, run_log)
|
198
204
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
205
|
+
if (
|
206
|
+
run_log is not None
|
207
|
+
and run_log.metadata is not None
|
208
|
+
and run_log.metadata.limits.output is not None
|
209
|
+
):
|
210
|
+
output_size = program_output.stat().st_size
|
211
|
+
if output_size > run_log.metadata.limits.output * 1024:
|
212
|
+
return CheckerResult(
|
213
|
+
outcome=Outcome.OUTPUT_LIMIT_EXCEEDED,
|
214
|
+
message=f'Output size {run_log.metadata.limits.output}kb, limit is {output_size // 1024}kb.',
|
215
|
+
)
|
206
216
|
|
207
217
|
error = DigestHolder()
|
208
218
|
inputs = [
|
@@ -219,14 +229,14 @@ async def _check(
|
|
219
229
|
dest=pathlib.PosixPath('output.txt'),
|
220
230
|
),
|
221
231
|
]
|
222
|
-
checker_run_log = await run_item(
|
232
|
+
checker_run_log = await code.run_item(
|
223
233
|
package.get_checker(),
|
224
234
|
DigestOrSource.create(checker_digest),
|
225
235
|
stderr=DigestOrDest.create(error),
|
226
236
|
inputs=inputs,
|
227
237
|
extra_args='input.txt output.txt expected.txt',
|
228
238
|
)
|
229
|
-
message = package.get_digest_as_string(error.value
|
239
|
+
message = package.get_digest_as_string(error.value) or ''
|
230
240
|
|
231
241
|
processed_checker_result = process_checker_run_log(checker_run_log, message)
|
232
242
|
if processed_checker_result.outcome == Outcome.INTERNAL_ERROR:
|
@@ -237,6 +247,7 @@ async def _check(
|
|
237
247
|
console.console.print(
|
238
248
|
f'[error]Summary:[/error] {checker_run_log.get_summary()}'
|
239
249
|
)
|
250
|
+
console.console.print(f'[error]Message:[/error] {message}')
|
240
251
|
console.console.print(
|
241
252
|
f'[error]Testcase input:[/error] [item]{testcase.inputPath}[/item]'
|
242
253
|
)
|
@@ -334,14 +345,7 @@ async def check_communication(
|
|
334
345
|
if (
|
335
346
|
interactor_run_log is not None
|
336
347
|
and run_log is not None
|
337
|
-
and (
|
338
|
-
run_log.exitcode == -signal.SIGPIPE
|
339
|
-
or run_log.exitstatus == SandboxBase.EXIT_TERMINATED
|
340
|
-
or (
|
341
|
-
run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
|
342
|
-
and not _is_testlib_eof(interactor_stderr.read_text())
|
343
|
-
)
|
344
|
-
)
|
348
|
+
and (interactor_run_log.exitindex < run_log.exitindex)
|
345
349
|
):
|
346
350
|
result = _check_interactor()
|
347
351
|
if result is not None and result.outcome != Outcome.ACCEPTED:
|
rbx/box/cli.py
CHANGED
@@ -128,7 +128,6 @@ def main(
|
|
128
128
|
capture: bool = typer.Option(
|
129
129
|
True,
|
130
130
|
'--nocapture',
|
131
|
-
flag_value=False,
|
132
131
|
help='Whether to save extra logs and outputs from interactive solutions.',
|
133
132
|
),
|
134
133
|
profile: bool = typer.Option(
|
@@ -259,7 +258,6 @@ async def run(
|
|
259
258
|
check: bool = typer.Option(
|
260
259
|
True,
|
261
260
|
'--nocheck',
|
262
|
-
flag_value=False,
|
263
261
|
help='Whether to not build outputs for tests and run checker.',
|
264
262
|
),
|
265
263
|
detailed: bool = typer.Option(
|
@@ -432,7 +430,6 @@ async def time(
|
|
432
430
|
check: bool = typer.Option(
|
433
431
|
True,
|
434
432
|
'--nocheck',
|
435
|
-
flag_value=False,
|
436
433
|
help='Whether to not build outputs for tests and run checker.',
|
437
434
|
),
|
438
435
|
detailed: bool = typer.Option(
|
@@ -488,7 +485,6 @@ async def irun(
|
|
488
485
|
check: bool = typer.Option(
|
489
486
|
True,
|
490
487
|
'--nocheck',
|
491
|
-
flag_value=False,
|
492
488
|
help='Whether to not build outputs for tests and run checker.',
|
493
489
|
),
|
494
490
|
generator: Optional[str] = typer.Option(
|
rbx/box/code.py
CHANGED
@@ -12,7 +12,7 @@ import rich.text
|
|
12
12
|
import typer
|
13
13
|
from pydantic import BaseModel
|
14
14
|
|
15
|
-
from rbx import console
|
15
|
+
from rbx import console
|
16
16
|
from rbx.box import download, global_package, package, setter_config, state
|
17
17
|
from rbx.box.environment import (
|
18
18
|
CompilationConfig,
|
@@ -48,6 +48,8 @@ from rbx.grading.steps import (
|
|
48
48
|
maybe_get_bits_stdcpp_for_commands,
|
49
49
|
)
|
50
50
|
|
51
|
+
MERGED_CAPTURE_FILENAME = 'merged_capture.pio'
|
52
|
+
|
51
53
|
|
52
54
|
class SanitizationLevel(Enum):
|
53
55
|
NONE = 0
|
@@ -207,6 +209,9 @@ def _format_stack_limit(limit: int) -> str:
|
|
207
209
|
|
208
210
|
|
209
211
|
def _check_stack_limit():
|
212
|
+
cfg = setter_config.get_setter_config()
|
213
|
+
if not cfg.judging.check_stack:
|
214
|
+
return
|
210
215
|
if not state.STATE.run_through_cli:
|
211
216
|
return
|
212
217
|
soft, hard = resource.RLIM_INFINITY, resource.RLIM_INFINITY
|
@@ -261,52 +266,6 @@ class PreparedRun:
|
|
261
266
|
metadata: RunLogMetadata
|
262
267
|
|
263
268
|
|
264
|
-
@dataclasses.dataclass
|
265
|
-
class CaptureSpec:
|
266
|
-
prefix: str
|
267
|
-
output: Optional[DigestOrDest] = None
|
268
|
-
merged_capture: Optional[pathlib.Path] = None
|
269
|
-
|
270
|
-
|
271
|
-
def _prepare_for_communication(
|
272
|
-
run: PreparedRun,
|
273
|
-
stdin: pathlib.Path,
|
274
|
-
stdout: pathlib.Path,
|
275
|
-
reverse_io: bool = False,
|
276
|
-
capture: Optional[CaptureSpec] = None,
|
277
|
-
):
|
278
|
-
run.sandbox_params.set_stdio(
|
279
|
-
stdin=stdin,
|
280
|
-
stdout=stdout,
|
281
|
-
)
|
282
|
-
run.sandbox_params.reverse_io = reverse_io
|
283
|
-
if capture is not None:
|
284
|
-
run.sandbox_params.timeit_prefix = capture.prefix
|
285
|
-
|
286
|
-
if capture.output is not None:
|
287
|
-
output_path = PosixPath('capture')
|
288
|
-
run.sandbox_params.timeit_dups['do'].append(output_path)
|
289
|
-
|
290
|
-
run.artifacts.outputs.append(
|
291
|
-
GradingFileOutput(
|
292
|
-
src=output_path,
|
293
|
-
**capture.output.expand(),
|
294
|
-
touch=True,
|
295
|
-
)
|
296
|
-
)
|
297
|
-
|
298
|
-
if capture.merged_capture is not None:
|
299
|
-
merged_output_path = utils.abspath(package.get_merged_capture_path())
|
300
|
-
run.sandbox_params.timeit_dups['Do'].append(merged_output_path)
|
301
|
-
|
302
|
-
run.artifacts.outputs.append(
|
303
|
-
GradingFileOutput(
|
304
|
-
src=merged_output_path,
|
305
|
-
dest=capture.merged_capture,
|
306
|
-
)
|
307
|
-
)
|
308
|
-
|
309
|
-
|
310
269
|
def _prepare_run(
|
311
270
|
code: CodeItem,
|
312
271
|
executable: DigestOrSource,
|
@@ -318,12 +277,13 @@ def _prepare_run(
|
|
318
277
|
extra_args: Optional[str] = None,
|
319
278
|
extra_config: Optional[ExecutionConfig] = None,
|
320
279
|
retry_index: Optional[int] = None,
|
280
|
+
file_prefix: Optional[str] = None,
|
321
281
|
):
|
322
282
|
language = find_language_name(code)
|
323
283
|
execution_options = get_execution_config(language)
|
324
284
|
if extra_config is not None:
|
325
285
|
execution_options = merge_execution_configs([execution_options, extra_config])
|
326
|
-
file_mapping = get_file_mapping(language)
|
286
|
+
file_mapping = get_file_mapping(language, file_prefix)
|
327
287
|
sandbox_params = get_sandbox_params_from_config(execution_options.sandbox)
|
328
288
|
|
329
289
|
# Sanitization parameters.
|
@@ -739,6 +699,7 @@ async def run_item(
|
|
739
699
|
class CommunicationItem:
|
740
700
|
code: CodeItem
|
741
701
|
executable: DigestOrSource
|
702
|
+
file_prefix: str
|
742
703
|
stderr: Optional[DigestOrDest] = None
|
743
704
|
inputs: Optional[List[GradingFileInput]] = None
|
744
705
|
outputs: Optional[List[GradingFileOutput]] = None
|
@@ -750,21 +711,22 @@ class CommunicationItem:
|
|
750
711
|
return _prepare_run(
|
751
712
|
self.code,
|
752
713
|
self.executable,
|
714
|
+
stdout=self.capture,
|
753
715
|
stderr=self.stderr,
|
754
716
|
inputs=self.inputs,
|
755
717
|
outputs=self.outputs,
|
756
718
|
extra_args=self.extra_args,
|
757
719
|
extra_config=self.extra_config,
|
720
|
+
file_prefix=self.file_prefix,
|
758
721
|
)
|
759
722
|
|
760
723
|
|
761
724
|
async def run_communication(
|
762
725
|
interactor: CommunicationItem,
|
763
726
|
solution: CommunicationItem,
|
764
|
-
merged_capture: Optional[
|
727
|
+
merged_capture: Optional[DigestOrDest] = None,
|
765
728
|
retry_index: Optional[int] = None,
|
766
729
|
):
|
767
|
-
fifo_in, fifo_out = package.get_fifos()
|
768
730
|
interactor_prepared = interactor.prepare()
|
769
731
|
solution_prepared = solution.prepare()
|
770
732
|
|
@@ -772,48 +734,30 @@ async def run_communication(
|
|
772
734
|
interactor_prepared.metadata.retryIndex = retry_index
|
773
735
|
solution_prepared.metadata.retryIndex = retry_index
|
774
736
|
|
775
|
-
|
776
|
-
|
737
|
+
grading_artifacts = GradingArtifacts()
|
738
|
+
grading_artifacts.inputs.extend(interactor_prepared.artifacts.inputs)
|
739
|
+
grading_artifacts.outputs.extend(interactor_prepared.artifacts.outputs)
|
740
|
+
grading_artifacts.inputs.extend(solution_prepared.artifacts.inputs)
|
741
|
+
grading_artifacts.outputs.extend(solution_prepared.artifacts.outputs)
|
777
742
|
|
743
|
+
merged_capture_path: Optional[pathlib.Path] = None
|
778
744
|
if merged_capture is not None:
|
779
|
-
|
780
|
-
|
745
|
+
merged_capture_path = pathlib.Path(MERGED_CAPTURE_FILENAME)
|
746
|
+
grading_artifacts.outputs.append(
|
747
|
+
GradingFileOutput(
|
748
|
+
src=merged_capture_path,
|
749
|
+
**merged_capture.expand(),
|
750
|
+
)
|
781
751
|
)
|
782
752
|
|
783
|
-
_prepare_for_communication(
|
784
|
-
interactor_prepared,
|
785
|
-
fifo_out,
|
786
|
-
fifo_in,
|
787
|
-
capture=CaptureSpec(
|
788
|
-
prefix=interactor_prefix,
|
789
|
-
output=interactor.capture,
|
790
|
-
merged_capture=merged_capture,
|
791
|
-
),
|
792
|
-
)
|
793
|
-
_prepare_for_communication(
|
794
|
-
solution_prepared,
|
795
|
-
fifo_in,
|
796
|
-
fifo_out,
|
797
|
-
reverse_io=True,
|
798
|
-
capture=CaptureSpec(
|
799
|
-
prefix=solution_prefix,
|
800
|
-
output=solution.capture,
|
801
|
-
merged_capture=merged_capture,
|
802
|
-
),
|
803
|
-
)
|
804
|
-
|
805
753
|
interactor_run_params = steps.CoordinatedRunParams(
|
806
754
|
command=interactor_prepared.command,
|
807
755
|
params=interactor_prepared.sandbox_params,
|
808
|
-
sandbox=package.get_singleton_interactor_sandbox(),
|
809
|
-
artifacts=interactor_prepared.artifacts,
|
810
756
|
metadata=interactor_prepared.metadata,
|
811
757
|
)
|
812
758
|
solution_run_params = steps.CoordinatedRunParams(
|
813
759
|
command=solution_prepared.command,
|
814
760
|
params=solution_prepared.sandbox_params,
|
815
|
-
sandbox=package.get_singleton_sandbox(),
|
816
|
-
artifacts=solution_prepared.artifacts,
|
817
761
|
metadata=solution_prepared.metadata,
|
818
762
|
)
|
819
763
|
|
@@ -825,5 +769,8 @@ async def run_communication(
|
|
825
769
|
return await steps_with_caching.run_coordinated(
|
826
770
|
interactor_run_params,
|
827
771
|
solution_run_params,
|
772
|
+
sandbox=package.get_singleton_sandbox(),
|
773
|
+
artifacts=grading_artifacts,
|
828
774
|
dependency_cache=package.get_dependency_cache(),
|
775
|
+
merged_capture=merged_capture_path,
|
829
776
|
)
|
@@ -10,6 +10,7 @@ from rbx import console, testing_utils, utils
|
|
10
10
|
from rbx.box import cd, package
|
11
11
|
from rbx.box.contest.contest_package import get_problems
|
12
12
|
from rbx.box.contest.schema import Contest, ContestProblem, ContestStatement
|
13
|
+
from rbx.box.fields import Primitive
|
13
14
|
from rbx.box.formatting import href
|
14
15
|
from rbx.box.schema import Package, Testcase
|
15
16
|
from rbx.box.statements import build_statements, latex
|
@@ -72,14 +73,21 @@ def get_statement_builder_problems(
|
|
72
73
|
|
73
74
|
|
74
75
|
def get_statement_builder_contest(
|
76
|
+
contest: Contest,
|
75
77
|
statement: ContestStatement,
|
76
78
|
extracted_problems: List[ExtractedProblem],
|
79
|
+
custom_vars: Optional[Dict[str, Primitive]] = None,
|
77
80
|
) -> StatementBuilderContest:
|
78
81
|
return StatementBuilderContest(
|
79
82
|
title=statement.title,
|
80
83
|
location=statement.location,
|
81
84
|
date=statement.date,
|
82
85
|
problems=get_statement_builder_problems(extracted_problems),
|
86
|
+
vars={
|
87
|
+
**contest.expanded_vars,
|
88
|
+
**statement.expanded_vars,
|
89
|
+
**(custom_vars or {}),
|
90
|
+
},
|
83
91
|
)
|
84
92
|
|
85
93
|
|
@@ -241,10 +249,13 @@ def build_contest_only(
|
|
241
249
|
languages=get_environment_languages_for_statement(),
|
242
250
|
params=params,
|
243
251
|
root=pathlib.Path(td),
|
252
|
+
),
|
253
|
+
item=get_statement_builder_contest(
|
254
|
+
contest,
|
255
|
+
statement,
|
256
|
+
extracted_problems,
|
244
257
|
custom_vars=custom_vars,
|
245
|
-
vars={**contest.expanded_vars, **statement.expanded_vars},
|
246
258
|
),
|
247
|
-
item=get_statement_builder_contest(statement, extracted_problems),
|
248
259
|
verbose=False,
|
249
260
|
)
|
250
261
|
|
@@ -326,7 +337,9 @@ def build_statement_rooted(
|
|
326
337
|
last_content = joiner.build(
|
327
338
|
last_content,
|
328
339
|
context=joiner_context,
|
329
|
-
contest=get_statement_builder_contest(
|
340
|
+
contest=get_statement_builder_contest(
|
341
|
+
contest, statement, extracted_problems, custom_vars=custom_vars
|
342
|
+
),
|
330
343
|
)
|
331
344
|
last_output = joiner.output_type()
|
332
345
|
|
rbx/box/contest/schema.py
CHANGED
@@ -3,8 +3,7 @@ from typing import Annotated, Dict, List, Optional
|
|
3
3
|
|
4
4
|
from pydantic import AfterValidator, BaseModel, ConfigDict, Field, model_validator
|
5
5
|
|
6
|
-
from rbx.box.fields import FNameField, NameField
|
7
|
-
from rbx.box.schema import Primitive, expand_var
|
6
|
+
from rbx.box.fields import FNameField, NameField, Primitive, expand_var
|
8
7
|
from rbx.box.statements.expander import expand_statements
|
9
8
|
from rbx.box.statements.schema import (
|
10
9
|
ConversionStep,
|
rbx/box/environment.py
CHANGED
@@ -10,7 +10,6 @@ from rbx import config, console, utils
|
|
10
10
|
from rbx.box import presets
|
11
11
|
from rbx.box.extensions import Extensions, LanguageExtensions
|
12
12
|
from rbx.grading.judge.sandbox import SandboxBase, SandboxParams
|
13
|
-
from rbx.grading.judge.sandboxes.isolate import IsolateSandbox
|
14
13
|
from rbx.grading.judge.sandboxes.stupid_sandbox import StupidSandbox
|
15
14
|
from rbx.grading.limits import Limits
|
16
15
|
|
@@ -58,6 +57,12 @@ relative to the sandbox root.""",
|
|
58
57
|
relative to the sandbox root.""",
|
59
58
|
)
|
60
59
|
|
60
|
+
capture: str = Field(
|
61
|
+
default='capture',
|
62
|
+
description="""Path where to output the capture file after running the program,
|
63
|
+
relative to the sandbox root.""",
|
64
|
+
)
|
65
|
+
|
61
66
|
compilable: str = Field(
|
62
67
|
default='compilable',
|
63
68
|
description="""Path where to copy the compilable file to before compiling the program,
|
@@ -232,7 +237,7 @@ execution config can be individually overridden in the language configuration.""
|
|
232
237
|
|
233
238
|
sandbox: str = Field(
|
234
239
|
default='stupid',
|
235
|
-
description="""Identifier of the sandbox used by this environment (e.g. "stupid"
|
240
|
+
description="""Identifier of the sandbox used by this environment (e.g. "stupid")""",
|
236
241
|
)
|
237
242
|
|
238
243
|
timing: TimingConfig = Field(
|
@@ -381,13 +386,20 @@ def get_execution_config(language: str) -> ExecutionConfig:
|
|
381
386
|
|
382
387
|
|
383
388
|
@functools.cache
|
384
|
-
def get_file_mapping(language: str) -> FileMapping:
|
389
|
+
def get_file_mapping(language: str, file_prefix: Optional[str] = None) -> FileMapping:
|
385
390
|
environment = get_environment()
|
386
|
-
|
391
|
+
mapping = _merge_shallow_models(
|
387
392
|
FileMapping,
|
388
393
|
environment.defaultFileMapping or FileMapping(),
|
389
394
|
get_language(language).fileMapping or FileMapping(),
|
390
395
|
)
|
396
|
+
if file_prefix is not None:
|
397
|
+
mapping.input = f'{file_prefix}_{mapping.input}'
|
398
|
+
mapping.output = f'{file_prefix}_{mapping.output}'
|
399
|
+
mapping.error = f'{file_prefix}_{mapping.error}'
|
400
|
+
mapping.compilable = f'{file_prefix}_{mapping.compilable}'
|
401
|
+
mapping.executable = f'{file_prefix}_{mapping.executable}'
|
402
|
+
return mapping
|
391
403
|
|
392
404
|
|
393
405
|
@functools.cache
|
@@ -395,8 +407,6 @@ def get_sandbox_type() -> Type[SandboxBase]:
|
|
395
407
|
used_sandbox = get_environment().sandbox
|
396
408
|
if used_sandbox == 'stupid':
|
397
409
|
return StupidSandbox
|
398
|
-
if used_sandbox == 'isolate':
|
399
|
-
return IsolateSandbox
|
400
410
|
return StupidSandbox
|
401
411
|
|
402
412
|
|
rbx/box/fields.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import TypeVar
|
1
|
+
from typing import Dict, TypeVar, Union
|
2
2
|
|
3
3
|
from deepmerge import always_merger
|
4
4
|
from pydantic import BaseModel, Field
|
@@ -33,3 +33,27 @@ def merge_pydantic_models(base: T, nxt: T) -> T:
|
|
33
33
|
nxt_dict = nxt.model_dump(exclude_unset=True)
|
34
34
|
merged_dict = always_merger.merge(base_dict, nxt_dict)
|
35
35
|
return base.model_validate(merged_dict)
|
36
|
+
|
37
|
+
|
38
|
+
Primitive = Union[str, int, float, bool]
|
39
|
+
|
40
|
+
|
41
|
+
def expand_var(value: Primitive) -> Primitive:
|
42
|
+
if not isinstance(value, str):
|
43
|
+
return value
|
44
|
+
if value.startswith('\\'):
|
45
|
+
return value[1:]
|
46
|
+
if not value.startswith('py`') or not value.endswith('`'):
|
47
|
+
return value
|
48
|
+
res = eval(value[3:-1])
|
49
|
+
for supported_type in [str, int, float, bool]:
|
50
|
+
if isinstance(res, supported_type):
|
51
|
+
return res
|
52
|
+
|
53
|
+
raise TypeError(
|
54
|
+
f'Variable with backticks should evaluate to a primitive Python type: {value}'
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
def expand_vars(vars: Dict[str, Primitive]) -> Dict[str, Primitive]:
|
59
|
+
return {key: expand_var(value) for key, value in vars.items()}
|
rbx/box/generators.py
CHANGED
@@ -63,13 +63,16 @@ def _warn_about_crlf(path: pathlib.Path):
|
|
63
63
|
|
64
64
|
|
65
65
|
def _check_crlf(path: pathlib.Path):
|
66
|
+
should_fix = False
|
66
67
|
with open(path, 'rb') as f:
|
67
68
|
for line in f:
|
68
|
-
if line.endswith(b'\r\n')
|
69
|
+
if line.endswith(b'\r\n'):
|
69
70
|
_warn_about_crlf(path)
|
71
|
+
should_fix = True
|
70
72
|
break
|
71
73
|
|
72
|
-
|
74
|
+
if should_fix:
|
75
|
+
path.write_text('\n'.join(path.read_text().splitlines()) + '\n')
|
73
76
|
|
74
77
|
|
75
78
|
def _copy_testcase_over(
|
@@ -117,15 +120,25 @@ def _copy_testcase_output_over(
|
|
117
120
|
|
118
121
|
|
119
122
|
def _copy_testcase_outputs_over(
|
120
|
-
testcase: Testcase,
|
123
|
+
testcase: Testcase,
|
124
|
+
dest: Testcase,
|
125
|
+
pipes: bool = False,
|
126
|
+
only_pipes: bool = False,
|
127
|
+
dry_run: bool = False,
|
121
128
|
):
|
129
|
+
if only_pipes:
|
130
|
+
pipes = True
|
122
131
|
assert dest.outputPath is not None
|
123
132
|
if not dry_run:
|
124
133
|
dest.outputPath.parent.mkdir(parents=True, exist_ok=True)
|
125
134
|
|
126
135
|
has_copied = False
|
127
136
|
|
128
|
-
if
|
137
|
+
if (
|
138
|
+
not only_pipes
|
139
|
+
and testcase.outputPath is not None
|
140
|
+
and testcase.outputPath.is_file()
|
141
|
+
):
|
129
142
|
if not dry_run:
|
130
143
|
_check_crlf(testcase.outputPath)
|
131
144
|
shutil.copy(str(testcase.outputPath), str(dest.outputPath))
|
@@ -368,6 +381,7 @@ async def generate_output_for_testcase(
|
|
368
381
|
main_solution_digest: str,
|
369
382
|
testcase: Testcase,
|
370
383
|
interactor_digest: Optional[str] = None,
|
384
|
+
capture_pipes: Optional[bool] = None,
|
371
385
|
):
|
372
386
|
assert testcase.outputPath is not None
|
373
387
|
testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
|
@@ -385,7 +399,7 @@ async def generate_output_for_testcase(
|
|
385
399
|
interactor_digest=interactor_digest,
|
386
400
|
use_retries=False,
|
387
401
|
use_timelimit=False,
|
388
|
-
capture_pipes=
|
402
|
+
capture_pipes=capture_pipes,
|
389
403
|
)
|
390
404
|
|
391
405
|
if eval.result.outcome.is_slow() and eval.result.no_tle_outcome == Outcome.ACCEPTED:
|
@@ -471,10 +485,22 @@ async def generate_outputs_for_testcases(
|
|
471
485
|
raise typer.Exit(1)
|
472
486
|
|
473
487
|
assert solution_digest is not None
|
488
|
+
capture_pipes = None
|
489
|
+
if (
|
490
|
+
pkg.type == TaskType.COMMUNICATION
|
491
|
+
and entry.metadata.copied_from is not None
|
492
|
+
):
|
493
|
+
# If some pipe file is already specified, we don't need to capture the pipes
|
494
|
+
# when running the program.
|
495
|
+
capture_pipes = not _copy_testcase_outputs_over(
|
496
|
+
entry.metadata.copied_from, tc, only_pipes=True, dry_run=True
|
497
|
+
)
|
498
|
+
|
474
499
|
await generate_output_for_testcase(
|
475
500
|
solution_digest,
|
476
501
|
tc,
|
477
502
|
interactor_digest=interactor_digest,
|
503
|
+
capture_pipes=capture_pipes,
|
478
504
|
)
|
479
505
|
if entry.metadata.copied_from is not None:
|
480
506
|
# Copy remaining pipe files.
|
rbx/box/global_package.py
CHANGED
@@ -9,7 +9,7 @@ from rbx.grading.judge.sandbox import SandboxBase
|
|
9
9
|
from rbx.grading.judge.sandboxes.stupid_sandbox import StupidSandbox
|
10
10
|
from rbx.grading.judge.storage import FilesystemStorage, Storage
|
11
11
|
|
12
|
-
CACHE_STEP_VERSION =
|
12
|
+
CACHE_STEP_VERSION = 4
|
13
13
|
|
14
14
|
|
15
15
|
def get_cache_fingerprint() -> str:
|
@@ -29,9 +29,13 @@ def is_cache_valid(cache_dir: pathlib.Path) -> bool:
|
|
29
29
|
return True
|
30
30
|
|
31
31
|
|
32
|
+
def get_global_cache_dir_path() -> pathlib.Path:
|
33
|
+
return get_app_path() / '.box'
|
34
|
+
|
35
|
+
|
32
36
|
@functools.cache
|
33
37
|
def get_global_cache_dir() -> pathlib.Path:
|
34
|
-
cache_dir =
|
38
|
+
cache_dir = get_global_cache_dir_path()
|
35
39
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
36
40
|
fingerprint_file = cache_dir / 'fingerprint'
|
37
41
|
if not fingerprint_file.is_file():
|