rbx.cp 0.5.45__py3-none-any.whl → 0.5.47__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 +81 -28
- rbx/box/cli.py +12 -10
- rbx/box/generators.py +77 -40
- rbx/box/packaging/boca/packager.py +44 -7
- rbx/box/packaging/main.py +7 -0
- rbx/box/packaging/moj/packager.py +88 -8
- rbx/box/packaging/packager.py +7 -2
- rbx/box/packaging/polygon/packager.py +5 -4
- rbx/box/solutions.py +7 -5
- rbx/box/statements/builders.py +22 -3
- rbx/box/stresses.py +0 -1
- rbx/box/tasks.py +18 -8
- rbx/box/testcase_utils.py +66 -0
- rbx/grading/judge/sandbox.py +29 -1
- rbx/grading/judge/sandboxes/isolate.py +12 -2
- rbx/grading/judge/sandboxes/stupid_sandbox.py +17 -5
- rbx/grading/judge/sandboxes/timeit.py +12 -3
- rbx/grading/processing_context.py +48 -0
- rbx/grading/steps.py +24 -13
- rbx/resources/packagers/boca/checker.sh +8 -6
- rbx/resources/packagers/boca/compare.sh +48 -0
- rbx/resources/packagers/boca/interactive/c +207 -0
- rbx/resources/packagers/boca/interactive/cc +207 -0
- rbx/resources/packagers/boca/interactive/cpp +207 -0
- rbx/resources/packagers/boca/interactive/java +240 -0
- rbx/resources/packagers/boca/interactive/kt +231 -0
- rbx/resources/packagers/boca/interactive/py2 +209 -0
- rbx/resources/packagers/boca/interactive/py3 +209 -0
- rbx/resources/packagers/boca/interactor_compile.sh +45 -0
- rbx/resources/packagers/boca/run/bkp +163 -0
- rbx/resources/packagers/boca/run/c +19 -19
- rbx/resources/packagers/boca/run/cc +19 -19
- rbx/resources/packagers/boca/run/cpp +19 -19
- rbx/resources/packagers/boca/run/java +51 -51
- rbx/resources/packagers/boca/run/kt +30 -30
- rbx/resources/packagers/boca/run/py2 +42 -42
- rbx/resources/packagers/boca/run/py3 +42 -42
- rbx/resources/packagers/moj/scripts/c/compile.sh +19 -0
- rbx/resources/packagers/moj/scripts/c/prep.sh +5 -0
- rbx/resources/packagers/moj/scripts/c/run.sh +16 -0
- rbx/resources/packagers/moj/scripts/compare.sh +32 -6
- rbx/resources/packagers/moj/scripts/cpp/compile.sh +19 -0
- rbx/resources/packagers/moj/scripts/cpp/prep.sh +5 -0
- rbx/resources/packagers/moj/scripts/cpp/run.sh +16 -0
- rbx/resources/packagers/moj/scripts/interactor_prep.sh +14 -0
- rbx/resources/packagers/moj/scripts/interactor_run.sh +38 -0
- rbx/resources/packagers/moj/scripts/java/compile.sh +12 -0
- rbx/resources/packagers/moj/scripts/java/prep.sh +8 -0
- rbx/resources/packagers/moj/scripts/java/run.sh +17 -0
- rbx/resources/packagers/moj/scripts/py2/compile.sh +7 -0
- rbx/resources/packagers/moj/scripts/py2/prep.sh +5 -0
- rbx/resources/packagers/moj/scripts/py2/run.sh +16 -0
- rbx/resources/packagers/moj/scripts/py3/compile.sh +7 -0
- rbx/resources/packagers/moj/scripts/py3/prep.sh +5 -0
- rbx/resources/packagers/moj/scripts/py3/run.sh +16 -0
- {rbx_cp-0.5.45.dist-info → rbx_cp-0.5.47.dist-info}/METADATA +1 -1
- {rbx_cp-0.5.45.dist-info → rbx_cp-0.5.47.dist-info}/RECORD +60 -33
- rbx/resources/packagers/boca/compare +0 -53
- {rbx_cp-0.5.45.dist-info → rbx_cp-0.5.47.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.45.dist-info → rbx_cp-0.5.47.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.45.dist-info → rbx_cp-0.5.47.dist-info}/entry_points.txt +0 -0
rbx/box/checkers.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import pathlib
|
2
|
+
import signal
|
2
3
|
from typing import Optional
|
3
4
|
|
4
5
|
import typer
|
@@ -108,23 +109,31 @@ def _convert_tle(result: CheckerResult, run_log: Optional[RunLog]) -> CheckerRes
|
|
108
109
|
return result
|
109
110
|
|
110
111
|
|
112
|
+
def _is_checker_exitcode(exitcode: int) -> bool:
|
113
|
+
return exitcode in [0, 1, 2, 3]
|
114
|
+
|
115
|
+
|
111
116
|
def process_checker_run_log(
|
112
117
|
checker_run_log: Optional[RunLog], message: str
|
113
|
-
) ->
|
118
|
+
) -> CheckerResult:
|
114
119
|
if (
|
115
120
|
checker_run_log is not None
|
116
|
-
and checker_run_log.
|
117
|
-
and (
|
118
|
-
checker_run_log.exitstatus != SandboxBase.EXIT_NONZERO_RETURN
|
119
|
-
or checker_run_log.exitcode not in [0, 1, 2, 3]
|
120
|
-
)
|
121
|
+
and checker_run_log.exitstatus == SandboxBase.EXIT_SANDBOX_ERROR
|
121
122
|
):
|
122
|
-
|
123
|
+
# When the sandbox fails, it means the checker failed to run.
|
124
|
+
# We don't know what happened.
|
125
|
+
return CheckerResult(
|
126
|
+
outcome=Outcome.INTERNAL_ERROR,
|
127
|
+
message='sandbox failed to run checker',
|
128
|
+
)
|
123
129
|
|
124
130
|
if checker_run_log is None:
|
125
131
|
return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
|
126
|
-
if checker_run_log.exitcode
|
127
|
-
return
|
132
|
+
if not _is_checker_exitcode(checker_run_log.exitcode):
|
133
|
+
return CheckerResult(
|
134
|
+
outcome=Outcome.JUDGE_FAILED,
|
135
|
+
message=f'checker failed with unknown exit code {checker_run_log.exitcode}: {message}',
|
136
|
+
)
|
128
137
|
|
129
138
|
result = CheckerResult(outcome=Outcome.ACCEPTED, message=message)
|
130
139
|
|
@@ -185,7 +194,7 @@ async def _check(
|
|
185
194
|
message = package.get_digest_as_string(error.value or '') or ''
|
186
195
|
|
187
196
|
processed_checker_result = process_checker_run_log(checker_run_log, message)
|
188
|
-
if processed_checker_result
|
197
|
+
if processed_checker_result.outcome == Outcome.INTERNAL_ERROR:
|
189
198
|
console.console.print(
|
190
199
|
f'[error]Checker [item]{package.get_checker().path}[/item] failed unexpectedly.[/error]'
|
191
200
|
)
|
@@ -240,33 +249,77 @@ async def check_communication(
|
|
240
249
|
program_output: pathlib.Path,
|
241
250
|
skip_run_log: bool = False,
|
242
251
|
) -> CheckerResult:
|
243
|
-
|
252
|
+
def _extra_check_and_sanitize(result: CheckerResult) -> CheckerResult:
|
253
|
+
result.sanitizer_warnings = _check_sanitizer_warnings(run_log)
|
254
|
+
return result
|
255
|
+
|
256
|
+
def _check_interactor(reinterpret_rte: bool = True) -> Optional[CheckerResult]:
|
257
|
+
result = process_checker_run_log(
|
258
|
+
interactor_run_log, interactor_stderr.read_text()
|
259
|
+
)
|
260
|
+
if result.outcome in [Outcome.JUDGE_FAILED, Outcome.WRONG_ANSWER]:
|
261
|
+
# Only return testlib errors (exit code 2 and 3), skip other types of RTEs and verdicts.
|
262
|
+
if (
|
263
|
+
interactor_run_log is not None
|
264
|
+
and _is_checker_exitcode(interactor_run_log.exitcode)
|
265
|
+
and interactor_run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
|
266
|
+
):
|
267
|
+
return _extra_check_and_sanitize(result)
|
268
|
+
else:
|
269
|
+
# Check for other verdicts, but potentially reinterpret RTEs as JUDGE_FAILED.
|
270
|
+
result = check_with_no_output(interactor_run_log)
|
271
|
+
if result.outcome == Outcome.RUNTIME_ERROR and reinterpret_rte:
|
272
|
+
result.outcome = Outcome.JUDGE_FAILED
|
273
|
+
if result.outcome != Outcome.ACCEPTED:
|
274
|
+
return _extra_check_and_sanitize(result)
|
275
|
+
else:
|
276
|
+
# Return any other checker/interactor errors, such as INTERNAL_ERRORs.
|
277
|
+
return _extra_check_and_sanitize(result)
|
278
|
+
|
279
|
+
# No relevant error was found.
|
280
|
+
return None
|
244
281
|
|
282
|
+
# 1. If the solution received SIGPIPE or was terminated, it means the
|
283
|
+
# interactor exited before it. Thus, check the interactor, as it might have
|
284
|
+
# returned a checker verdict.
|
285
|
+
#
|
286
|
+
# Also, do the same if the solution returned a non-zero exit code. Checker verdict
|
287
|
+
# should usually supersede a solution's RTE verdict.
|
288
|
+
if (
|
289
|
+
interactor_run_log is not None
|
290
|
+
and run_log is not None
|
291
|
+
and (
|
292
|
+
run_log.exitcode == -signal.SIGPIPE
|
293
|
+
or run_log.exitstatus == SandboxBase.EXIT_TERMINATED
|
294
|
+
or run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
|
295
|
+
)
|
296
|
+
):
|
297
|
+
result = _check_interactor()
|
298
|
+
if result is not None and result.outcome != Outcome.ACCEPTED:
|
299
|
+
return _extra_check_and_sanitize(result)
|
300
|
+
|
301
|
+
# 2. Check if the solution failed without looking at its output (TLE, MLE, RTE, etc).
|
245
302
|
result = check_with_no_output(run_log)
|
246
|
-
result.sanitizer_warnings = sanitizer_warnings
|
247
303
|
if result.outcome != Outcome.ACCEPTED:
|
248
|
-
return result
|
304
|
+
return _extra_check_and_sanitize(result)
|
249
305
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
if result is None:
|
256
|
-
result = check_with_no_output(interactor_run_log)
|
257
|
-
result.sanitizer_warnings = sanitizer_warnings
|
258
|
-
if result.outcome != Outcome.ACCEPTED:
|
259
|
-
result.outcome = Outcome.JUDGE_FAILED
|
260
|
-
return result
|
306
|
+
# 3. Now check interactor return code regardless of what happened to the
|
307
|
+
# solution.
|
308
|
+
result = _check_interactor()
|
309
|
+
if result is not None and result.outcome != Outcome.ACCEPTED:
|
310
|
+
return _extra_check_and_sanitize(result)
|
261
311
|
|
262
|
-
result
|
312
|
+
# Just a defensive pattern to ensure result is not None, should never happen.
|
313
|
+
result = check_with_no_output(interactor_run_log)
|
263
314
|
if result.outcome != Outcome.ACCEPTED:
|
264
|
-
|
315
|
+
if result.outcome == Outcome.RUNTIME_ERROR:
|
316
|
+
result.outcome = Outcome.JUDGE_FAILED
|
317
|
+
return _extra_check_and_sanitize(result)
|
265
318
|
|
319
|
+
# 4. Now actually check the output with a checker.
|
266
320
|
if checker_digest is not None:
|
267
321
|
result = await check(
|
268
322
|
checker_digest, run_log, testcase, program_output, skip_run_log
|
269
323
|
)
|
270
|
-
result.sanitizer_warnings = sanitizer_warnings
|
271
324
|
|
272
|
-
return result
|
325
|
+
return _extra_check_and_sanitize(result)
|
rbx/box/cli.py
CHANGED
@@ -222,7 +222,7 @@ async def run(
|
|
222
222
|
tracked_solutions = {solution}
|
223
223
|
|
224
224
|
if choice:
|
225
|
-
tracked_solutions = set(pick_solutions(tracked_solutions))
|
225
|
+
tracked_solutions = set(await pick_solutions(tracked_solutions))
|
226
226
|
if not tracked_solutions:
|
227
227
|
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
228
228
|
raise typer.Exit(1)
|
@@ -453,7 +453,7 @@ async def irun(
|
|
453
453
|
tracked_solutions = {solution}
|
454
454
|
|
455
455
|
if choice:
|
456
|
-
tracked_solutions = set(pick_solutions(tracked_solutions))
|
456
|
+
tracked_solutions = set(await pick_solutions(tracked_solutions))
|
457
457
|
if not tracked_solutions:
|
458
458
|
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
459
459
|
raise typer.Exit(1)
|
@@ -515,7 +515,8 @@ def create(
|
|
515
515
|
help='Run a stress test.',
|
516
516
|
)
|
517
517
|
@package.within_problem
|
518
|
-
|
518
|
+
@syncer.sync
|
519
|
+
async def stress(
|
519
520
|
name: Annotated[
|
520
521
|
str,
|
521
522
|
typer.Argument(
|
@@ -574,7 +575,7 @@ def stress(
|
|
574
575
|
from rbx.box import stresses
|
575
576
|
|
576
577
|
with utils.StatusProgress('Running stress...') as s:
|
577
|
-
report = stresses.run_stress(
|
578
|
+
report = await stresses.run_stress(
|
578
579
|
name,
|
579
580
|
timeout,
|
580
581
|
args=generator_args,
|
@@ -608,15 +609,15 @@ def stress(
|
|
608
609
|
|
609
610
|
import questionary
|
610
611
|
|
611
|
-
testgroup = questionary.select(
|
612
|
+
testgroup = await questionary.select(
|
612
613
|
'Choose the testgroup to add the tests to.\nOnly test groups that have a .txt generatorScript are shown below: ',
|
613
614
|
choices=list(groups_by_name) + ['(create new script)', '(skip)'],
|
614
|
-
).
|
615
|
+
).ask_async()
|
615
616
|
|
616
617
|
if testgroup == '(create new script)':
|
617
|
-
new_script_name = questionary.text(
|
618
|
+
new_script_name = await questionary.text(
|
618
619
|
'Enter the name of the new .txt generatorScript file: '
|
619
|
-
).
|
620
|
+
).ask_async()
|
620
621
|
new_script_path = pathlib.Path(new_script_name).with_suffix('.txt')
|
621
622
|
new_script_path.parent.mkdir(parents=True, exist_ok=True)
|
622
623
|
new_script_path.touch()
|
@@ -673,7 +674,8 @@ def stress(
|
|
673
674
|
help='Compile an asset given its path.',
|
674
675
|
)
|
675
676
|
@package.within_problem
|
676
|
-
|
677
|
+
@syncer.sync
|
678
|
+
async def compile_command(
|
677
679
|
path: Annotated[
|
678
680
|
Optional[str],
|
679
681
|
typer.Argument(help='Path to the asset to compile.'),
|
@@ -694,7 +696,7 @@ def compile_command(
|
|
694
696
|
if path is None:
|
695
697
|
import questionary
|
696
698
|
|
697
|
-
path = questionary.path("What's the path to your asset?").
|
699
|
+
path = await questionary.path("What's the path to your asset?").ask_async()
|
698
700
|
if path is None:
|
699
701
|
console.console.print('[error]No path specified.[/error]')
|
700
702
|
raise typer.Exit(1)
|
rbx/box/generators.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import pathlib
|
2
2
|
import shutil
|
3
|
-
import tempfile
|
4
3
|
from typing import Dict, List, Optional, Set
|
5
4
|
|
6
5
|
import typer
|
@@ -63,6 +62,47 @@ def _copy_testcase_over(
|
|
63
62
|
)
|
64
63
|
|
65
64
|
|
65
|
+
def _copy_testcase_output_over(
|
66
|
+
src_output_path: pathlib.Path, dest_output_path: pathlib.Path, suffix: str
|
67
|
+
) -> bool:
|
68
|
+
dest_output_path.parent.mkdir(parents=True, exist_ok=True)
|
69
|
+
|
70
|
+
src_path = src_output_path.with_suffix(suffix)
|
71
|
+
if not src_path.is_file():
|
72
|
+
return False
|
73
|
+
|
74
|
+
shutil.copy(str(src_path), str(dest_output_path.with_suffix(suffix)))
|
75
|
+
return True
|
76
|
+
|
77
|
+
|
78
|
+
def _copy_testcase_outputs_over(
|
79
|
+
testcase: Testcase, dest: Testcase, pipes: bool = False
|
80
|
+
):
|
81
|
+
assert dest.outputPath is not None
|
82
|
+
dest.outputPath.parent.mkdir(parents=True, exist_ok=True)
|
83
|
+
|
84
|
+
has_copied = False
|
85
|
+
|
86
|
+
if testcase.outputPath is not None and testcase.outputPath.is_file():
|
87
|
+
shutil.copy(str(testcase.outputPath), str(dest.outputPath))
|
88
|
+
has_copied = True
|
89
|
+
|
90
|
+
if not pipes:
|
91
|
+
return has_copied
|
92
|
+
|
93
|
+
reference_path = testcase.outputPath or testcase.inputPath
|
94
|
+
if _copy_testcase_output_over(reference_path, dest.outputPath, '.pin'):
|
95
|
+
has_copied = True
|
96
|
+
|
97
|
+
if _copy_testcase_output_over(reference_path, dest.outputPath, '.pout'):
|
98
|
+
has_copied = True
|
99
|
+
|
100
|
+
if _copy_testcase_output_over(reference_path, dest.outputPath, '.pio'):
|
101
|
+
has_copied = True
|
102
|
+
|
103
|
+
return has_copied
|
104
|
+
|
105
|
+
|
66
106
|
def get_all_built_testcases() -> Dict[str, List[Testcase]]:
|
67
107
|
pkg = package.find_problem_package_or_die()
|
68
108
|
res = {group.name: find_built_testcases(group) for group in pkg.testcases}
|
@@ -256,59 +296,46 @@ async def generate_testcases(
|
|
256
296
|
async def generate_output_for_testcase(
|
257
297
|
main_solution_digest: str,
|
258
298
|
testcase: Testcase,
|
259
|
-
stderr_path: Optional[pathlib.Path] = None,
|
260
299
|
interactor_digest: Optional[str] = None,
|
261
300
|
):
|
262
301
|
assert testcase.outputPath is not None
|
263
302
|
testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
|
264
303
|
testcase.outputPath.parent.mkdir(parents=True, exist_ok=True)
|
265
304
|
|
266
|
-
if testcase.outputPath.is_file():
|
267
|
-
# Output file was already copied over from manual tests.
|
268
|
-
return
|
269
|
-
|
270
305
|
main_solution = package.get_main_solution()
|
271
306
|
if main_solution is None:
|
272
307
|
return
|
273
308
|
|
274
|
-
|
275
|
-
|
309
|
+
eval: Evaluation = await run_solution_on_testcase(
|
310
|
+
main_solution,
|
311
|
+
main_solution_digest,
|
312
|
+
None,
|
313
|
+
testcase,
|
314
|
+
interactor_digest=interactor_digest,
|
315
|
+
use_retries=False,
|
316
|
+
use_timelimit=False,
|
317
|
+
capture_pipes=True,
|
318
|
+
)
|
276
319
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
None,
|
281
|
-
testcase,
|
282
|
-
output_dir,
|
283
|
-
interactor_digest=interactor_digest,
|
284
|
-
use_retries=False,
|
285
|
-
use_timelimit=False,
|
320
|
+
if eval.result.outcome != Outcome.ACCEPTED:
|
321
|
+
console.console.print(
|
322
|
+
f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
|
286
323
|
)
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
console.console.print(f'[error]Summary:[/error] {eval.log.get_summary()}')
|
298
|
-
console.console.print(
|
299
|
-
f'[warning]Verdict: [item]{eval.result.outcome.value}[/item][/warning]',
|
300
|
-
)
|
301
|
-
console.console.print(
|
302
|
-
f'[warning]Message: [info]{eval.result.message}[/info][/warning]',
|
303
|
-
)
|
304
|
-
console.console.print(f'Input written at [item]{testcase.inputPath}[/item]')
|
324
|
+
console.console.print(f'[error]Summary:[/error] {eval.log.get_summary()}')
|
325
|
+
console.console.print(
|
326
|
+
f'[warning]Verdict: [item]{eval.result.outcome.value}[/item][/warning]',
|
327
|
+
)
|
328
|
+
console.console.print(
|
329
|
+
f'[warning]Message: [info]{eval.result.message}[/info][/warning]',
|
330
|
+
)
|
331
|
+
console.console.print(f'Input written at [item]{testcase.inputPath}[/item]')
|
332
|
+
console.console.print(f'Output written at [item]{testcase.outputPath}[/item]')
|
333
|
+
if eval.log.stderr_absolute_path is not None:
|
305
334
|
console.console.print(
|
306
|
-
f'
|
335
|
+
f'Stderr written at [item]{eval.log.stderr_absolute_path}[/item]'
|
307
336
|
)
|
308
|
-
if stderr_path is not None:
|
309
|
-
console.console.print(f'Stderr written at [item]{stderr_path}[/item]')
|
310
337
|
|
311
|
-
|
338
|
+
raise typer.Exit(1)
|
312
339
|
|
313
340
|
|
314
341
|
async def generate_outputs_for_testcases(
|
@@ -350,6 +377,14 @@ async def generate_outputs_for_testcases(
|
|
350
377
|
return
|
351
378
|
assert tc.outputPath is not None
|
352
379
|
|
380
|
+
if entry.metadata.copied_from is not None and _copy_testcase_outputs_over(
|
381
|
+
entry.metadata.copied_from, tc
|
382
|
+
):
|
383
|
+
# Copy remaining pipe files.
|
384
|
+
_copy_testcase_outputs_over(entry.metadata.copied_from, tc, pipes=True)
|
385
|
+
step()
|
386
|
+
continue
|
387
|
+
|
353
388
|
if (
|
354
389
|
main_solution is None or solution_digest is None
|
355
390
|
) and not tc.outputPath.is_file():
|
@@ -362,7 +397,9 @@ async def generate_outputs_for_testcases(
|
|
362
397
|
await generate_output_for_testcase(
|
363
398
|
solution_digest,
|
364
399
|
tc,
|
365
|
-
gen_runs_dir / 'main.stderr',
|
366
400
|
interactor_digest=interactor_digest,
|
367
401
|
)
|
402
|
+
if entry.metadata.copied_from is not None:
|
403
|
+
# Copy remaining pipe files.
|
404
|
+
_copy_testcase_outputs_over(entry.metadata.copied_from, tc, pipes=True)
|
368
405
|
step()
|
@@ -10,6 +10,7 @@ from rbx.box import package
|
|
10
10
|
from rbx.box.environment import get_extension_or_default
|
11
11
|
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
|
12
12
|
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
13
|
+
from rbx.box.schema import TaskType
|
13
14
|
from rbx.box.statements.schema import Statement
|
14
15
|
from rbx.config import get_default_app_path, get_testlib
|
15
16
|
|
@@ -24,6 +25,10 @@ def test_time(time):
|
|
24
25
|
|
25
26
|
|
26
27
|
class BocaPackager(BasePackager):
|
28
|
+
@classmethod
|
29
|
+
def task_types(cls) -> List[TaskType]:
|
30
|
+
return [TaskType.BATCH, TaskType.COMMUNICATION]
|
31
|
+
|
27
32
|
def _get_main_statement(self) -> Statement:
|
28
33
|
pkg = package.find_problem_package_or_die()
|
29
34
|
|
@@ -119,7 +124,7 @@ class BocaPackager(BasePackager):
|
|
119
124
|
)
|
120
125
|
|
121
126
|
def _get_compare(self) -> str:
|
122
|
-
compare_path = get_default_app_path() / 'packagers' / 'boca' / 'compare'
|
127
|
+
compare_path = get_default_app_path() / 'packagers' / 'boca' / 'compare.sh'
|
123
128
|
if not compare_path.exists():
|
124
129
|
console.console.print(
|
125
130
|
'[error]BOCA template compare script not found.[/error]'
|
@@ -145,7 +150,26 @@ class BocaPackager(BasePackager):
|
|
145
150
|
.replace('{{checker_content}}', checker)
|
146
151
|
)
|
147
152
|
|
153
|
+
def _get_interactor(self) -> str:
|
154
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
155
|
+
|
156
|
+
interactor_path = (
|
157
|
+
get_default_app_path() / 'packagers' / 'boca' / 'interactor_compile.sh'
|
158
|
+
)
|
159
|
+
if not interactor_path.exists():
|
160
|
+
console.console.print(
|
161
|
+
'[error]BOCA template interactor compile script not found.[/error]'
|
162
|
+
)
|
163
|
+
raise typer.Exit(1)
|
164
|
+
|
165
|
+
interactor_text = interactor_path.read_text()
|
166
|
+
interactor = package.get_interactor().path.read_text()
|
167
|
+
return interactor_text.replace(
|
168
|
+
'{{rbxFlags}}', extension.flags_with_defaults()['cc']
|
169
|
+
).replace('{{interactor_content}}', interactor)
|
170
|
+
|
148
171
|
def _get_compile(self, language: BocaLanguage) -> str:
|
172
|
+
pkg = package.find_problem_package_or_die()
|
149
173
|
extension = get_extension_or_default('boca', BocaExtension)
|
150
174
|
|
151
175
|
compile_path = (
|
@@ -160,6 +184,10 @@ class BocaPackager(BasePackager):
|
|
160
184
|
compile_text = compile_path.read_text()
|
161
185
|
|
162
186
|
assert 'umask 0022' in compile_text
|
187
|
+
if pkg.type == TaskType.COMMUNICATION:
|
188
|
+
compile_text = compile_text.replace(
|
189
|
+
'umask 0022', 'umask 0022\n\n' + self._get_interactor()
|
190
|
+
)
|
163
191
|
compile_text = compile_text.replace(
|
164
192
|
'umask 0022', 'umask 0022\n\n' + self._get_checker()
|
165
193
|
)
|
@@ -197,7 +225,8 @@ class BocaPackager(BasePackager):
|
|
197
225
|
)
|
198
226
|
)
|
199
227
|
|
200
|
-
|
228
|
+
@classmethod
|
229
|
+
def name(cls) -> str:
|
201
230
|
return 'boca'
|
202
231
|
|
203
232
|
def package(
|
@@ -207,7 +236,7 @@ class BocaPackager(BasePackager):
|
|
207
236
|
built_statements: List[BuiltStatement],
|
208
237
|
) -> pathlib.Path:
|
209
238
|
extension = get_extension_or_default('boca', BocaExtension)
|
210
|
-
|
239
|
+
pkg = package.find_problem_package_or_die()
|
211
240
|
# Prepare limits
|
212
241
|
limits_path = into_path / 'limits'
|
213
242
|
limits_path.mkdir(parents=True, exist_ok=True)
|
@@ -227,9 +256,17 @@ class BocaPackager(BasePackager):
|
|
227
256
|
run_orig_path = (
|
228
257
|
get_default_app_path() / 'packagers' / 'boca' / 'run' / language
|
229
258
|
)
|
259
|
+
if pkg.type == TaskType.COMMUNICATION:
|
260
|
+
run_orig_path = (
|
261
|
+
get_default_app_path()
|
262
|
+
/ 'packagers'
|
263
|
+
/ 'boca'
|
264
|
+
/ 'interactive'
|
265
|
+
/ language
|
266
|
+
)
|
230
267
|
if not run_orig_path.is_file():
|
231
268
|
console.console.print(
|
232
|
-
f'[error]Run script for language [item]{language}[/item] not found.[/error]'
|
269
|
+
f'[error]Run script for language [item]{language}[/item] not found for task of type [item]{pkg.type}[/item].[/error]'
|
233
270
|
)
|
234
271
|
raise typer.Exit(1)
|
235
272
|
shutil.copyfile(run_orig_path, run_path / language)
|
@@ -268,11 +305,11 @@ class BocaPackager(BasePackager):
|
|
268
305
|
|
269
306
|
testcases = self.get_flattened_built_testcases()
|
270
307
|
for i, testcase in enumerate(testcases):
|
271
|
-
shutil.copyfile(testcase.inputPath, inputs_path / f'{i+1:03d}')
|
308
|
+
shutil.copyfile(testcase.inputPath, inputs_path / f'{i + 1:03d}')
|
272
309
|
if testcase.outputPath is not None:
|
273
|
-
shutil.copyfile(testcase.outputPath, outputs_path / f'{i+1:03d}')
|
310
|
+
shutil.copyfile(testcase.outputPath, outputs_path / f'{i + 1:03d}')
|
274
311
|
else:
|
275
|
-
(outputs_path / f'{i+1:03d}').touch()
|
312
|
+
(outputs_path / f'{i + 1:03d}').touch()
|
276
313
|
|
277
314
|
# Zip all.
|
278
315
|
shutil.make_archive(
|
rbx/box/packaging/main.py
CHANGED
@@ -28,6 +28,13 @@ async def run_packager(
|
|
28
28
|
raise typer.Exit(1)
|
29
29
|
|
30
30
|
pkg = package.find_problem_package_or_die()
|
31
|
+
|
32
|
+
if pkg.type not in packager_cls.task_types():
|
33
|
+
console.console.print(
|
34
|
+
f'[error]Packager [item]{packager_cls.name()}[/item] does not support task type [item]{pkg.type}[/item].[/error]'
|
35
|
+
)
|
36
|
+
raise typer.Exit(1)
|
37
|
+
|
31
38
|
packager = packager_cls(**kwargs)
|
32
39
|
|
33
40
|
statement_types = packager.statement_types()
|
@@ -7,10 +7,10 @@ import typer
|
|
7
7
|
from rbx import console
|
8
8
|
from rbx.box import package
|
9
9
|
from rbx.box.environment import get_extension_or_default
|
10
|
-
from rbx.box.packaging.boca.extension import BocaExtension
|
10
|
+
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
|
11
11
|
from rbx.box.packaging.boca.packager import BocaPackager
|
12
12
|
from rbx.box.packaging.packager import BuiltStatement
|
13
|
-
from rbx.box.schema import ExpectedOutcome
|
13
|
+
from rbx.box.schema import ExpectedOutcome, TaskType
|
14
14
|
from rbx.config import get_default_app_path, get_testlib
|
15
15
|
from rbx.grading.judge.digester import digest_cooperatively
|
16
16
|
|
@@ -20,6 +20,10 @@ class MojPackager(BocaPackager):
|
|
20
20
|
super().__init__()
|
21
21
|
self.for_boca = for_boca
|
22
22
|
|
23
|
+
@classmethod
|
24
|
+
def task_types(cls) -> List[TaskType]:
|
25
|
+
return [TaskType.COMMUNICATION, TaskType.BATCH]
|
26
|
+
|
23
27
|
def _get_problem_info(self) -> str:
|
24
28
|
statement = self._get_main_statement()
|
25
29
|
return (
|
@@ -40,8 +44,12 @@ class MojPackager(BocaPackager):
|
|
40
44
|
def _get_limits(self) -> str:
|
41
45
|
pkg = package.find_problem_package_or_die()
|
42
46
|
ml = pkg.memoryLimit
|
43
|
-
ol = pkg.outputLimit
|
44
|
-
|
47
|
+
# ol = pkg.outputLimit
|
48
|
+
limits = [
|
49
|
+
f'ULIMITS[-v]={ml * 1024}',
|
50
|
+
# f'ULIMITS[-f]={ol}',
|
51
|
+
]
|
52
|
+
return '\n'.join(limits) + '\n'
|
45
53
|
|
46
54
|
def _get_compare(self) -> str:
|
47
55
|
extension = get_extension_or_default('boca', BocaExtension)
|
@@ -65,6 +73,33 @@ class MojPackager(BocaPackager):
|
|
65
73
|
def _get_checker(self) -> str:
|
66
74
|
return package.get_checker().path.read_text()
|
67
75
|
|
76
|
+
def _get_interactor(self) -> str:
|
77
|
+
return package.get_interactor().path.read_text()
|
78
|
+
|
79
|
+
def _expand_language_vars(self, language: BocaLanguage, dir: pathlib.Path):
|
80
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
81
|
+
|
82
|
+
for path in dir.glob('**/*'):
|
83
|
+
if not path.is_file():
|
84
|
+
continue
|
85
|
+
|
86
|
+
replaced = path.read_text()
|
87
|
+
replaced = replaced.replace(
|
88
|
+
'{{rbxMaxMemory}}', f'{self._get_pkg_memorylimit(language)}'
|
89
|
+
).replace(
|
90
|
+
'{{rbxInitialMemory}}',
|
91
|
+
f'{min(512, int(self._get_pkg_memorylimit(language) * 0.9))}',
|
92
|
+
)
|
93
|
+
|
94
|
+
flags = extension.flags_with_defaults()
|
95
|
+
if language in flags:
|
96
|
+
replaced = replaced.replace('{{rbxFlags}}', flags[language])
|
97
|
+
|
98
|
+
path.write_text(replaced)
|
99
|
+
|
100
|
+
if path.suffix == '.sh':
|
101
|
+
path.chmod(0o755)
|
102
|
+
|
68
103
|
def _copy_solutions_moj(self, into_path: pathlib.Path):
|
69
104
|
into_path = into_path / 'sols'
|
70
105
|
has_good = False
|
@@ -83,7 +118,8 @@ class MojPackager(BocaPackager):
|
|
83
118
|
console.console.print('[error]No good solution found.[/error]')
|
84
119
|
raise typer.Exit(1)
|
85
120
|
|
86
|
-
|
121
|
+
@classmethod
|
122
|
+
def name(cls) -> str:
|
87
123
|
return 'moj'
|
88
124
|
|
89
125
|
def package(
|
@@ -92,6 +128,8 @@ class MojPackager(BocaPackager):
|
|
92
128
|
into_path: pathlib.Path,
|
93
129
|
built_statements: List[BuiltStatement],
|
94
130
|
) -> pathlib.Path:
|
131
|
+
pkg = package.find_problem_package_or_die()
|
132
|
+
|
95
133
|
# Prepare dummy files
|
96
134
|
author_path = into_path / 'author'
|
97
135
|
author_path.parent.mkdir(parents=True, exist_ok=True)
|
@@ -128,6 +166,48 @@ class MojPackager(BocaPackager):
|
|
128
166
|
checker_path.parent.mkdir(parents=True, exist_ok=True)
|
129
167
|
checker_path.write_text(self._get_checker())
|
130
168
|
|
169
|
+
# Prepare interactor
|
170
|
+
if pkg.type == TaskType.COMMUNICATION:
|
171
|
+
interactor_path = into_path / 'scripts' / 'interactor.cpp'
|
172
|
+
interactor_path.parent.mkdir(parents=True, exist_ok=True)
|
173
|
+
interactor_path.write_text(self._get_interactor())
|
174
|
+
|
175
|
+
interactor_prep_path = into_path / 'scripts' / 'interactor_prep.sh'
|
176
|
+
interactor_prep_path.parent.mkdir(parents=True, exist_ok=True)
|
177
|
+
shutil.copy(
|
178
|
+
get_default_app_path()
|
179
|
+
/ 'packagers'
|
180
|
+
/ 'moj'
|
181
|
+
/ 'scripts'
|
182
|
+
/ 'interactor_prep.sh',
|
183
|
+
interactor_prep_path,
|
184
|
+
)
|
185
|
+
interactor_prep_path.chmod(0o755)
|
186
|
+
|
187
|
+
interactor_run_path = into_path / 'scripts' / 'interactor_run.sh'
|
188
|
+
interactor_run_path.parent.mkdir(parents=True, exist_ok=True)
|
189
|
+
shutil.copy(
|
190
|
+
get_default_app_path()
|
191
|
+
/ 'packagers'
|
192
|
+
/ 'moj'
|
193
|
+
/ 'scripts'
|
194
|
+
/ 'interactor_run.sh',
|
195
|
+
interactor_run_path,
|
196
|
+
)
|
197
|
+
interactor_run_path.chmod(0o755)
|
198
|
+
|
199
|
+
# Prepare language scripts
|
200
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
201
|
+
for language in extension.languages:
|
202
|
+
language_path = into_path / 'scripts' / language
|
203
|
+
language_path.parent.mkdir(parents=True, exist_ok=True)
|
204
|
+
src_path = (
|
205
|
+
get_default_app_path() / 'packagers' / 'moj' / 'scripts' / language
|
206
|
+
)
|
207
|
+
if src_path.exists():
|
208
|
+
shutil.copytree(src_path, language_path)
|
209
|
+
self._expand_language_vars(language, language_path)
|
210
|
+
|
131
211
|
# Problem statement
|
132
212
|
enunciado_path = into_path / 'docs' / 'enunciado.pdf'
|
133
213
|
enunciado_path.parent.mkdir(parents=True, exist_ok=True)
|
@@ -150,11 +230,11 @@ class MojPackager(BocaPackager):
|
|
150
230
|
|
151
231
|
testcases = self.get_flattened_built_testcases()
|
152
232
|
for i, testcase in enumerate(testcases):
|
153
|
-
shutil.copyfile(testcase.inputPath, inputs_path / f'{i+1:03d}')
|
233
|
+
shutil.copyfile(testcase.inputPath, inputs_path / f'{i + 1:03d}')
|
154
234
|
if testcase.outputPath is not None:
|
155
|
-
shutil.copyfile(testcase.outputPath, outputs_path / f'{i+1:03d}')
|
235
|
+
shutil.copyfile(testcase.outputPath, outputs_path / f'{i + 1:03d}')
|
156
236
|
else:
|
157
|
-
(outputs_path / f'{i+1:03d}').touch()
|
237
|
+
(outputs_path / f'{i + 1:03d}').touch()
|
158
238
|
|
159
239
|
# Zip all.
|
160
240
|
shutil.make_archive(
|