Skip to content

Commit 496b97d

Browse files
authored
Fix running with time_limits and memory_limits present in config (#68)
* Fix time and memory limits * Add package for testing * Add tests * Update package to work on macOS * Fix tests for macOS * Fix again * Possible fix * Add comments to `print_view` * Add function for stringifying keys * Bump version for release
1 parent 9963f57 commit 496b97d

File tree

16 files changed

+288
-23
lines changed

16 files changed

+288
-23
lines changed

src/sinol_make/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from sinol_make import util
88

9-
__version__ = "1.4.1"
9+
__version__ = "1.4.2"
1010

1111
def configure_parsers():
1212
parser = argparse.ArgumentParser(

src/sinol_make/commands/run/__init__.py

+33-19
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def update_group_status(group_status, new_status):
5555

5656

5757
def print_view(term_width, term_height, program_groups_scores, all_results, print_data: PrintData, names, executions,
58-
groups, scores, tests, possible_score, time_limit, memory_limit, cpus, hide_memory):
58+
groups, scores, tests, possible_score, cpus, hide_memory, config):
5959
width = term_width - 13 # First column has 6 characters, the " | " separator has 3 characters and 4 for margin
6060
programs_in_row = width // 13 # Each program has 10 characters and the " | " separator has 3 characters
6161

@@ -64,9 +64,18 @@ def print_view(term_width, term_height, program_groups_scores, all_results, prin
6464
sys.stdout = output
6565

6666
program_scores = collections.defaultdict(int)
67-
program_times = collections.defaultdict(lambda: -1)
68-
program_memory = collections.defaultdict(lambda: -1)
69-
time_remaining = (len(executions) - print_data.i - 1) * 2 * time_limit / cpus / 1000.0
67+
# program_times and program_memory are dictionaries of tuples (max, limit),
68+
# where max is the maximum time/memory used by a program and
69+
# limit is the time/memory limit of the test that caused the maximum
70+
# time/memory usage.
71+
program_times = collections.defaultdict(lambda: (-1, 0))
72+
program_memory = collections.defaultdict(lambda: (-1, 0))
73+
74+
time_sum = 0
75+
for test in tests:
76+
time_sum += package_util.get_time_limit(test, config)
77+
78+
time_remaining = (len(executions) - print_data.i - 1) * 2 * time_sum / cpus / 1000.0
7079
title = 'Done %4d/%4d. Time remaining (in the worst case): %5d seconds.' \
7180
% (print_data.i + 1, len(executions), time_remaining)
7281
title = title.center(term_width)
@@ -104,15 +113,17 @@ def print_table_end():
104113
min_points = min(min_points, results[test].Points)
105114
status = results[test].Status
106115
if getattr(results[test], "Time") is not None:
107-
program_times[program] = max(
108-
program_times[program], results[test].Time)
116+
if program_times[program][0] < results[test].Time:
117+
program_times[program] = (results[test].Time, package_util.get_time_limit(test, config))
109118
elif status == "TL":
110-
program_times[program] = 2 * time_limit
119+
program_times[program] = (2 * package_util.get_time_limit(test, config),
120+
package_util.get_time_limit(test, config))
111121
if getattr(results[test], "Memory") is not None:
112-
program_memory[program] = max(
113-
program_memory[program], results[test].Memory)
122+
if program_memory[program][0] < results[test].Memory:
123+
program_memory[program] = (results[test].Memory, package_util.get_memory_limit(test, config))
114124
elif status == "ML":
115-
program_memory[program] = 2 * memory_limit
125+
program_memory[program] = (2 * package_util.get_memory_limit(test, config),
126+
package_util.get_memory_limit(test, config))
116127
if status == " ":
117128
group_status = " "
118129
min_points = 0
@@ -138,15 +149,15 @@ def print_table_end():
138149
print(margin + " time", end=" | ")
139150
for program in program_group:
140151
program_time = program_times[program]
141-
print(util.bold(("%20s" % color_time(program_time, time_limit))
142-
if program_time < 2 * time_limit and program_time >= 0
152+
print(util.bold(("%20s" % color_time(program_time[0], program_time[1]))
153+
if program_time[0] < 2 * program_time[1] and program_time[0] >= 0
143154
else " " + 7 * '-'), end=" | ")
144155
print()
145156
print(margin + "memory", end=" | ")
146157
for program in program_group:
147158
program_mem = program_memory[program]
148-
print(util.bold(("%20s" % color_memory(program_mem, memory_limit))
149-
if program_mem < 2 * memory_limit and program_mem >= 0
159+
print(util.bold(("%20s" % color_memory(program_mem[0], program_mem[1]))
160+
if program_mem[0] < 2 * program_mem[1] and program_mem[0] >= 0
150161
else " " + 7 * '-'), end=" | ")
151162
print()
152163
print(8*" ", end=" | ")
@@ -177,13 +188,15 @@ def print_group_seperator():
177188
if status == " ": print(10*' ', end=" | ")
178189
else:
179190
print("%3s" % colorize_status(status),
180-
("%17s" % color_time(result.Time, time_limit)) if getattr(result, "Time") is not None else 7*" ", end=" | ")
191+
("%17s" % color_time(result.Time, package_util.get_time_limit(test, config)))
192+
if getattr(result, "Time") is not None else 7*" ", end=" | ")
181193
print()
182194
if not hide_memory:
183195
print(8*" ", end=" | ")
184196
for program in program_group:
185197
result = all_results[program][package_util.get_group(test)][test]
186-
print(("%20s" % color_memory(result.Memory, memory_limit)) if getattr(result, "Memory") is not None else 10*" ", end=" | ")
198+
print(("%20s" % color_memory(result.Memory, package_util.get_memory_limit(test, config)))
199+
if getattr(result, "Memory") is not None else 10*" ", end=" | ")
187200
print()
188201

189202
print_table_end()
@@ -580,7 +593,8 @@ def run_solutions(self, compiled_commands, names, solutions):
580593
for (name, executable, result) in compiled_commands:
581594
if result:
582595
for test in self.tests:
583-
executions.append((name, executable, test, self.time_limit, self.memory_limit, self.timetool_path))
596+
executions.append((name, executable, test, package_util.get_time_limit(test, self.config),
597+
package_util.get_memory_limit(test, self.config), self.timetool_path))
584598
all_results[name][self.get_group(test)][test] = ExecutionResult(" ")
585599
os.makedirs(os.path.join(self.EXECUTIONS_DIR, name), exist_ok=True)
586600
else:
@@ -599,7 +613,7 @@ def run_solutions(self, compiled_commands, names, solutions):
599613
thr = threading.Thread(target=printer.printer_thread,
600614
args=(run_event, print_view, program_groups_scores, all_results, print_data, names,
601615
executions, self.groups, self.scores, self.tests, self.possible_score,
602-
self.time_limit, self.memory_limit, self.cpus, self.args.hide_memory))
616+
self.cpus, self.args.hide_memory, self.config))
603617
thr.start()
604618

605619
pool = mp.Pool(self.cpus)
@@ -620,7 +634,7 @@ def run_solutions(self, compiled_commands, names, solutions):
620634

621635
print("\n".join(print_view(terminal_width, terminal_height, program_groups_scores, all_results, print_data,
622636
names, executions, self.groups, self.scores, self.tests, self.possible_score,
623-
self.time_limit, self.memory_limit, self.cpus, self.args.hide_memory)[0]))
637+
self.cpus, self.args.hide_memory, self.config)[0]))
624638

