-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_subtask_inclusion.py
More file actions
339 lines (286 loc) · 11.2 KB
/
check_subtask_inclusion.py
File metadata and controls
339 lines (286 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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
#!/usr/bin/python3
# This script will run all testcases towards the validation for all testgroups,
# and report any potential testcases which could also be included in additional groups.
import subprocess
import os
import yaml
import concurrent.futures
import re
import resource
from pathlib import Path
from typing import Iterable, List, Any
import argparse
resource.setrlimit(resource.RLIMIT_AS, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
parser = argparse.ArgumentParser()
parser.add_argument(
"--target-markdown",
action="store_true",
help="Optimize output formatting for Markdown (otherwise console)"
)
parser.add_argument(
"directory",
nargs="?",
default=Path("."),
type=Path,
help="Directory to process (default: current directory)"
)
args = parser.parse_args()
target_markdown = args.target_markdown
# Formatting
OK_STR = "OK"
OK_NO_STR = "OK:N"
OK_YES_STR = "OK:Y"
SKIP_STR = "SKIP"
MISS_STR = "MISS"
BAD_STR = "BAD"
class Colors:
GREEN = '\033[92m'
RED = '\033[91m'
ORANGE = '\033[93m'
GRAY = '\033[90m'
RESET = '\033[0m'
def orange(text, console_only=False):
if target_markdown:
if console_only:
return text
return f"⚠️{text}"
return f"{Colors.ORANGE}{text}{Colors.RESET}"
def red(text, console_only=False):
if target_markdown:
if console_only:
return text
return f"❌{text}"
return f"{Colors.RED}{text}{Colors.RESET}"
def green(text, console_only=False):
if target_markdown:
if console_only:
return text
return f"✅{text}"
return f"{Colors.GREEN}{text}{Colors.RESET}"
def gray(text, console_only=False):
if target_markdown:
return text
return f"{Colors.GRAY}{text}{Colors.RESET}"
def h2():
if target_markdown:
return "## "
return ""
def h3():
if target_markdown:
return "### "
return ""
def print_md_newline():
if target_markdown:
print("")
def get_problem_name(problem: Path) -> str:
problem_name = green(problem.resolve().name, console_only=True)
parent = problem.parent.name
if parent != Path(__file__).parent.name and parent:
problem_name = parent + '/' + problem_name
return problem_name
def validate_problem(problem: Path):
# Name of the C++ source file
input_validator_path = "input_validators/validator/validator.cpp"
cpp_file = problem / input_validator_path
if not cpp_file.exists():
print_md_newline()
warning_text = f'Skipping {get_problem_name(problem)}: no C++ input validator found. Looked at {input_validator_path}'
print(f"{h2()}{orange(warning_text)}\n")
return
# Name of the output executable
output_executable = '/tmp/validator.out'
# Compile the C++ file
compile_command = ['g++', '-O2', cpp_file, '-o', output_executable, '-std=c++20']
compile_process = subprocess.run(compile_command, capture_output=True, text=True)
# Check if compilation was successful
if compile_process.returncode != 0:
print(f"{h2()}{red('Validator Compilation Failed:')}")
print(red(compile_process.stderr))
return
def run_validator(file, flags, group):
run_command = [output_executable] + flags.split()
with open(file) as inp:
run_process = subprocess.run(run_command, stdin=inp, capture_output=True, text=True)
return run_process.returncode == 42
group_to_flags = {}
tc_to_groups = {}
infiles_path = {}
group_testcases = {}
# os.walk generates the file names in a directory tree
for dirpath, dirnames, filenames in os.walk(os.path.join(problem,'data')):
group = os.path.basename(dirpath)
if group not in ("data", "secret"):
group_to_flags[group] = ""
group_testcases[group] = []
if "testdata.yaml" in filenames:
with open(os.path.join(dirpath,'testdata.yaml'), 'r') as file:
# Load the YAML content
config = yaml.safe_load(file)
if 'input_validator_flags' in config:
group_to_flags[group] = config['input_validator_flags']
for file in filenames:
if file.endswith('.in'):
if file not in tc_to_groups:
tc_to_groups[file] = []
group_testcases[group].append(file)
tc_to_groups[file].append(group)
infiles_path[file] = os.path.join(dirpath,file)
inputs = sorted(tc_to_groups.keys())
groups = sorted(group_to_flags.keys())
if 'sample' in groups:
groups.remove('sample')
groups = ['sample'] + groups
inputs = sorted(inputs, key=lambda x: (re.match(r'(\d+)\.in', x) is None, x))
data = []
def go(file, g):
try:
val = run_validator(infiles_path[file],group_to_flags[g],g)
inc = 1 if g in tc_to_groups[file] else 0
if val == inc:
return f"{OK_YES_STR}" if val else f"{OK_NO_STR}"
elif val:
return MISS_STR
else:
return BAD_STR
except Exception as e:
print(f"{red('Exception while validating')} {file} for group {g}: {e}")
return "UNKNOWN"
for file in inputs:
#row = [file] + [go(file, g) for g in groups]
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [executor.submit(go, file, g) for g in groups]
row = [file] + [future.result() for future in futures]
data.append(row)
def print_table(data: Iterable[Iterable[Any]], headers: Iterable[Any]) -> str:
ncols = max(len(headers), max((len(r) for r in data), default=0))
col_widths: List[int] = []
for col in range(ncols):
max_width = len(headers[col])
for r in data:
max_width = max(max_width, len(r[col]))
col_widths.append(max(3, max_width))
col_widths[0] = max(col_widths[0], max(len(g) for g in groups))
def pad(text: str, width: int, text_width=None) -> str:
if not text_width:
text_width = len(text)
w = (width - text_width)
l_half = w//2
r_half = w-l_half
return ' ' * l_half + text + ' ' * r_half
def color_cell(cell: str) -> str:
if OK_STR in cell:
return green(cell)
elif MISS_STR in cell:
return orange(cell)
elif BAD_STR in cell:
return red(cell)
elif SKIP_STR in cell:
return gray(cell)
return cell
def format_group(group_name, sep='-'):
if target_markdown:
group_name = pad(f"#{group_name}", col_widths[0])
else:
group_name = pad(green(group_name, console_only=True), col_widths[0], len(group_name))
contents = [group_name] + [pad(sep * col_widths[i], col_widths[i]) for i in range(1, len(col_widths))]
line = '| ' + ' | '.join(contents[i] for i in range(len(contents))) + ' |'
return line
row_lines = []
if "sample" in groups:
row_lines.append(format_group("sample", sep=' '))
def get_group_name(tc):
if 'sample' in tc_to_groups[tc]:
return 'sample'
return min(tc_to_groups[tc])
for r in range(len(data)):
line = '| ' + ' | '.join(pad(color_cell(data[r][i]), col_widths[i], len(data[r][i])) for i in range(ncols)) + ' |'
row_lines.append(line)
# Insert group names inbetween
if r + 1 < len(data):
curr_tc = data[r][0]
next_tc = data[r+1][0]
if get_group_name(curr_tc) != get_group_name(next_tc):
row_lines.append(format_group(get_group_name(next_tc)))
header_line = '| ' + ' | '.join(pad(headers[i], col_widths[i]) for i in range(ncols)) + ' |'
separator_line = '| ' + ' | '.join('-' * col_widths[i] for i in range(ncols)) + ' |'
lines = [separator_line if not target_markdown else '', header_line, separator_line] + row_lines
table = '\n'.join(lines)
return table
def count_word_occurrences(word, table):
count = 0
for row in table:
for item in row:
if word in item:
count += 1
return count
# We really dont care what couldve been put in sample
if 'sample' in groups:
for row in data:
if row[groups.index('sample')+1] == MISS_STR:
row[groups.index('sample')+1] = SKIP_STR
# Warning/bad summary
any_bads = count_word_occurrences(BAD_STR, data)>0
any_misses = count_word_occurrences(MISS_STR, data)>0
if any_misses:
num_misses = count_word_occurrences(MISS_STR, data)
p_misses = num_misses / (len(data)*len(groups)) * 100
print(f"{h3()}{orange('Misses')}: {num_misses}, {p_misses:.2f}% of all checks.\n")
if any_bads:
print(f"{h3()}{red('Bads')}: {count_word_occurrences('BAD', data)}")
# Subtask inclusion misses
tc_index = {}
for i in range(len(data)):
tc_index[data[i][0]] = i
for g1 in groups:
missed_inclusions = []
for g2_ind, g2 in enumerate(groups):
if g2 == "sample":
continue
include_allowed = True
any_miss = False
for tc in group_testcases[g1]:
verdict = data[tc_index[tc]][g2_ind + 1]
if verdict in (OK_NO_STR, BAD_STR):
include_allowed = False
break
if verdict == MISS_STR:
any_miss = True
if include_allowed and any_miss:
missed_inclusions.append(g2)
warning_string = orange('Missed sample inclusion') if g1 == 'sample' else red('Missed inclusion')
if not missed_inclusions:
continue
elif len(missed_inclusions) == 1:
print(f"{warning_string}: {orange(g1, console_only=True)} can be included in {orange(missed_inclusions[0], console_only=True)}")
else:
print(f"{warning_string}: {orange(g1, console_only=True)} can be included in")
for g2 in missed_inclusions:
print(f" - {orange(g2, console_only=True)}")
print_md_newline()
# Table
print("")
headers = ['INPUT'] + groups
if target_markdown:
print("<details>\n")
print(print_table(data,headers))
print_md_newline()
if target_markdown:
print("</details>\n")
print("")
def discover_problems(root: Path):
if root.is_file():
root = root.parent
candidates = [p.parent for p in root.rglob('problem.yaml')]
candidates = [p for p in candidates if "testdata_tools" not in str(p)]
return candidates
directory = args.directory
problems = discover_problems(directory)
num_problems = len(problems)
plural = '' if num_problems == 1 else 's'
print(f"Will check {num_problems} problem{plural}.")
i=1
for problem in problems:
print(f"{h2()}Problem {i}/{num_problems}: {get_problem_name(problem)}")
validate_problem(problem)
i += 1