Skip to content

Commit d724a8b

Browse files
authored
Fix interactive problems in python (#220)
* Fix interactive problems in python * Cleanup * Copy swig generated files * Fix tests * Valid lib package in python * Add caching for extra execution files * Fix typo
1 parent a4acce7 commit d724a8b

File tree

6 files changed

+65
-30
lines changed

6 files changed

+65
-30
lines changed

src/sinol_make/commands/run/__init__.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import time
88
import psutil
99
import glob
10+
import shutil
1011
from io import StringIO
1112
from typing import Dict
1213

@@ -438,7 +439,7 @@ def check_output(self, name, input_file, output_file_path, output, answer_file_p
438439
return self.check_output_checker(name, input_file, output_file_path, answer_file_path)
439440

440441
def execute_oiejq(self, name, timetool_path, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
441-
time_limit, memory_limit, hard_time_limit):
442+
time_limit, memory_limit, hard_time_limit, execution_dir):
442443
command = f'"{timetool_path}" "{executable}"'
443444
env = os.environ.copy()
444445
env["MEM_LIMIT"] = f'{memory_limit}K'
@@ -449,7 +450,7 @@ def execute_oiejq(self, name, timetool_path, executable, result_file_path, input
449450
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file, \
450451
open(result_file_path, "w") as result_file:
451452
process = subprocess.Popen(command, shell=True, stdin=input_file, stdout=output_file,
452-
stderr=result_file, env=env, preexec_fn=os.setsid)
453+
stderr=result_file, env=env, preexec_fn=os.setsid, cwd=execution_dir)
453454

454455
def sigint_handler(signum, frame):
455456
try:
@@ -518,7 +519,7 @@ def sigint_handler(signum, frame):
518519

519520

520521
def execute_time(self, name, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
521-
time_limit, memory_limit, hard_time_limit):
522+
time_limit, memory_limit, hard_time_limit, execution_dir):
522523
if sys.platform == 'darwin':
523524
time_name = 'gtime'
524525
elif sys.platform == 'linux':
@@ -531,7 +532,7 @@ def execute_time(self, name, executable, result_file_path, input_file_path, outp
531532
mem_limit_exceeded = False
532533
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file:
533534
process = subprocess.Popen(command, stdin=input_file, stdout=output_file, stderr=subprocess.DEVNULL,
534-
preexec_fn=os.setsid)
535+
preexec_fn=os.setsid, cwd=execution_dir)
535536

536537
def sigint_handler(signum, frame):
537538
try:
@@ -596,7 +597,7 @@ def sigint_handler(signum, frame):
596597
program_exit_code = int(lines[0].strip().split(" ")[-1])
597598
elif not mem_limit_exceeded:
598599
result.Status = Status.RE
599-
result.Error = "Unexpected output from time command: " + "\n".join(lines)
600+
result.Error = "Unexpected output from time command: " + "".join(lines)
600601
return result
601602

