Skip to content

Commit c9f499e

Browse files
authored
Move to subprocess (#52)
* Move to subprocess * fix execution with oiejq * Fix oiejq execution on Arch Linux * Use with statements * Add missing with statement * Add a function for file diff * Remove flush
1 parent fa06059 commit c9f499e

File tree

5 files changed

+74
-34
lines changed

5 files changed

+74
-34
lines changed

src/sinol_make/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from sinol_make import util
55

6-
__version__ = "1.2.1"
6+
__version__ = "1.2.2"
77

88
def configure_parsers():
99
parser = argparse.ArgumentParser(

src/sinol_make/commands/inwer/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ def verify_test(execution: InwerExecution) -> VerificationResult:
5353
output_dir = os.path.join(os.getcwd(), 'cache', 'executions', execution.test_name)
5454
os.makedirs(output_dir, exist_ok=True)
5555

56-
command = f'{execution.inwer_exe_path} < {execution.test_path}'
57-
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
58-
process.wait()
56+
command = [execution.inwer_exe_path]
57+
with open(execution.test_path, 'r') as test:
58+
process = subprocess.Popen(command, stdin=test, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
59+
process.wait()
5960
exit_code = process.returncode
6061
out, _ = process.communicate()
6162

src/sinol_make/commands/run/__init__.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Modified version of https://sinol3.dasie.mimuw.edu.pl/oij/jury/package/-/blob/master/runner.py
22
# Author of the original code: Bartosz Kostka <[email protected]>
33
# Version 0.6 (2021-08-29)
4+
import subprocess
45

56
from sinol_make.commands.run.structs import ExecutionResult, ResultChange, ValidationResult, ExecutionData
67
from sinol_make.helpers.parsers import add_compilation_arguments
@@ -184,22 +185,34 @@ def compile(self, solution):
184185
return False
185186

186187

187-
def execute_oiejq(self, command, result_file, output_file, answer_file, time_limit, memory_limit):
188-
timeout_exit_code = os.system(command)
188+
def execute_oiejq(self, command, input_file_path, answer_file_path,
189+
time_limit, memory_limit):
190+
env = os.environ.copy()
191+
env["MEM_LIMIT"] = f'{memory_limit}K'
192+
env["MEASURE_MEM"] = "1"
193+
with open(input_file_path, "r") as input_file:
194+
process = subprocess.Popen(command, shell=True, stdin=input_file, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
195+
process.wait()
196+
timeout_exit_code = process.returncode
197+
lines = process.stderr.read().decode("utf-8").splitlines()
198+
output = process.stdout.read().decode("utf-8").splitlines()
199+
189200
result = ExecutionResult(None, None, None)
190-
with open(result_file) as r:
191-
for line in r:
192-
line = line.strip()
193-
if ": " in line:
194-
(key, value) = line.split(": ")[:2]
195-
if key == "Time":
196-
result.Time = self.parse_time(value)
197-
elif key == "Memory":
198-
result.Memory = self.parse_memory(value)
199-
else:
200-
setattr(result, key, value)
201201

202-
if timeout_exit_code == 35072:
202+
for line in lines:
203+
line = line.strip()
204+
if ": " in line:
205+
(key, value) = line.split(": ")[:2]
206+
if key == "Time":
207+
result.Time = self.parse_time(value)
208+
elif key == "Memory":
209+
result.Memory = self.parse_memory(value)
210+
else:
211+
setattr(result, key, value)
212+
213+
# If timeout kills the process, the exit code should be 137.
214+
# But on Arch Linux it returns the negative value of the signal that killed the process.
215+
if timeout_exit_code == 137 or timeout_exit_code == -9:
203216
result.Status = "TL"
204217
elif getattr(result, "Time") is not None and result.Time > time_limit:
205218
result.Status = "TL"
@@ -212,20 +225,24 @@ def execute_oiejq(self, command, result_file, output_file, answer_file, time_lim
212225
result.Status = "TL"
213226
elif result.Memory > memory_limit:
214227
result.Status = "ML"
215-
elif os.system("diff -q -Z %s %s >/dev/null"
216-
% (output_file, answer_file)):
228+
elif not util.lines_diff(output, open(answer_file_path).readlines()):
217229
result.Status = "WA"
218230
else:
219231
result.Status = result.Status[:2]
220232

221233
return result
222234

223235

224-
def execute_time(self, command, result_file, output_file, answer_file, time_limit, memory_limit):
225-
timeout_exit_code = os.system(command)
236+
def execute_time(self, command, result_file_path, input_file_path, answer_file_path,
237+
time_limit, memory_limit):
238+
with open(input_file_path, "r") as input_file:
239+
process = subprocess.Popen(command, stdin=input_file, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
240+
process.wait()
241+
timeout_exit_code = process.returncode
242+
output = process.stdout.read().decode("utf-8").splitlines()
226243

227244
result = ExecutionResult(None, None, None)
228-
lines = open(result_file).readlines()
245+
lines = open(result_file_path).readlines()
229246
program_exit_code = None
230247
if len(lines) == 3:
231248
"""
@@ -254,7 +271,7 @@ def execute_time(self, command, result_file, output_file, answer_file, time_limi
254271
result.Status = "TL"
255272
elif result.Memory > memory_limit:
256273
result.Status = "ML"
257-
elif os.system("diff -q -Z %s %s >/dev/null" % (output_file, answer_file)):
274+
elif not util.lines_diff(output, open(answer_file_path).readlines()):
258275
result.Status = "WA"
259276
else:
260277
result.Status = "OK"
@@ -269,17 +286,13 @@ def run_solution(self, data_for_execution: ExecutionData):
269286

270287
(name, executable, test, time_limit, memory_limit, timetool_path) = data_for_execution
271288
file_no_ext = os.path.join(self.EXECUTIONS_DIR, name, self.extract_test_id(test))
272-
output_file = file_no_ext + ".out"
273289
result_file = file_no_ext + ".res"
274290
hard_time_limit_in_s = math.ceil(2 * time_limit / 1000.0)
275291

276292
if self.args.time_tool == 'oiejq':
277-
command = "MEM_LIMIT=%sK MEASURE_MEM=true timeout -k %ds -s SIGKILL %ds %s %s <%s >%s 2>%s" \
278-
% (memory_limit, hard_time_limit_in_s,
279-
hard_time_limit_in_s, timetool_path,
280-
executable, test, output_file, result_file)
293+
command = f'timeout -k {hard_time_limit_in_s}s -s SIGKILL {hard_time_limit_in_s}s "{timetool_path}" "{executable}"'
281294

282-
return self.execute_oiejq(command, result_file, output_file, self.get_output_file(test), time_limit, memory_limit)
295+
return self.execute_oiejq(command, test, self.get_output_file(test), time_limit, memory_limit)
283296
elif self.args.time_tool == 'time':
284297
if sys.platform == 'darwin':
285298
timeout_name = 'gtimeout'
@@ -290,9 +303,9 @@ def run_solution(self, data_for_execution: ExecutionData):
290303
elif sys.platform == 'win32' or sys.platform == 'cygwin':
291304
raise Exception("Measuring time with GNU time on Windows is not supported.")
292305

293-
command = f'{timeout_name} -k {hard_time_limit_in_s}s {hard_time_limit_in_s}s ' \
294-
f'{time_name} -f "%U\\n%M\\n%x" -o {result_file} {executable} <{test} >{output_file} 2>/dev/null'
295-
return self.execute_time(command, result_file, output_file, self.get_output_file(test), time_limit, memory_limit)
306+
command = [f'{timeout_name}', '-k', f'{hard_time_limit_in_s}s', f'{hard_time_limit_in_s}s',
307+
f'{time_name}', '-f', '%U\\n%M\\n%x', '-o', result_file, executable]
308+
return self.execute_time(command, result_file, test, self.get_output_file(test), time_limit, memory_limit)
296309

297310

298311
def update_group_status(self, group_status, new_status):

src/sinol_make/util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,29 @@ def save_config(config):
154154
yaml.dump(config, config_file)
155155

156156

157+
def lines_diff(lines1, lines2):
158+
"""
159+
Function to compare two lists of lines.
160+
Returns True if they are the same, False otherwise.
161+
"""
162+
if len(lines1) != len(lines2):
163+
return False
164+
165+
for i in range(len(lines1)):
166+
if lines1[i].rstrip() != lines2[i].rstrip():
167+
return False
168+
169+
return True
170+
171+
172+
def file_diff(file1, file2):
173+
"""
174+
Function to compare two files.
175+
Returns True if they are the same, False otherwise.
176+
"""
177+
return lines_diff(open(file1).readlines(), open(file2).readlines())
178+
179+
157180
def color_red(text): return "\033[91m{}\033[00m".format(text)
158181
def color_green(text): return "\033[92m{}\033[00m".format(text)
159182
def color_yellow(text): return "\033[93m{}\033[00m".format(text)

tests/util.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import glob
3+
import subprocess
34

45
from sinol_make.helpers import compile
56

@@ -53,7 +54,9 @@ def create_outs(package_path):
5354
assert compile.compile(solution, solution_executable)
5455
os.chdir(os.path.join(package_path, "in"))
5556
for file in glob.glob("*.in"):
56-
os.system(f'{os.path.join(package_path, "cache", "executables", "solution.e")} < {file} > ../out/{file.replace(".in", ".out")}')
57+
with open(file, "r") as in_file, open(os.path.join("../out", file.replace(".in", ".out")), "w") as out_file:
58+
subprocess.Popen([os.path.join(package_path, "cache", "executables", "solution.e")],
59+
stdin=in_file, stdout=out_file).wait()
5760
os.chdir(package_path)
5861

5962

0 commit comments

Comments
 (0)