625639
if keyboard_interrupt:
626640
util.exit_with_error("Stopped due to keyboard interrupt.")

src/sinol_make/helpers/package_util.py

+34
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22
from typing import List, Union
33

4+
from sinol_make import util
5+
46

57
def get_task_id() -> str:
68
return os.path.split(os.getcwd())[-1]
@@ -55,3 +57,35 @@ def get_executable_path(solution: str) -> str:
5557
Returns path to compiled executable for given solution.
5658
"""
5759
return os.path.join(os.getcwd(), 'cache', 'executables', get_executable(solution))
60+
61+
62+
def get_time_limit(test_path, config):
63+
"""
64+
Returns time limit for given test.
65+
"""
66+
str_config = util.stringify_keys(config)
67+
test_id = extract_test_id(test_path)
68+
test_group = str(get_group(test_path))
69+
70+
if "time_limits" in str_config:
71+
if test_id in str_config["time_limits"]:
72+
return str_config["time_limits"][test_id]
73+
elif test_group in str_config["time_limits"]:
74+
return str_config["time_limits"][test_group]
75+
return str_config["time_limit"]
76+
77+
78+
def get_memory_limit(test_path, config):
79+
"""
80+
Returns memory limit for given test.
81+
"""
82+
str_config = util.stringify_keys(config)
83+
test_id = extract_test_id(test_path)
84+
test_group = str(get_group(test_path))
85+
86+
if "memory_limits" in str_config:
87+
if test_id in str_config["memory_limits"]:
88+
return str_config["memory_limits"][test_id]
89+
elif test_group in str_config["memory_limits"]:
90+
return str_config["memory_limits"][test_group]
91+
return str_config["memory_limit"]

src/sinol_make/util.py

+12
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,18 @@ def fix_file_endings(file):
304304
f.write(content.replace(b"\r\n", b"\n"))
305305

306306

307+
def stringify_keys(d):
308+
"""
309+
Function to stringify all keys in a dict.
310+
"""
311+
if isinstance(d, dict):
312+
return {str(k): stringify_keys(v) for k, v in d.items()}
313+
elif isinstance(d, list):
314+
return [stringify_keys(x) for x in d]
315+
else:
316+
return d
317+
318+
307319
def color_red(text): return "\033[91m{}\033[00m".format(text)
308320
def color_green(text): return "\033[92m{}\033[00m".format(text)
309321
def color_yellow(text): return "\033[93m{}\033[00m".format(text)

tests/commands/run/test_integration.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55

66
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
7-
get_checker_package_path(), get_library_package_path()], indirect=True)
7+
get_checker_package_path(), get_library_package_path(),
8+
get_limits_package_path()], indirect=True)
89
def test_simple(create_package, time_tool):
910
"""
1011
Test a simple run.
@@ -20,7 +21,8 @@ def test_simple(create_package, time_tool):
2021