602603
if program_exit_code is not None and program_exit_code != 0:
@@ -630,20 +631,20 @@ def run_solution(self, data_for_execution: ExecutionData):
630631
Run an execution and return the result as ExecutionResult object.
631632
"""
632633

633-
(name, executable, test, time_limit, memory_limit, timetool_path) = data_for_execution
634+
(name, executable, test, time_limit, memory_limit, timetool_path, execution_dir) = data_for_execution
634635
file_no_ext = paths.get_executions_path(name, package_util.extract_test_id(test, self.ID))
635636
output_file = file_no_ext + ".out"
636637
result_file = file_no_ext + ".res"
637638
hard_time_limit_in_s = math.ceil(2 * time_limit / 1000.0)
638639

639640
if self.timetool_name == 'oiejq':
640641
return self.execute_oiejq(name, timetool_path, executable, result_file, test, output_file, self.get_output_file(test),
641-
time_limit, memory_limit, hard_time_limit_in_s)
642+
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
642643
elif self.timetool_name == 'time':
643644
return self.execute_time(name, executable, result_file, test, output_file, self.get_output_file(test),
644-
time_limit, memory_limit, hard_time_limit_in_s)
645+
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
645646

646-
def run_solutions(self, compiled_commands, names, solutions):
647+
def run_solutions(self, compiled_commands, names, solutions, executables_dir):
647648
"""
648649
Run solutions on tests and print the results as a table to stdout.
649650
"""
@@ -653,6 +654,13 @@ def run_solutions(self, compiled_commands, names, solutions):
653654
all_results = collections.defaultdict(
654655
lambda: collections.defaultdict(lambda: collections.defaultdict(map)))
655656

657+
for lang, files in self.config.get('extra_execution_files', {}).items():
658+
for file in files:
659+
shutil.copy(os.path.join(os.getcwd(), "prog", file), executables_dir)
660+
# Copy swig generated .so files
661+
for file in glob.glob(os.path.join(os.getcwd(), "prog", f"_{self.ID}lib.so")):
662+
shutil.copy(file, executables_dir)
663+
656664
for (name, executable, result) in compiled_commands:
657665
lang = package_util.get_file_lang(name)
658666
solution_cache = cache.get_cache_file(os.path.join(os.getcwd(), "prog", name))
@@ -670,7 +678,7 @@ def run_solutions(self, compiled_commands, names, solutions):
670678
all_results[name][self.get_group(test)][test] = test_result.result
671679
else:
672680
executions.append((name, executable, test, test_time_limit, test_memory_limit,
673-
self.timetool_path))
681+
self.timetool_path, os.path.dirname(executable)))
674682
all_results[name][self.get_group(test)][test] = ExecutionResult(Status.PENDING)
675683
os.makedirs(paths.get_executions_path(name), exist_ok=True)
676684
else:
@@ -743,7 +751,7 @@ def compile_and_run(self, solutions):
743751
executables = [paths.get_executables_path(package_util.get_executable(solution)) for solution in solutions]
744752
compiled_commands = zip(solutions, executables, compilation_results)
745753
names = solutions
746-
return self.run_solutions(compiled_commands, names, solutions)
754+
return self.run_solutions(compiled_commands, names, solutions, paths.get_executables_path())
747755

748756
def convert_status_to_string(self, dictionary):
749757
"""
@@ -1196,7 +1204,8 @@ def run(self, args):
11961204
title = self.config["title"]
11971205
print("Task: %s (tag: %s)" % (title, self.ID))
11981206
self.cpus = args.cpus or util.default_cpu_count()
1199-
cache.save_to_cache_extra_compilation_files(self.config.get("extra_compilation_files", []), self.ID)
1207+
cache.process_extra_compilation_files(self.config.get("extra_compilation_files", []), self.ID)
1208+
cache.process_extra_execution_files(self.config.get("extra_execution_files", {}), self.ID)
12001209
cache.remove_results_if_contest_type_changed(self.config.get("sinol_contest_type", "default"))
12011210

12021211
checker = package_util.get_files_matching_pattern(self.ID, f'{self.ID}chk.*')

src/sinol_make/helpers/cache.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,29 @@ def save_compiled(file_path: str, exe_path: str, is_checker: bool = False):
7070
remove_results_cache()
7171

7272

73-
def save_to_cache_extra_compilation_files(extra_compilation_files, task_id):
73+
def _check_file_changed(file_path, lang, task_id):
74+
solutions_re = package_util.get_solutions_re(task_id)
75+
md5sum = util.get_file_md5(file_path)
76+
info = get_cache_file(file_path)
77+
78+
if info.md5sum != md5sum:
79+
for solution in os.listdir(paths.get_cache_path('md5sums')):
80+
# Remove only files in the same language and matching the solution regex
81+
if package_util.get_file_lang(solution) == lang and \
82+
solutions_re.match(solution) is not None:
83+
os.unlink(paths.get_cache_path('md5sums', solution))
84+
85+
info.md5sum = md5sum
86+
info.save(file_path)
87+
88+
89+
def process_extra_compilation_files(extra_compilation_files, task_id):
7490
"""
7591
Checks if extra compilation files have changed and saves them to cache.
7692
If they have, removes all cached solutions that use them.
7793
:param extra_compilation_files: List of extra compilation files
7894
:param task_id: Task id
7995
"""
80-
solutions_re = package_util.get_solutions_re(task_id)
8196
for file in extra_compilation_files:
8297
file_path = os.path.join(os.getcwd(), "prog", file)
8398
if not os.path.exists(file_path):
@@ -86,17 +101,22 @@ def save_to_cache_extra_compilation_files(extra_compilation_files, task_id):
86101
lang = package_util.get_file_lang(file)
87102
if lang == 'h':
88103
lang = 'cpp'
89-
info = get_cache_file(file_path)
104+
_check_file_changed(file_path, lang, task_id)
90105

91-
if info.md5sum != md5sum:
92-
for solution in os.listdir(paths.get_cache_path('md5sums')):
93-
# Remove only files in the same language and matching the solution regex
94-
if package_util.get_file_lang(solution) == lang and \
95-
solutions_re.match(solution) is not None:
96-
os.unlink(paths.get_cache_path('md5sums', solution))
97106

