rbx.cp 0.13.4__py3-none-any.whl → 0.13.6__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 +2 -9
- rbx/box/cli.py +0 -1
- rbx/box/code.py +27 -80
- rbx/box/environment.py +16 -6
- rbx/box/generators.py +26 -3
- rbx/box/global_package.py +1 -1
- rbx/box/header.py +26 -8
- rbx/box/package.py +0 -14
- rbx/box/setter_config.py +11 -0
- rbx/box/solutions.py +12 -4
- rbx/box/tasks.py +9 -4
- rbx/box/testing/testing_package.py +69 -2
- 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/unit.py +4 -4
- rbx/box/validators.py +3 -1
- rbx/grading/caching.py +65 -15
- rbx/grading/judge/cacher.py +5 -3
- rbx/grading/judge/program.py +300 -0
- rbx/grading/judge/sandbox.py +30 -200
- rbx/grading/judge/sandboxes/stupid_sandbox.py +234 -240
- rbx/grading/judge/sandboxes/tee.py +31 -0
- rbx/grading/judge/storage.py +7 -1
- rbx/grading/steps.py +89 -201
- rbx/grading/steps_with_caching.py +15 -6
- rbx/resources/presets/default/problem/problem.rbx.yml +0 -2
- rbx/resources/templates/rbx.h +43 -2
- rbx/testing_utils.py +7 -0
- rbx/utils.py +104 -6
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/METADATA +1 -1
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/RECORD +35 -40
- rbx/grading/judge/sandboxes/isolate.py +0 -695
- rbx/grading/judge/sandboxes/timeit.py +0 -358
- rbx/grading/judge/test.py +0 -38
- rbx/grading/judge/testiso.py +0 -54
- rbx/grading/processing_context.py +0 -71
- rbx/resources/envs/isolate.rbx.yml +0 -36
- rbx/resources/presets/default/problem/sols/slow.cpp +0 -15
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/LICENSE +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/WHEEL +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/entry_points.txt +0 -0
rbx/grading/judge/sandbox.py
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
import abc
|
2
2
|
import asyncio
|
3
|
-
import collections
|
4
3
|
import dataclasses
|
5
4
|
import io
|
5
|
+
import json
|
6
6
|
import logging
|
7
7
|
import os
|
8
8
|
import pathlib
|
9
9
|
import select
|
10
|
-
import signal
|
11
10
|
import stat
|
12
11
|
import subprocess
|
13
12
|
import sys
|
14
13
|
import typing
|
15
|
-
from typing import IO, Any, Dict, List, Optional
|
14
|
+
from typing import IO, Any, Dict, List, Optional, Tuple
|
16
15
|
|
17
16
|
import pydantic
|
18
17
|
|
@@ -146,12 +145,6 @@ class SandboxParams(pydantic.BaseModel):
|
|
146
145
|
reverse_io: bool = False
|
147
146
|
pgid: Optional[int] = None
|
148
147
|
|
149
|
-
# For timeit
|
150
|
-
timeit_dups: Dict[str, List[pathlib.Path]] = dataclasses.field(
|
151
|
-
default_factory=lambda: collections.defaultdict(list)
|
152
|
-
)
|
153
|
-
timeit_prefix: Optional[str] = None
|
154
|
-
|
155
148
|
def get_cacheable_params(self) -> Dict[str, Any]:
|
156
149
|
return self.model_dump(mode='json', exclude_unset=True, exclude_none=True)
|
157
150
|
|
@@ -210,6 +203,22 @@ class SandboxParams(pydantic.BaseModel):
|
|
210
203
|
self.dirs.append(DirectoryMount(src, dest, options))
|
211
204
|
|
212
205
|
|
206
|
+
class SandboxLog(pydantic.BaseModel):
|
207
|
+
"""A log of the sandbox."""
|
208
|
+
|
209
|
+
params: SandboxParams
|
210
|
+
execution_time: float # seconds
|
211
|
+
memory_used: int # bytes
|
212
|
+
exitcode: int
|
213
|
+
exitstatus: str
|
214
|
+
killing_signal: Optional[int] = None
|
215
|
+
exit_index: int = 0
|
216
|
+
other_logs: Dict[str, Any] = pydantic.Field(default_factory=dict)
|
217
|
+
|
218
|
+
def dump_other_logs(self) -> str:
|
219
|
+
return json.dumps(self.other_logs)
|
220
|
+
|
221
|
+
|
213
222
|
class SandboxBase(abc.ABC):
|
214
223
|
"""A base class for all sandboxes, meant to contain common
|
215
224
|
resources.
|
@@ -229,16 +238,12 @@ class SandboxBase(abc.ABC):
|
|
229
238
|
file_cacher: cacher.FileCacher
|
230
239
|
name: str
|
231
240
|
temp_dir: Optional[pathlib.Path]
|
232
|
-
cmd_file: pathlib.Path
|
233
|
-
|
234
|
-
params: SandboxParams
|
235
241
|
|
236
242
|
def __init__(
|
237
243
|
self,
|
238
244
|
file_cacher: Optional[cacher.FileCacher] = None,
|
239
245
|
name: Optional[str] = None,
|
240
246
|
temp_dir: Optional[pathlib.Path] = None,
|
241
|
-
params: Optional[SandboxParams] = None,
|
242
247
|
):
|
243
248
|
"""Initialization.
|
244
249
|
|
@@ -253,56 +258,7 @@ class SandboxBase(abc.ABC):
|
|
253
258
|
self.file_cacher = file_cacher or cacher.FileCacher(storage.NullStorage())
|
254
259
|
self.name = name if name is not None else 'unnamed'
|
255
260
|
self.temp_dir = temp_dir
|
256
|
-
|
257
|
-
self.cmd_file = pathlib.PosixPath('commands.log')
|
258
|
-
|
259
|
-
self.params = params or SandboxParams()
|
260
|
-
self.pid = None
|
261
|
-
self.pid_event = Event_ts()
|
262
|
-
|
263
|
-
# Set common environment variables.
|
264
|
-
# Specifically needed by Python, that searches the home for
|
265
|
-
# packages.
|
266
|
-
self.params.set_env['HOME'] = './'
|
267
|
-
|
268
|
-
def set_params(self, params: SandboxParams):
|
269
|
-
"""Set the parameters of the sandbox.
|
270
|
-
|
271
|
-
params (SandboxParams): the parameters to set.
|
272
|
-
|
273
|
-
"""
|
274
|
-
self.params = params
|
275
|
-
|
276
|
-
def set_multiprocess(self, multiprocess: bool):
|
277
|
-
"""Set the sandbox to (dis-)allow multiple threads and processes.
|
278
|
-
|
279
|
-
multiprocess (bool): whether to allow multiple thread/processes or not.
|
280
|
-
|
281
|
-
"""
|
282
|
-
if multiprocess:
|
283
|
-
# Max processes is set to 1000 to limit the effect of fork bombs.
|
284
|
-
self.params.max_processes = 1000
|
285
|
-
else:
|
286
|
-
self.params.max_processes = 1
|
287
|
-
|
288
|
-
def get_stats(self) -> str:
|
289
|
-
"""Return a human-readable string representing execution time
|
290
|
-
and memory usage.
|
291
|
-
|
292
|
-
return (string): human-readable stats.
|
293
|
-
|
294
|
-
"""
|
295
|
-
execution_time = self.get_execution_time()
|
296
|
-
if execution_time is not None:
|
297
|
-
time_str = f'{execution_time:.3f} sec'
|
298
|
-
else:
|
299
|
-
time_str = '(time unknown)'
|
300
|
-
memory_used = self.get_memory_used()
|
301
|
-
if memory_used is not None:
|
302
|
-
mem_str = f'{memory_used / (1024 * 1024):.2f} MB'
|
303
|
-
else:
|
304
|
-
mem_str = '(memory usage unknown)'
|
305
|
-
return f'[{time_str} - {mem_str}]'
|
261
|
+
self.cmd_file = pathlib.PosixPath('__commands__.log')
|
306
262
|
|
307
263
|
@abc.abstractmethod
|
308
264
|
def get_root_path(self) -> pathlib.Path:
|
@@ -313,101 +269,6 @@ class SandboxBase(abc.ABC):
|
|
313
269
|
"""
|
314
270
|
pass
|
315
271
|
|
316
|
-
@abc.abstractmethod
|
317
|
-
def get_execution_time(self) -> Optional[float]:
|
318
|
-
"""Return the time spent in the sandbox.
|
319
|
-
|
320
|
-
return (float): time spent in the sandbox.
|
321
|
-
|
322
|
-
"""
|
323
|
-
pass
|
324
|
-
|
325
|
-
@abc.abstractmethod
|
326
|
-
def get_memory_used(self) -> Optional[int]:
|
327
|
-
"""Return the memory used by the sandbox.
|
328
|
-
|
329
|
-
return (int): memory used by the sandbox (in bytes).
|
330
|
-
|
331
|
-
"""
|
332
|
-
pass
|
333
|
-
|
334
|
-
@abc.abstractmethod
|
335
|
-
def get_killing_signal(self) -> int:
|
336
|
-
"""Return the signal that killed the sandboxed process.
|
337
|
-
|
338
|
-
return (int): offending signal, or 0.
|
339
|
-
|
340
|
-
"""
|
341
|
-
pass
|
342
|
-
|
343
|
-
@abc.abstractmethod
|
344
|
-
def get_exit_status(self) -> str:
|
345
|
-
"""Get information about how the sandbox terminated.
|
346
|
-
|
347
|
-
return (string): the main reason why the sandbox terminated.
|
348
|
-
|
349
|
-
"""
|
350
|
-
pass
|
351
|
-
|
352
|
-
@abc.abstractmethod
|
353
|
-
def get_exit_code(self) -> int:
|
354
|
-
"""Return the exit code of the sandboxed process.
|
355
|
-
|
356
|
-
return (int): exitcode, or 0.
|
357
|
-
|
358
|
-
"""
|
359
|
-
pass
|
360
|
-
|
361
|
-
def set_pid(self, pid: int):
|
362
|
-
"""Set the PID of the sandboxed process.
|
363
|
-
|
364
|
-
pid (int): the PID of the sandboxed process.
|
365
|
-
|
366
|
-
"""
|
367
|
-
self.pid = pid
|
368
|
-
self.pid_event.set()
|
369
|
-
|
370
|
-
async def get_pid(self) -> int:
|
371
|
-
"""Return the PID of the sandboxed process.
|
372
|
-
|
373
|
-
Blocks until the PID is set.
|
374
|
-
|
375
|
-
return (int): the PID of the sandboxed process.
|
376
|
-
|
377
|
-
"""
|
378
|
-
await self.pid_event.wait()
|
379
|
-
assert self.pid is not None
|
380
|
-
return self.pid
|
381
|
-
|
382
|
-
def clear_pid(self):
|
383
|
-
"""Clear the PID of the sandboxed process."""
|
384
|
-
self.pid_event.clear()
|
385
|
-
self.pid = None
|
386
|
-
|
387
|
-
def use_pgid(self) -> bool:
|
388
|
-
"""Whether the sandbox supports process groups."""
|
389
|
-
return False
|
390
|
-
|
391
|
-
@abc.abstractmethod
|
392
|
-
def get_detailed_logs(self) -> str:
|
393
|
-
"""Return the detailed logs of the sandbox.
|
394
|
-
|
395
|
-
return (string): the detailed logs of the sandbox.
|
396
|
-
|
397
|
-
"""
|
398
|
-
pass
|
399
|
-
|
400
|
-
@abc.abstractmethod
|
401
|
-
def get_human_exit_description(self) -> str:
|
402
|
-
"""Get the status of the sandbox and return a human-readable
|
403
|
-
string describing it.
|
404
|
-
|
405
|
-
return (string): human-readable explaination of why the
|
406
|
-
sandbox terminated.
|
407
|
-
|
408
|
-
"""
|
409
|
-
pass
|
410
|
-
|
411
272
|
def use_soft_timeout(self) -> bool:
|
412
273
|
return False
|
413
274
|
|
@@ -702,52 +563,24 @@ class SandboxBase(abc.ABC):
|
|
702
563
|
]
|
703
564
|
|
704
565
|
@abc.abstractmethod
|
705
|
-
def
|
566
|
+
def run(
|
706
567
|
self,
|
707
568
|
command: List[str],
|
708
|
-
|
709
|
-
|
710
|
-
subprocess.Popen and discarding standard input, output and
|
711
|
-
error. More specifically, the standard input gets closed just
|
712
|
-
after the execution has started; standard output and error are
|
713
|
-
read until the end, in a way that prevents the execution from
|
714
|
-
being blocked because of insufficient buffering.
|
715
|
-
|
716
|
-
command ([string]): executable filename and arguments of the
|
717
|
-
command.
|
718
|
-
wait (bool): True if this call is blocking, False otherwise
|
719
|
-
|
720
|
-
return (bool|Popen): if the call is blocking, then return True
|
721
|
-
if the sandbox didn't report errors (caused by the sandbox
|
722
|
-
itself), False otherwise; if the call is not blocking,
|
723
|
-
return the Popen object from subprocess.
|
724
|
-
|
725
|
-
"""
|
569
|
+
params: SandboxParams,
|
570
|
+
) -> SandboxLog:
|
726
571
|
pass
|
727
572
|
|
728
573
|
@abc.abstractmethod
|
729
|
-
def
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
574
|
+
def run_communication(
|
575
|
+
self,
|
576
|
+
command: List[str],
|
577
|
+
params: SandboxParams,
|
578
|
+
interactor_command: List[str],
|
579
|
+
interactor_params: SandboxParams,
|
580
|
+
merged_capture: Optional[pathlib.Path] = None,
|
581
|
+
) -> Tuple[SandboxLog, SandboxLog]:
|
735
582
|
pass
|
736
583
|
|
737
|
-
def translate_box_exitcode(self, exitcode: int) -> bool:
|
738
|
-
"""Translate the sandbox exit code to a boolean sandbox success.
|
739
|
-
|
740
|
-
_ (int): the exit code of the sandbox.
|
741
|
-
|
742
|
-
return (bool): False if the sandbox had an error, True if it
|
743
|
-
terminated correctly (regardless of what the internal process
|
744
|
-
did).
|
745
|
-
|
746
|
-
"""
|
747
|
-
# SIGTERM can be safely ignored, just in case it leaks away from
|
748
|
-
# the sandbox.
|
749
|
-
return exitcode == 0 or exitcode == -signal.SIGTERM
|
750
|
-
|
751
584
|
@abc.abstractmethod
|
752
585
|
def initialize(self):
|
753
586
|
"""Initialize the sandbox.
|
@@ -779,9 +612,6 @@ class SandboxBase(abc.ABC):
|
|
779
612
|
"""
|
780
613
|
pass
|
781
614
|
|
782
|
-
def debug_message(self) -> Any:
|
783
|
-
return 'N/A'
|
784
|
-
|
785
615
|
|
786
616
|
class Truncator(io.RawIOBase):
|
787
617
|
"""Wrap a file-like object to simulate truncation.
|