2122

2223
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
23-
get_checker_package_path(), get_library_package_path()], indirect=True)
24+
get_checker_package_path(), get_library_package_path(),
25+
get_limits_package_path()], indirect=True)
2426
def test_no_expected_scores(capsys, create_package, time_tool):
2527
"""
2628
Test with no sinol_expected_scores in config.yml.
@@ -53,7 +55,8 @@ def test_no_expected_scores(capsys, create_package, time_tool):
5355

5456

5557
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
56-
get_checker_package_path(), get_library_package_path()], indirect=True)
58+
get_checker_package_path(), get_library_package_path(),
59+
get_limits_package_path()], indirect=True)
5760
def test_apply_suggestions(create_package, time_tool):
5861
"""
5962
Test with no sinol_expected_scores in config.yml.
@@ -225,3 +228,30 @@ def test_missing_output_files(capsys, create_package):
225228

226229
out = capsys.readouterr().out
227230
assert f'Missing output files for tests: {out1}, {out2}' in out
231+
232+
233+
@pytest.mark.parametrize("create_package", [get_limits_package_path()], indirect=True)
234+
def test_no_limits_in_config(capsys, create_package, time_tool):
235+
"""
236+
Test with missing `time_limits` and `memory_limits` keys in config.yml.
237+
"""
238+
package_path = create_package
239+
command = get_command()
240+
create_ins_outs(package_path)
241+
242+
config_path = os.path.join(package_path, "config.yml")
243+
with open(config_path, "r") as config_file:
244+
config = yaml.load(config_file, Loader=yaml.SafeLoader)
245+
del config["time_limits"]
246+
del config["memory_limits"]
247+
with open(config_path, "w") as config_file:
248+
config_file.write(yaml.dump(config))
249+
250+
parser = configure_parsers()
251+
args = parser.parse_args(["run", "--time-tool", time_tool])
252+
command = Command()
253+
with pytest.raises(SystemExit):
254+
command.run(args)
255+
256+
out = capsys.readouterr().out
257+
assert "Use flag --apply-suggestions to apply suggestions." in out

tests/helpers/test_package_util.py

+36
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,39 @@ def test_extract_file_name():
3131

