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
@@ -9,17 +9,42 @@ import signal
|
|
9
9
|
import subprocess
|
10
10
|
import sys
|
11
11
|
import tempfile
|
12
|
-
|
12
|
+
import typing
|
13
|
+
from typing import List, Optional, Tuple
|
13
14
|
|
14
15
|
from rbx import utils
|
15
16
|
from rbx.grading.judge.cacher import FileCacher
|
17
|
+
from rbx.grading.judge.program import (
|
18
|
+
FileLike,
|
19
|
+
Program,
|
20
|
+
ProgramCode,
|
21
|
+
ProgramIO,
|
22
|
+
ProgramParams,
|
23
|
+
ProgramResult,
|
24
|
+
)
|
16
25
|
from rbx.grading.judge.sandbox import (
|
17
26
|
SandboxBase,
|
27
|
+
SandboxLog,
|
18
28
|
SandboxParams,
|
19
29
|
)
|
20
30
|
|
21
31
|
logger = logging.getLogger(__name__)
|
22
32
|
|
33
|
+
TEE_CODE = R"""
|
34
|
+
import sys
|
35
|
+
c = sys.argv[1]
|
36
|
+
new = True
|
37
|
+
while True:
|
38
|
+
l = sys.stdin.read(1)
|
39
|
+
if l=='': break
|
40
|
+
sys.stdout.write(l)
|
41
|
+
sys.stdout.flush()
|
42
|
+
if new: sys.stderr.write(c)
|
43
|
+
sys.stderr.write(l)
|
44
|
+
sys.stderr.flush()
|
45
|
+
new = l=='\n'
|
46
|
+
"""
|
47
|
+
|
23
48
|
|
24
49
|
class StupidSandbox(SandboxBase):
|
25
50
|
"""A stupid sandbox implementation. It has very few features and
|
@@ -31,16 +56,12 @@ class StupidSandbox(SandboxBase):
|
|
31
56
|
"""
|
32
57
|
|
33
58
|
exec_num: int
|
34
|
-
popen: Optional[subprocess.Popen]
|
35
|
-
returncode: Optional[int]
|
36
|
-
log: Optional[Dict[str, str]]
|
37
59
|
|
38
60
|
def __init__(
|
39
61
|
self,
|
40
62
|
file_cacher: Optional[FileCacher] = None,
|
41
63
|
name: Optional[str] = None,
|
42
64
|
temp_dir: Optional[pathlib.Path] = None,
|
43
|
-
params: Optional[SandboxParams] = None,
|
44
65
|
):
|
45
66
|
"""Initialization.
|
46
67
|
|
@@ -49,7 +70,7 @@ class StupidSandbox(SandboxBase):
|
|
49
70
|
"""
|
50
71
|
if not temp_dir:
|
51
72
|
temp_dir = pathlib.Path(tempfile.gettempdir())
|
52
|
-
SandboxBase.__init__(self, file_cacher, name, temp_dir
|
73
|
+
SandboxBase.__init__(self, file_cacher, name, temp_dir)
|
53
74
|
|
54
75
|
# Make box directory
|
55
76
|
self.initialize()
|
@@ -68,56 +89,6 @@ class StupidSandbox(SandboxBase):
|
|
68
89
|
# Box parameters
|
69
90
|
self.chdir = self._path
|
70
91
|
|
71
|
-
def get_timeit_executable(self) -> pathlib.Path:
|
72
|
-
with importlib.resources.as_file(
|
73
|
-
importlib.resources.files('rbx')
|
74
|
-
/ 'grading'
|
75
|
-
/ 'judge'
|
76
|
-
/ 'sandboxes'
|
77
|
-
/ 'timeit.py'
|
78
|
-
) as file:
|
79
|
-
return file
|
80
|
-
|
81
|
-
def get_timeit_args(self) -> List[str]:
|
82
|
-
args = []
|
83
|
-
if self.params.timeout:
|
84
|
-
timeout_in_s = self.params.timeout / 1000
|
85
|
-
if self.params.extra_timeout:
|
86
|
-
timeout_in_s += self.params.extra_timeout / 1000
|
87
|
-
args.append(f'-t{timeout_in_s:.3f}')
|
88
|
-
if self.params.wallclock_timeout:
|
89
|
-
walltimeout_in_s = self.params.wallclock_timeout / 1000
|
90
|
-
args.append(f'-w{walltimeout_in_s:.3f}')
|
91
|
-
if self.params.address_space:
|
92
|
-
args.append(f'-m{self.params.address_space}')
|
93
|
-
if self.params.fsize:
|
94
|
-
args.append(f'-f{self.params.fsize}')
|
95
|
-
if self.chdir:
|
96
|
-
args.append(f'-c{self.chdir}')
|
97
|
-
if self.use_pgid() and self.params.pgid is not None:
|
98
|
-
args.append(f'-g{self.params.pgid}')
|
99
|
-
|
100
|
-
file_args = []
|
101
|
-
if self.params.stdin_file:
|
102
|
-
file_args.append(f'-i{self.params.stdin_file}')
|
103
|
-
if self.params.stdout_file:
|
104
|
-
file_args.append(f'-o{self.params.stdout_file}')
|
105
|
-
if self.params.stderr_file:
|
106
|
-
file_args.append(f'-e{self.params.stderr_file}')
|
107
|
-
if self.params.reverse_io:
|
108
|
-
file_args.reverse()
|
109
|
-
args.extend(file_args)
|
110
|
-
|
111
|
-
if self.params.timeit_dups:
|
112
|
-
for i, files in self.params.timeit_dups.items():
|
113
|
-
assert i.lower() in ['di', 'do', 'de']
|
114
|
-
for file in files:
|
115
|
-
args.append(f'-{i}{file}')
|
116
|
-
if self.params.timeit_prefix:
|
117
|
-
args.append(f'-P{self.params.timeit_prefix}')
|
118
|
-
|
119
|
-
return args
|
120
|
-
|
121
92
|
def get_root_path(self) -> pathlib.Path:
|
122
93
|
"""Return the toplevel path of the sandbox.
|
123
94
|
|
@@ -126,171 +97,153 @@ class StupidSandbox(SandboxBase):
|
|
126
97
|
"""
|
127
98
|
return self._path
|
128
99
|
|
129
|
-
def get_execution_time(self) -> Optional[float]:
|
130
|
-
"""Return the time spent in the sandbox.
|
131
|
-
|
132
|
-
return (float): time spent in the sandbox.
|
133
|
-
|
134
|
-
"""
|
135
|
-
if self.log is None:
|
136
|
-
return None
|
137
|
-
return float(self.log['time'])
|
138
|
-
|
139
|
-
def get_execution_wall_clock_time(self) -> Optional[float]:
|
140
|
-
"""Return the total time from the start of the sandbox to the
|
141
|
-
conclusion of the task.
|
142
|
-
|
143
|
-
return (float): total time the sandbox was alive.
|
144
|
-
|
145
|
-
"""
|
146
|
-
if self.log is None:
|
147
|
-
return None
|
148
|
-
return float(self.log['time-wall'])
|
149
|
-
|
150
100
|
def use_soft_timeout(self) -> bool:
|
151
101
|
return True
|
152
102
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
if
|
163
|
-
return
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
103
|
+
def _get_exit_status(self, result: ProgramResult) -> str:
|
104
|
+
if ProgramCode.TE in result.program_codes:
|
105
|
+
return SandboxBase.EXIT_TERMINATED
|
106
|
+
if ProgramCode.WT in result.program_codes:
|
107
|
+
return SandboxBase.EXIT_TIMEOUT_WALL
|
108
|
+
if ProgramCode.TO in result.program_codes:
|
109
|
+
return SandboxBase.EXIT_TIMEOUT
|
110
|
+
if ProgramCode.OL in result.program_codes:
|
111
|
+
return SandboxBase.EXIT_OUTPUT_LIMIT_EXCEEDED
|
112
|
+
if ProgramCode.ML in result.program_codes:
|
113
|
+
return SandboxBase.EXIT_MEMORY_LIMIT_EXCEEDED
|
114
|
+
if ProgramCode.SG in result.program_codes:
|
115
|
+
return SandboxBase.EXIT_SIGNAL
|
116
|
+
if ProgramCode.RE in result.program_codes:
|
117
|
+
return SandboxBase.EXIT_NONZERO_RETURN
|
118
|
+
return SandboxBase.EXIT_OK
|
119
|
+
|
120
|
+
def _get_io(self, params: SandboxParams, pipe_io: bool = False) -> ProgramIO:
|
121
|
+
io = ProgramIO()
|
122
|
+
if params.stdin_file and not pipe_io:
|
123
|
+
io.input = self.relative_path(params.stdin_file)
|
124
|
+
if params.stdout_file and not pipe_io:
|
125
|
+
io.output = self.relative_path(params.stdout_file)
|
126
|
+
if params.stderr_file:
|
127
|
+
io.stderr = self.relative_path(params.stderr_file)
|
128
|
+
return io
|
129
|
+
|
130
|
+
def _get_program_params(self, params: SandboxParams) -> ProgramParams:
|
131
|
+
return ProgramParams(
|
132
|
+
chdir=self.chdir,
|
133
|
+
time_limit=params.timeout / 1000 if params.timeout else None,
|
134
|
+
wall_time_limit=params.wallclock_timeout / 1000
|
135
|
+
if params.wallclock_timeout
|
136
|
+
else None,
|
137
|
+
memory_limit=params.address_space,
|
138
|
+
fs_limit=params.fsize,
|
139
|
+
env=params.set_env,
|
140
|
+
io=self._get_io(params),
|
141
|
+
)
|
170
142
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
143
|
+
def _get_tee_program_params(self, io: ProgramIO, pgid: int) -> ProgramParams:
|
144
|
+
return ProgramParams(
|
145
|
+
chdir=self.chdir,
|
146
|
+
time_limit=None,
|
147
|
+
wall_time_limit=None,
|
148
|
+
memory_limit=None,
|
149
|
+
io=io,
|
150
|
+
pgid=pgid,
|
151
|
+
)
|
176
152
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
153
|
+
def _get_sandbox_log(
|
154
|
+
self, result: ProgramResult, params: SandboxParams
|
155
|
+
) -> SandboxLog:
|
156
|
+
return SandboxLog(
|
157
|
+
params=params.model_copy(deep=True),
|
158
|
+
execution_time=result.wall_time,
|
159
|
+
memory_used=result.memory_used,
|
160
|
+
exitcode=result.exitcode,
|
161
|
+
exitstatus=self._get_exit_status(result),
|
162
|
+
killing_signal=result.killing_signal,
|
163
|
+
other_logs={
|
164
|
+
'program_codes': [code.value for code in result.program_codes],
|
165
|
+
'alarm_msg': result.alarm_msg,
|
166
|
+
},
|
167
|
+
)
|
180
168
|
|
181
|
-
|
169
|
+
def _needs_teeing(
|
170
|
+
self,
|
171
|
+
params: SandboxParams,
|
172
|
+
interactor_params: SandboxParams,
|
173
|
+
merged_capture: Optional[pathlib.Path] = None,
|
174
|
+
) -> bool:
|
175
|
+
return (
|
176
|
+
params.stdout_file is not None
|
177
|
+
or interactor_params.stdout_file is not None
|
178
|
+
or merged_capture is not None
|
179
|
+
)
|
182
180
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
181
|
+
def _get_tee_executable(self) -> pathlib.Path:
|
182
|
+
with importlib.resources.as_file(
|
183
|
+
importlib.resources.files('rbx')
|
184
|
+
/ 'grading'
|
185
|
+
/ 'judge'
|
186
|
+
/ 'sandboxes'
|
187
|
+
/ 'tee.py'
|
188
|
+
) as file:
|
189
|
+
return file
|
188
190
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
191
|
+
def _get_tee_command(self, char: str, extra: Optional[str] = None) -> List[str]:
|
192
|
+
return [
|
193
|
+
sys.executable,
|
194
|
+
str(utils.abspath(self._get_tee_executable())),
|
195
|
+
char,
|
196
|
+
extra or '/dev/null',
|
197
|
+
]
|
195
198
|
|
196
|
-
|
199
|
+
def _get_tee_program(
|
200
|
+
self,
|
201
|
+
char: str,
|
202
|
+
stdin: FileLike,
|
203
|
+
stdout: FileLike,
|
204
|
+
pgid: int,
|
205
|
+
capture: Optional[pathlib.Path] = None,
|
206
|
+
merged_capture: Optional[pathlib.Path] = None,
|
207
|
+
) -> Program:
|
208
|
+
io = ProgramIO(input=stdin, output=stdout, stderr=subprocess.DEVNULL)
|
209
|
+
if merged_capture:
|
210
|
+
io.stderr = self.relative_path(merged_capture).open('ab')
|
211
|
+
return Program(
|
212
|
+
self._get_tee_command(
|
213
|
+
char, str(self.relative_path(capture)) if capture else None
|
214
|
+
),
|
215
|
+
self._get_tee_program_params(io, pgid),
|
216
|
+
)
|
197
217
|
|
198
|
-
|
199
|
-
if
|
200
|
-
|
201
|
-
|
202
|
-
return self.EXIT_SANDBOX_ERROR
|
203
|
-
status_list = self.get_status_list()
|
204
|
-
if 'TE' in status_list:
|
205
|
-
return self.EXIT_TERMINATED
|
206
|
-
if 'WT' in status_list:
|
207
|
-
return self.EXIT_TIMEOUT_WALL
|
208
|
-
if 'TO' in status_list:
|
209
|
-
return self.EXIT_TIMEOUT
|
210
|
-
if 'OL' in status_list:
|
211
|
-
return self.EXIT_OUTPUT_LIMIT_EXCEEDED
|
212
|
-
if 'ML' in status_list:
|
213
|
-
return self.EXIT_MEMORY_LIMIT_EXCEEDED
|
214
|
-
if 'SG' in status_list:
|
215
|
-
return self.EXIT_SIGNAL
|
216
|
-
if 'RE' in status_list:
|
217
|
-
return self.EXIT_NONZERO_RETURN
|
218
|
-
return self.EXIT_OK
|
219
|
-
|
220
|
-
def get_exit_code(self) -> int:
|
221
|
-
"""Return the exit code of the sandboxed process.
|
222
|
-
|
223
|
-
return (float): exitcode, or 0.
|
218
|
+
def _get_pathlike_stdout(self, io: ProgramIO) -> Optional[pathlib.Path]:
|
219
|
+
if isinstance(io.output, str) or isinstance(io.output, pathlib.Path):
|
220
|
+
return pathlib.Path(io.output)
|
221
|
+
return None
|
224
222
|
|
225
|
-
|
226
|
-
|
227
|
-
return int(self.log['exit-code'])
|
223
|
+
def run(self, command: List[str], params: SandboxParams) -> SandboxLog:
|
224
|
+
self.exec_num += 1
|
228
225
|
|
229
|
-
|
230
|
-
|
226
|
+
logger.debug(
|
227
|
+
"Executing program in sandbox with command: `%s'.", ' '.join(command)
|
228
|
+
)
|
229
|
+
with open(
|
230
|
+
self.relative_path(self.cmd_file), 'at', encoding='utf-8'
|
231
|
+
) as commands:
|
232
|
+
commands.write('%s\n' % command)
|
231
233
|
|
232
|
-
|
233
|
-
|
234
|
-
string describing it.
|
234
|
+
program = Program(command, self._get_program_params(params))
|
235
|
+
result = program.wait()
|
235
236
|
|
236
|
-
return (
|
237
|
-
sandbox terminated.
|
237
|
+
return self._get_sandbox_log(result, params)
|
238
238
|
|
239
|
-
|
240
|
-
status = self.get_exit_status()
|
241
|
-
if status == self.EXIT_OK:
|
242
|
-
return (
|
243
|
-
'Execution successfully finished (with exit code %d)'
|
244
|
-
% self.get_exit_code()
|
245
|
-
)
|
246
|
-
elif status == self.EXIT_SANDBOX_ERROR:
|
247
|
-
return 'Execution failed because of sandbox error'
|
248
|
-
elif status == self.EXIT_TIMEOUT:
|
249
|
-
return 'Execution timed out'
|
250
|
-
elif status == self.EXIT_TIMEOUT_WALL:
|
251
|
-
return 'Execution timed out (wall clock limit exceeded)'
|
252
|
-
elif status == self.EXIT_SIGNAL:
|
253
|
-
return 'Execution killed with signal %s' % self.get_killing_signal()
|
254
|
-
elif status == self.EXIT_NONZERO_RETURN:
|
255
|
-
return 'Execution failed because the return code was nonzero'
|
256
|
-
elif status == self.EXIT_OUTPUT_LIMIT_EXCEEDED:
|
257
|
-
return 'Execution exceeded output limit'
|
258
|
-
return ''
|
259
|
-
|
260
|
-
def get_current_log_name(self) -> pathlib.Path:
|
261
|
-
return pathlib.Path(f'logs.{self.exec_num}')
|
262
|
-
|
263
|
-
def hydrate_logs(self):
|
264
|
-
self.log = None
|
265
|
-
if not self.relative_path(self.get_current_log_name()).is_file():
|
266
|
-
return
|
267
|
-
self.log = {}
|
268
|
-
raw_log = self.get_file_to_string(self.get_current_log_name(), maxlen=None)
|
269
|
-
for line in raw_log.splitlines():
|
270
|
-
items = line.split(':', 1)
|
271
|
-
if len(items) != 2:
|
272
|
-
continue
|
273
|
-
key, value = items
|
274
|
-
self.log[key] = value.strip()
|
275
|
-
|
276
|
-
def execute_without_std(
|
239
|
+
def run_communication(
|
277
240
|
self,
|
278
241
|
command: List[str],
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
read until the end, in a way that prevents the execution from
|
285
|
-
being blocked because of insufficient buffering.
|
286
|
-
|
287
|
-
command ([string]): executable filename and arguments of the
|
288
|
-
command.
|
289
|
-
|
290
|
-
return (bool): True if the sandbox didn't report errors
|
291
|
-
(caused by the sandbox itself), False otherwise
|
292
|
-
|
293
|
-
"""
|
242
|
+
params: SandboxParams,
|
243
|
+
interactor_command: List[str],
|
244
|
+
interactor_params: SandboxParams,
|
245
|
+
merged_capture: Optional[pathlib.Path] = None,
|
246
|
+
) -> Tuple[SandboxLog, SandboxLog]:
|
294
247
|
self.exec_num += 1
|
295
248
|
|
296
249
|
logger.debug(
|
@@ -301,43 +254,84 @@ class StupidSandbox(SandboxBase):
|
|
301
254
|
) as commands:
|
302
255
|
commands.write('%s\n' % command)
|
303
256
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
]
|
310
|
-
+ self.get_timeit_args()
|
311
|
-
+ command
|
257
|
+
interactor_program_params = self._get_program_params(interactor_params)
|
258
|
+
interactor_program_params.io = self._get_io(interactor_params, pipe_io=True)
|
259
|
+
interactor = Program(
|
260
|
+
interactor_command,
|
261
|
+
interactor_program_params,
|
312
262
|
)
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
263
|
+
assert interactor.pipes.output is not None
|
264
|
+
assert interactor.pipes.input is not None
|
265
|
+
solution_input_pipe = interactor.pipes.output
|
266
|
+
solution_output_pipe = interactor.pipes.input
|
267
|
+
|
268
|
+
group_id = os.getpgid(interactor.pid)
|
269
|
+
should_tee = self._needs_teeing(params, interactor_params, merged_capture)
|
270
|
+
|
271
|
+
if should_tee:
|
272
|
+
if merged_capture:
|
273
|
+
self.create_file_from_string(merged_capture, '<\n>\n', override=True)
|
274
|
+
|
275
|
+
solution_tee = self._get_tee_program(
|
276
|
+
'>',
|
277
|
+
stdin=subprocess.PIPE,
|
278
|
+
stdout=interactor.pipes.input,
|
279
|
+
capture=self._get_pathlike_stdout(self._get_io(params)),
|
280
|
+
merged_capture=merged_capture,
|
281
|
+
pgid=group_id,
|
282
|
+
)
|
283
|
+
interactor_tee = self._get_tee_program(
|
284
|
+
'<',
|
285
|
+
stdin=interactor.pipes.output,
|
286
|
+
stdout=subprocess.PIPE,
|
287
|
+
capture=self._get_pathlike_stdout(self._get_io(interactor_params)),
|
288
|
+
merged_capture=merged_capture,
|
289
|
+
pgid=group_id,
|
290
|
+
)
|
291
|
+
assert solution_tee.pipes.input is not None
|
292
|
+
assert interactor_tee.pipes.output is not None
|
293
|
+
solution_input_pipe = interactor_tee.pipes.output
|
294
|
+
solution_output_pipe = solution_tee.pipes.input
|
295
|
+
|
296
|
+
program_params = self._get_program_params(params)
|
297
|
+
program_params.io = self._get_io(params, pipe_io=True)
|
298
|
+
program_params.io.input = solution_input_pipe
|
299
|
+
program_params.io.output = solution_output_pipe
|
300
|
+
program_params.pgid = group_id
|
301
|
+
program = Program(command, program_params)
|
302
|
+
|
303
|
+
results: List[Optional[SandboxLog]] = [None, None]
|
304
|
+
|
305
|
+
for idx in range(4 if should_tee else 2):
|
306
|
+
pid, status, ru = os.wait4(-group_id, 0)
|
307
|
+
|
308
|
+
if pid == interactor.pid:
|
309
|
+
program_result = interactor.process_exit(status, ru)
|
310
|
+
results[1] = self._get_sandbox_log(program_result, interactor_params)
|
311
|
+
results[1].exit_index = idx
|
312
|
+
|
313
|
+
interactor.pipes.output.close()
|
314
|
+
if should_tee:
|
315
|
+
assert interactor_tee.pipes.output is not None
|
316
|
+
interactor_tee.pipes.output.close()
|
317
|
+
|
318
|
+
if idx == 0 and program_result.exitcode != 0:
|
319
|
+
os.killpg(group_id, signal.SIGKILL)
|
320
|
+
elif pid == program.pid:
|
321
|
+
program_result = program.process_exit(status, ru)
|
322
|
+
results[0] = self._get_sandbox_log(program_result, params)
|
323
|
+
results[0].exit_index = idx
|
324
|
+
|
325
|
+
interactor.pipes.input.close()
|
326
|
+
if should_tee:
|
327
|
+
assert solution_tee.pipes.input is not None
|
328
|
+
solution_tee.pipes.input.close()
|
329
|
+
elif should_tee and (pid in (solution_tee.pid, interactor_tee.pid)):
|
330
|
+
pass
|
331
|
+
else:
|
332
|
+
raise RuntimeError(f'Unknown pid: {pid}')
|
333
|
+
|
334
|
+
return typing.cast(Tuple[SandboxLog, SandboxLog], tuple(results))
|
341
335
|
|
342
336
|
def cleanup(self, delete=False):
|
343
337
|
"""See Sandbox.cleanup()."""
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Forked from https://github.com/RagnarGrootKoerkamp/BAPCtools/blob/master/bin/interactive.py
|
4
|
+
|
5
|
+
Takes a character and a file name as arguments.
|
6
|
+
|
7
|
+
Reads from stdin, and writes to stdout and stderr, with the given character prepended to
|
8
|
+
every line read from stdin.
|
9
|
+
"""
|
10
|
+
|
11
|
+
import sys
|
12
|
+
|
13
|
+
c = sys.argv[1]
|
14
|
+
extra = sys.argv[2]
|
15
|
+
|
16
|
+
new = True
|
17
|
+
|
18
|
+
with open(extra, 'w') as f:
|
19
|
+
while True:
|
20
|
+
rd = sys.stdin.read(1)
|
21
|
+
if rd == '':
|
22
|
+
break
|
23
|
+
sys.stdout.write(rd)
|
24
|
+
sys.stdout.flush()
|
25
|
+
if new:
|
26
|
+
sys.stderr.write(c)
|
27
|
+
sys.stderr.write(rd)
|
28
|
+
sys.stderr.flush()
|
29
|
+
|
30
|
+
f.write(rd)
|
31
|
+
new = rd == '\n'
|
rbx/grading/judge/storage.py
CHANGED
@@ -277,8 +277,14 @@ class FilesystemStorage(Storage):
|
|
277
277
|
return None
|
278
278
|
|
279
279
|
# Create a temporary file in the same directory
|
280
|
+
# Use only the basename for the suffix to avoid issues with subdirectories
|
281
|
+
filename_basename = pathlib.Path(filename).name
|
280
282
|
temp_file = tempfile.NamedTemporaryFile(
|
281
|
-
'wb',
|
283
|
+
'wb',
|
284
|
+
delete=False,
|
285
|
+
prefix='.tmp.',
|
286
|
+
suffix=f'.{filename_basename}',
|
287
|
+
dir=self.path,
|
282
288
|
)
|
283
289
|
metadata: Dict[str, Optional[BaseModel]] = {'compression': None}
|
284
290
|
if self.compress or grading_context.should_compress():
|