98-
info.md5sum = md5sum
99-
info.save(file_path)
107+
def process_extra_execution_files(extra_execution_files, task_id):
108+
"""
109+
Checks if extra execution files have changed and saves them to cache.
110+
If they have, removes all cached solutions that use them.
111+
:param extra_execution_files: List of extra execution files
112+
:param task_id: Task id
113+
"""
114+
for lang, files in extra_execution_files.items():
115+
for file in files:
116+
file_path = os.path.join(os.getcwd(), "prog", file)
117+
if not os.path.exists(file_path):
118+
continue
119+
_check_file_changed(file_path, lang, task_id)
100120

101121

102122
def remove_results_cache():

tests/commands/run/test_integration.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,14 +676,17 @@ def change_file(file, comment_character):
676676
with open(file, "w") as f:
677677
f.write(f"{comment_character} Changed source code.\n" + source)
678678

679-
def test(file_to_change, lang, comment_character):
679+
def test(file_to_change, lang, comment_character, extra_compilation_files=True):
680680
# First run to cache test results.
681681
command.run(args)
682682

683683
# Change file
684684
change_file(os.path.join(os.getcwd(), "prog", file_to_change), comment_character)
685685

686-
cache.save_to_cache_extra_compilation_files(command.config.get("extra_compilation_files", []), command.ID)
686+
if extra_compilation_files:
687+
cache.process_extra_compilation_files(command.config.get("extra_compilation_files", []), command.ID)
688+
else:
689+
cache.process_extra_execution_files(command.config.get("extra_execution_files", {}), command.ID)
687690
task_id = package_util.get_task_id()
688691
solutions = package_util.get_solutions(task_id, None)
689692
for solution in solutions:
@@ -695,7 +698,7 @@ def test(file_to_change, lang, comment_character):
695698

696699
test("liblib.cpp", "cpp", "//")
697700
test("liblib.h", "cpp", "//")
698-
test("liblib.py", "py", "#")
701+
test("liblib.py", "py", "#", False)
699702

700703

701704
@pytest.mark.parametrize("create_package", [get_simple_package_path()], indirect=True)

tests/commands/run/test_unit.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def test_execution(create_package, time_tool):
3939
config = yaml.load(config_file, Loader=yaml.FullLoader)
4040

4141
os.makedirs(paths.get_executions_path(solution), exist_ok=True)
42-
result = command.run_solution((solution, paths.get_executables_path(executable), test, config['time_limit'], config['memory_limit'], oiejq.get_oiejq_path()))
42+
result = command.run_solution((solution, paths.get_executables_path(executable), test, config['time_limit'],
43+
config['memory_limit'], oiejq.get_oiejq_path(), paths.get_executions_path()))
4344
assert result.Status == Status.OK
4445

4546

tests/helpers/test_cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ def test_cache():
8181
with open("prog/abclib.cpp", "w") as f:
8282
f.write("int main() { return 0; }")
8383

84-
cache.save_to_cache_extra_compilation_files(["abclib.cpp"], "abc")
84+
cache.process_extra_compilation_files(["abclib.cpp"], "abc")
8585
assert cache.get_cache_file("/some/very/long/path/abc.cpp") == CacheFile()
8686
assert cache.get_cache_file("abclib.cpp") != CacheFile()
8787

8888
cache_file.save("abc.cpp")
8989
cache_file.save("abc.py")
9090
with open("prog/abclib.cpp", "w") as f:
9191
f.write("/* Changed file */ int main() { return 0; }")
92-
cache.save_to_cache_extra_compilation_files(["abclib.cpp"], "abc")
92+
cache.process_extra_compilation_files(["abclib.cpp"], "abc")
9393
assert not os.path.exists(paths.get_cache_path("md5sums", "abc.cpp"))
9494
assert os.path.exists(paths.get_cache_path("md5sums", "abc.py"))
9595
assert cache.get_cache_file("abc.py") == cache_file

tests/packages/lib/config.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ time_limit: 1000
44
scores:
55
1: 50
66
2: 50
7-
extra_compilation_files: [liblib.cpp, liblib.h, liblib.py]
7+
extra_compilation_files: [liblib.cpp, liblib.h]
88
extra_compilation_args:
99
cpp: [liblib.cpp]
10+
extra_execution_files:
11+
py: [liblib.py]
1012
sinol_expected_scores:
1113
lib.cpp:
1214
expected:

0 commit comments

Comments
 (0)