3232
def test_get_executable():
3333
assert package_util.get_executable("abc.cpp") == "abc.e"
34+
35+
36+
def test_get_time_limit():
37+
config = {
38+
"time_limit": 1000,
39+
"time_limits": {
40+
"2": 2000,
41+
"2a": 3000,
42+
"3ocen": 5000
43+
}
44+
}
45+
46+
assert package_util.get_time_limit("in/abc1a.in", config) == 1000
47+
assert package_util.get_time_limit("in/abc2a.in", config) == 3000
48+
assert package_util.get_time_limit("in/abc2b.in", config) == 2000
49+
assert package_util.get_time_limit("in/abc3a.in", config) == 1000
50+
assert package_util.get_time_limit("in/abc3ocen.in", config) == 5000
51+
52+
53+
def test_get_memory_limit():
54+
config = {
55+
"memory_limit": 256,
56+
"memory_limits": {
57+
"2": 512,
58+
"2c": 1024,
59+
"3ocen": 2048,
60+
"3": 128
61+
}
62+
}
63+
64+
assert package_util.get_memory_limit("in/abc1a.in", config) == 256
65+
assert package_util.get_memory_limit("in/abc2a.in", config) == 512
66+
assert package_util.get_memory_limit("in/abc2b.in", config) == 512
67+
assert package_util.get_memory_limit("in/abc2c.in", config) == 1024
68+
assert package_util.get_memory_limit("in/abc3a.in", config) == 128
69+
assert package_util.get_memory_limit("in/abc3ocen.in", config) == 2048

tests/packages/lim/config.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
title: Package with `time_limits` and `memory_limits` set
2+
3+
memory_limit: 30000
4+
memory_limits:
5+
1a: 15000
6+
2: 60000
7+
8+
time_limit: 1000
9+
time_limits:
10+
1a: 500
11+
2: 2000
12+
13+
scores:
14+
1: 50
15+
2: 50
16+
17+
sinol_expected_scores:
18+
lim.cpp:
19+
expected: {1: OK, 2: OK}
20+
points: 100
21+
lim1.cpp:
22+
expected: {1: TL, 2: OK}
23+
points: 50
24+
lim2.cpp:
25+
expected: {1: TL, 2: TL}
26+
points: 0
27+
lim3.cpp:
28+
expected: {1: ML, 2: OK}
29+
points: 50
30+
lim4.cpp:
31+
expected: {1: ML, 2: ML}
32+
points: 0

tests/packages/lim/in/.gitkeep

Whitespace-only changes.

tests/packages/lim/out/.gitkeep

Whitespace-only changes.

tests/packages/lim/prog/lim.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <bits/stdc++.h>
2+
3+
using namespace std;
4+
5+
int main() {
6+
int a, b;
7+
cin >> a >> b;
8+
cout << a + b;
9+
}

tests/packages/lim/prog/lim1.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <bits/stdc++.h>
2+
#include <chrono>
3+
4+
using namespace std;
5+
using namespace std::chrono_literals;
6+
7+
int main() {
8+
int a, b;
9+
cin >> a >> b;
10+
11+
vector<int> v(5, 0);
12+
this_thread::sleep_for(1.1s);
13+
14+
cout << a + b << endl;
15+
}

tests/packages/lim/prog/lim2.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <bits/stdc++.h>
2+
#include <chrono>
3+
4+
using namespace std;
5+
using namespace std::chrono_literals;
6+
7+
int main() {
8+
int a, b;
9+
cin >> a >> b;
10+
11+
if (a == 2 && b == 1) {
12+
this_thread::sleep_for(6s);
13+
}
14+
else {
15+
this_thread::sleep_for(1.1s);
16+
}
17+
18+
cout << a + b << endl;
19+
}

tests/packages/lim/prog/lim3.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <bits/stdc++.h>
2+
3+
using namespace std;
4+
5+
int main() {
6+
int a, b;
7+
cin >> a >> b;
8+
9+
vector<long long> mem;
10+
if (a == 1) {
11+
for (int i = 1; i <= 1280000; i++) {
12+
mem.push_back(i);
13+
}
14+
}
15+
cout << a + b;
16+
}

0 commit comments

Comments
 (0)