rbx.cp 0.13.4__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/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 +13 -1
- 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 +7 -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/validators.py +3 -1
- rbx/grading/caching.py +64 -14
- 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/templates/rbx.h +43 -2
- rbx/testing_utils.py +7 -0
- rbx/utils.py +51 -1
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.5.dist-info}/METADATA +1 -1
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.5.dist-info}/RECORD +32 -34
- rbx/grading/judge/sandboxes/isolate.py +0 -695
- rbx/grading/judge/testiso.py +0 -54
- 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.5.dist-info}/LICENSE +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.5.dist-info}/WHEEL +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.5.dist-info}/entry_points.txt +0 -0
@@ -5,21 +5,45 @@ import logging
|
|
5
5
|
import os
|
6
6
|
import pathlib
|
7
7
|
import shutil
|
8
|
-
import signal
|
9
8
|
import subprocess
|
10
9
|
import sys
|
11
10
|
import tempfile
|
12
|
-
|
11
|
+
import typing
|
12
|
+
from typing import List, Optional, Tuple
|
13
13
|
|
14
14
|
from rbx import utils
|
15
15
|
from rbx.grading.judge.cacher import FileCacher
|
16
|
+
from rbx.grading.judge.program import (
|
17
|
+
FileLike,
|
18
|
+
Program,
|
19
|
+
ProgramCode,
|
20
|
+
ProgramIO,
|
21
|
+
ProgramParams,
|
22
|
+
ProgramResult,
|
23
|
+
)
|
16
24
|
from rbx.grading.judge.sandbox import (
|
17
25
|
SandboxBase,
|
26
|
+
SandboxLog,
|
18
27
|
SandboxParams,
|
19
28
|
)
|
20
29
|
|
21
30
|
logger = logging.getLogger(__name__)
|
22
31
|
|
32
|
+
TEE_CODE = R"""
|
33
|
+
import sys
|
34
|
+
c = sys.argv[1]
|
35
|
+
new = True
|
36
|
+
while True:
|
37
|
+
l = sys.stdin.read(1)
|
38
|
+
if l=='': break
|
39
|
+
sys.stdout.write(l)
|
40
|
+
sys.stdout.flush()
|
41
|
+
if new: sys.stderr.write(c)
|
42
|
+
sys.stderr.write(l)
|
43
|
+
sys.stderr.flush()
|
44
|
+
new = l=='\n'
|
45
|
+
"""
|
46
|
+
|
23
47
|
|
24
48
|
class StupidSandbox(SandboxBase):
|
25
49
|
"""A stupid sandbox implementation. It has very few features and
|
@@ -31,16 +55,12 @@ class StupidSandbox(SandboxBase):
|
|
31
55
|
"""
|
32
56
|
|
33
57
|
exec_num: int
|
34
|
-
popen: Optional[subprocess.Popen]
|
35
|
-
returncode: Optional[int]
|
36
|
-
log: Optional[Dict[str, str]]
|
37
58
|
|
38
59
|
def __init__(
|
39
60
|
self,
|
40
61
|
file_cacher: Optional[FileCacher] = None,
|
41
62
|
name: Optional[str] = None,
|
42
63
|
temp_dir: Optional[pathlib.Path] = None,
|
43
|
-
params: Optional[SandboxParams] = None,
|
44
64
|
):
|
45
65
|
"""Initialization.
|
46
66
|
|
@@ -49,7 +69,7 @@ class StupidSandbox(SandboxBase):
|
|
49
69
|
"""
|
50
70
|
if not temp_dir:
|
51
71
|
temp_dir = pathlib.Path(tempfile.gettempdir())
|
52
|
-
SandboxBase.__init__(self, file_cacher, name, temp_dir
|
72
|
+
SandboxBase.__init__(self, file_cacher, name, temp_dir)
|
53
73
|
|
54
74
|
# Make box directory
|
55
75
|
self.initialize()
|
@@ -68,56 +88,6 @@ class StupidSandbox(SandboxBase):
|
|
68
88
|
# Box parameters
|
69
89
|
self.chdir = self._path
|
70
90
|
|
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
91
|
def get_root_path(self) -> pathlib.Path:
|
122
92
|
"""Return the toplevel path of the sandbox.
|
123
93
|
|
@@ -126,171 +96,153 @@ class StupidSandbox(SandboxBase):
|
|
126
96
|
"""
|
127
97
|
return self._path
|
128
98
|
|
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
99
|
def use_soft_timeout(self) -> bool:
|
151
100
|
return True
|
152
101
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
if
|
163
|
-
return
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
102
|
+
def _get_exit_status(self, result: ProgramResult) -> str:
|
103
|
+
if ProgramCode.TE in result.program_codes:
|
104
|
+
return SandboxBase.EXIT_TERMINATED
|
105
|
+
if ProgramCode.WT in result.program_codes:
|
106
|
+
return SandboxBase.EXIT_TIMEOUT_WALL
|
107
|
+
if ProgramCode.TO in result.program_codes:
|
108
|
+
return SandboxBase.EXIT_TIMEOUT
|
109
|
+
if ProgramCode.OL in result.program_codes:
|
110
|
+
return SandboxBase.EXIT_OUTPUT_LIMIT_EXCEEDED
|
111
|
+
if ProgramCode.ML in result.program_codes:
|
112
|
+
return SandboxBase.EXIT_MEMORY_LIMIT_EXCEEDED
|
113
|
+
if ProgramCode.SG in result.program_codes:
|
114
|
+
return SandboxBase.EXIT_SIGNAL
|
115
|
+
if ProgramCode.RE in result.program_codes:
|
116
|
+
return SandboxBase.EXIT_NONZERO_RETURN
|
117
|
+
return SandboxBase.EXIT_OK
|
118
|
+
|
119
|
+
def _get_io(self, params: SandboxParams, pipe_io: bool = False) -> ProgramIO:
|
120
|
+
io = ProgramIO()
|
121
|
+
if params.stdin_file and not pipe_io:
|
122
|
+
io.input = self.relative_path(params.stdin_file)
|
123
|
+
if params.stdout_file and not pipe_io:
|
124
|
+
io.output = self.relative_path(params.stdout_file)
|
125
|
+
if params.stderr_file:
|
126
|
+
io.stderr = self.relative_path(params.stderr_file)
|
127
|
+
return io
|
128
|
+
|
129
|
+
def _get_program_params(self, params: SandboxParams) -> ProgramParams:
|
130
|
+
return ProgramParams(
|
131
|
+
chdir=self.chdir,
|
132
|
+
time_limit=params.timeout / 1000 if params.timeout else None,
|
133
|
+
wall_time_limit=params.wallclock_timeout / 1000
|
134
|
+
if params.wallclock_timeout
|
135
|
+
else None,
|
136
|
+
memory_limit=params.address_space,
|
137
|
+
fs_limit=params.fsize,
|
138
|
+
env=params.set_env,
|
139
|
+
io=self._get_io(params),
|
140
|
+
)
|
170
141
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
142
|
+
def _get_tee_program_params(self, io: ProgramIO, pgid: int) -> ProgramParams:
|
143
|
+
return ProgramParams(
|
144
|
+
chdir=self.chdir,
|
145
|
+
time_limit=None,
|
146
|
+
wall_time_limit=None,
|
147
|
+
memory_limit=None,
|
148
|
+
io=io,
|
149
|
+
pgid=pgid,
|
150
|
+
)
|
176
151
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
152
|
+
def _get_sandbox_log(
|
153
|
+
self, result: ProgramResult, params: SandboxParams
|
154
|
+
) -> SandboxLog:
|
155
|
+
return SandboxLog(
|
156
|
+
params=params.model_copy(deep=True),
|
157
|
+
execution_time=result.wall_time,
|
158
|
+
memory_used=result.memory_used,
|
159
|
+
exitcode=result.exitcode,
|
160
|
+
exitstatus=self._get_exit_status(result),
|
161
|
+
killing_signal=result.killing_signal,
|
162
|
+
other_logs={
|
163
|
+
'program_codes': [code.value for code in result.program_codes],
|
164
|
+
'alarm_msg': result.alarm_msg,
|
165
|
+
},
|
166
|
+
)
|
180
167
|
|
181
|
-
|
168
|
+
def _needs_teeing(
|
169
|
+
self,
|
170
|
+
params: SandboxParams,
|
171
|
+
interactor_params: SandboxParams,
|
172
|
+
merged_capture: Optional[pathlib.Path] = None,
|
173
|
+
) -> bool:
|
174
|
+
return (
|
175
|
+
params.stdout_file is not None
|
176
|
+
or interactor_params.stdout_file is not None
|
177
|
+
or merged_capture is not None
|
178
|
+
)
|
182
179
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
180
|
+
def _get_tee_executable(self) -> pathlib.Path:
|
181
|
+
with importlib.resources.as_file(
|
182
|
+
importlib.resources.files('rbx')
|
183
|
+
/ 'grading'
|
184
|
+
/ 'judge'
|
185
|
+
/ 'sandboxes'
|
186
|
+
/ 'tee.py'
|
187
|
+
) as file:
|
188
|
+
return file
|
188
189
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
190
|
+
def _get_tee_command(self, char: str, extra: Optional[str] = None) -> List[str]:
|
191
|
+
return [
|
192
|
+
sys.executable,
|
193
|
+
str(utils.abspath(self._get_tee_executable())),
|
194
|
+
char,
|
195
|
+
extra or '/dev/null',
|
196
|
+
]
|
195
197
|
|
196
|
-
|
198
|
+
def _get_tee_program(
|
199
|
+
self,
|
200
|
+
char: str,
|
201
|
+
stdin: FileLike,
|
202
|
+
stdout: FileLike,
|
203
|
+
pgid: int,
|
204
|
+
capture: Optional[pathlib.Path] = None,
|
205
|
+
merged_capture: Optional[pathlib.Path] = None,
|
206
|
+
) -> Program:
|
207
|
+
io = ProgramIO(input=stdin, output=stdout, stderr=subprocess.DEVNULL)
|
208
|
+
if merged_capture:
|
209
|
+
io.stderr = self.relative_path(merged_capture).open('ab')
|
210
|
+
return Program(
|
211
|
+
self._get_tee_command(
|
212
|
+
char, str(self.relative_path(capture)) if capture else None
|
213
|
+
),
|
214
|
+
self._get_tee_program_params(io, pgid),
|
215
|
+
)
|
197
216
|
|
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.
|
217
|
+
def _get_pathlike_stdout(self, io: ProgramIO) -> Optional[pathlib.Path]:
|
218
|
+
if isinstance(io.output, str) or isinstance(io.output, pathlib.Path):
|
219
|
+
return pathlib.Path(io.output)
|
220
|
+
return None
|
224
221
|
|
225
|
-
|
226
|
-
|
227
|
-
return int(self.log['exit-code'])
|
222
|
+
def run(self, command: List[str], params: SandboxParams) -> SandboxLog:
|
223
|
+
self.exec_num += 1
|
228
224
|
|
229
|
-
|
230
|
-
|
225
|
+
logger.debug(
|
226
|
+
"Executing program in sandbox with command: `%s'.", ' '.join(command)
|
227
|
+
)
|
228
|
+
with open(
|
229
|
+
self.relative_path(self.cmd_file), 'at', encoding='utf-8'
|
230
|
+
) as commands:
|
231
|
+
commands.write('%s\n' % command)
|
231
232
|
|
232
|
-
|
233
|
-
|
234
|
-
string describing it.
|
233
|
+
program = Program(command, self._get_program_params(params))
|
234
|
+
result = program.wait()
|
235
235
|
|
236
|
-
return (
|
237
|
-
sandbox terminated.
|
236
|
+
return self._get_sandbox_log(result, params)
|
238
237
|
|
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(
|
238
|
+
def run_communication(
|
277
239
|
self,
|
278
240
|
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
|
-
"""
|
241
|
+
params: SandboxParams,
|
242
|
+
interactor_command: List[str],
|
243
|
+
interactor_params: SandboxParams,
|
244
|
+
merged_capture: Optional[pathlib.Path] = None,
|
245
|
+
) -> Tuple[SandboxLog, SandboxLog]:
|
294
246
|
self.exec_num += 1
|
295
247
|
|
296
248
|
logger.debug(
|
@@ -301,43 +253,82 @@ class StupidSandbox(SandboxBase):
|
|
301
253
|
) as commands:
|
302
254
|
commands.write('%s\n' % command)
|
303
255
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
]
|
310
|
-
+ self.get_timeit_args()
|
311
|
-
+ command
|
256
|
+
interactor_program_params = self._get_program_params(interactor_params)
|
257
|
+
interactor_program_params.io = self._get_io(interactor_params, pipe_io=True)
|
258
|
+
interactor = Program(
|
259
|
+
interactor_command,
|
260
|
+
interactor_program_params,
|
312
261
|
)
|
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
|
-
|
262
|
+
assert interactor.pipes.output is not None
|
263
|
+
assert interactor.pipes.input is not None
|
264
|
+
solution_input_pipe = interactor.pipes.output
|
265
|
+
solution_output_pipe = interactor.pipes.input
|
266
|
+
|
267
|
+
group_id = os.getpgid(interactor.pid)
|
268
|
+
should_tee = self._needs_teeing(params, interactor_params, merged_capture)
|
269
|
+
|
270
|
+
if should_tee:
|
271
|
+
if merged_capture:
|
272
|
+
self.create_file_from_string(merged_capture, '<\n>\n', override=True)
|
273
|
+
|
274
|
+
solution_tee = self._get_tee_program(
|
275
|
+
'>',
|
276
|
+
stdin=subprocess.PIPE,
|
277
|
+
stdout=interactor.pipes.input,
|
278
|
+
capture=self._get_pathlike_stdout(self._get_io(params)),
|
279
|
+
merged_capture=merged_capture,
|
280
|
+
pgid=group_id,
|
281
|
+
)
|
282
|
+
interactor_tee = self._get_tee_program(
|
283
|
+
'<',
|
284
|
+
stdin=interactor.pipes.output,
|
285
|
+
stdout=subprocess.PIPE,
|
286
|
+
capture=self._get_pathlike_stdout(self._get_io(interactor_params)),
|
287
|
+
merged_capture=merged_capture,
|
288
|
+
pgid=group_id,
|
289
|
+
)
|
290
|
+
assert solution_tee.pipes.input is not None
|
291
|
+
assert interactor_tee.pipes.output is not None
|
292
|
+
solution_input_pipe = interactor_tee.pipes.output
|
293
|
+
solution_output_pipe = solution_tee.pipes.input
|
294
|
+
|
295
|
+
program_params = self._get_program_params(params)
|
296
|
+
program_params.io = self._get_io(params, pipe_io=True)
|
297
|
+
program_params.io.input = solution_input_pipe
|
298
|
+
program_params.io.output = solution_output_pipe
|
299
|
+
program_params.pgid = group_id
|
300
|
+
program = Program(command, program_params)
|
301
|
+
|
302
|
+
results: List[Optional[SandboxLog]] = [None, None]
|
303
|
+
|
304
|
+
for idx in range(4 if should_tee else 2):
|
305
|
+
pid, status, ru = os.wait4(-group_id, 0)
|
306
|
+
|
307
|
+
if pid == interactor.pid:
|
308
|
+
program_result = interactor.process_exit(status, ru)
|
309
|
+
results[1] = self._get_sandbox_log(program_result, interactor_params)
|
310
|
+
results[1].exit_index = idx
|
311
|
+
|
312
|
+
interactor.pipes.output.close()
|
313
|
+
if should_tee:
|
314
|
+
assert interactor_tee.pipes.output is not None
|
315
|
+
interactor_tee.pipes.output.close()
|
316
|
+
# TODO: kill in case of WA
|
317
|
+
elif pid == program.pid:
|
318
|
+
program_result = program.process_exit(status, ru)
|
319
|
+
results[0] = self._get_sandbox_log(program_result, params)
|
320
|
+
results[0].exit_index = idx
|
321
|
+
|
322
|
+
interactor.pipes.input.close()
|
323
|
+
if should_tee:
|
324
|
+
assert solution_tee.pipes.input is not None
|
325
|
+
solution_tee.pipes.input.close()
|
326
|
+
elif should_tee and (pid in (solution_tee.pid, interactor_tee.pid)):
|
327
|
+
pass
|
328
|
+
else:
|
329
|
+
raise RuntimeError(f'Unknown pid: {pid}')
|
330
|
+
|
331
|
+
return typing.cast(Tuple[SandboxLog, SandboxLog], tuple(results))
|
341
332
|
|
342
333
|
def cleanup(self, delete=False):
|
343
334
|
"""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'
|