Skip to content

Commit 2a05dc7

Browse files
authored
Merge branch 'draft' into absolute2
2 parents f5ba217 + 6da3bc5 commit 2a05dc7

19 files changed

+234
-148
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ repos:
77
- repo: https://github.com/astral-sh/ruff-pre-commit
88
rev: v0.9.9
99
hooks:
10-
- id: ruff-format
1110
- id: ruff
1211
args: [ --fix ]
12+
- id: ruff-format
1313
- repo: https://github.com/pre-commit/mirrors-mypy
1414
rev: v1.15.0
1515
hooks:

bin/config.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import re
66
from pathlib import Path
77
from collections.abc import Mapping, Sequence
8-
from typing import Final, Literal, Optional
8+
from typing import Any, Final, Literal, Optional
99

1010
SPEC_VERSION: Final[str] = "2023-07-draft"
1111

@@ -86,7 +86,6 @@
8686
"invalid_input",
8787
"invalid_answer",
8888
"invalid_output",
89-
"bad",
9089
]
9190

9291

@@ -105,7 +104,7 @@
105104

106105
args = argparse.Namespace()
107106

108-
DEFAULT_ARGS: Final[Mapping] = {
107+
DEFAULT_ARGS: Final[Mapping[str, Any]] = {
109108
"jobs": (os.cpu_count() or 1) // 2,
110109
"time": 600, # Used for `bt fuzz`
111110
"verbose": 0,

bin/constraints.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import re
22
from collections import defaultdict
3+
from typing import Optional
34

45
import latex
56
import validate
67
from colorama import Fore, Style
8+
from problem import Problem
79

810
# Local imports
911
from util import *
@@ -16,7 +18,9 @@
1618
"""
1719

1820

19-
def check_validators(problem):
21+
def check_validators(
22+
problem: Problem,
23+
) -> tuple[set[int | float], list[str | tuple[int | float, str, int | float]]]:
2024
in_constraints: validate.ConstraintsDict = {}
2125
ans_constraints: validate.ConstraintsDict = {}
2226
problem.validate_data(validate.Mode.INPUT, constraints=in_constraints)
@@ -27,10 +31,10 @@ def check_validators(problem):
2731
log("No constraint validation of answer values found in answer or output validators.")
2832
print()
2933

30-
validator_values = set()
34+
validator_values: set[int | float] = set()
3135
validator_defs: list[str | tuple[int | float, str, int | float]] = []
3236

33-
def f(cs):
37+
def f(cs: validate.ConstraintsDict) -> None:
3438
for loc, value in sorted(cs.items()):
3539
name, has_low, has_high, vmin, vmax, low, high = value
3640
validator_defs.append((low, name, high))
@@ -45,12 +49,12 @@ def f(cs):
4549
return validator_values, validator_defs
4650

4751

48-
def check_statement(problem, language):
52+
def check_statement(problem: Problem, language: str) -> tuple[set[int | float], list[str]]:
4953
statement_file = problem.path / latex.PdfType.PROBLEM.path(language)
5054
statement = statement_file.read_text()
5155

52-
statement_values = set()
53-
statement_defs = []
56+
statement_values: set[int | float] = set()
57+
statement_defs: list[str] = []
5458

5559
defines = ["\\def", "\\newcommand"]
5660
sections = ["Input", "Output", "Interaction"]
@@ -67,15 +71,16 @@ def check_statement(problem, language):
6771
}
6872
relations = re.compile(r"(<=|!=|>=|<|=|>)")
6973

70-
def math_eval(text):
74+
def math_eval(text: str) -> Optional[int | float]:
7175
try:
7276
# eval is dangerous, but on the other hand we run submission code so this is fine
7377
text = text.replace("^", "**")
74-
return eval(text, {"__builtin__": None})
78+
value = eval(text, {"__builtin__": None})
79+
return value if isinstance(value, (int, float)) else None
7580
except (SyntaxError, NameError, TypeError, ZeroDivisionError):
7681
return None
7782

78-
def constraint(text):
83+
def constraint(text: str) -> None:
7984
# handles $$math$$
8085
if len(text) == 0:
8186
return
@@ -132,13 +137,13 @@ def constraint(text):
132137
in_io = False
133138
end = None
134139

135-
def matches(text):
140+
def matches(text: str) -> bool:
136141
nonlocal pos
137142
if pos + len(text) > len(statement):
138143
return False
139144
return statement[pos : pos + len(text)] == text
140145

141-
def parse_group():
146+
def parse_group() -> str:
142147
nonlocal pos
143148
assert statement[pos] == "{"
144149
next = pos + 1
@@ -155,7 +160,7 @@ def parse_group():
155160
pos = next
156161
return name
157162

158-
def parse_command():
163+
def parse_command() -> str:
159164
nonlocal pos
160165
assert statement[pos] == "\\"
161166
next = pos + 1
@@ -251,16 +256,16 @@ def parse_command():
251256
return statement_values, statement_defs
252257

253258

254-
def check_constraints(problem):
259+
def check_constraints(problem: Problem) -> bool:
255260
validator_values, validator_defs = check_validators(problem)
256-
statement_values = defaultdict(set)
257-
statement_defs = defaultdict(set)
261+
statement_values: dict[int | float, set[str]] = defaultdict(set)
262+
statement_defs: dict[str, set[str]] = defaultdict(set)
258263
for lang in problem.statement_languages:
259264
values, defs = check_statement(problem, lang)
260-
for entry in values:
261-
statement_values[entry].add(lang)
262-
for entry in defs:
263-
statement_defs[entry].add(lang)
265+
for value_entry in values:
266+
statement_values[value_entry].add(lang)
267+
for def_entry in defs:
268+
statement_defs[def_entry].add(lang)
264269

265270
# print all the definitions.
266271
value_len = 12

bin/contest.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import config
22

33
from pathlib import Path
4+
from typing import cast, Any, Optional
45

56
from util import *
67

78
# Read the contest.yaml, if available
8-
_contest_yaml = None
9+
_contest_yaml: Optional[dict[str, Any]] = None
910

1011

11-
def contest_yaml():
12+
def contest_yaml() -> dict[str, Any]:
1213
global _contest_yaml
1314
if _contest_yaml is not None:
1415
return _contest_yaml
@@ -25,22 +26,22 @@ def contest_yaml():
2526
_problems_yaml = None
2627

2728

28-
def problems_yaml():
29+
def problems_yaml() -> Optional[list[dict[str, Any]]]:
2930
global _problems_yaml
30-
if _problems_yaml:
31-
return _problems_yaml
3231
if _problems_yaml is False:
3332
return None
33+
if _problems_yaml:
34+
return _problems_yaml
3435

3536
problemsyaml_path = Path("problems.yaml")
3637
if not problemsyaml_path.is_file():
3738
_problems_yaml = False
3839
return None
3940
_problems_yaml = read_yaml(problemsyaml_path)
40-
return _problems_yaml
41+
return cast(list[dict[str, Any]], _problems_yaml)
4142

4243

43-
def get_api():
44+
def get_api() -> str:
4445
api = config.args.api or contest_yaml().get("api")
4546
if not api:
4647
fatal(
@@ -105,7 +106,7 @@ def call_api(method, endpoint, **kwargs):
105106
return r
106107

107108

108-
def call_api_get_json(url):
109+
def call_api_get_json(url: str):
109110
r = call_api("GET", url)
110111
r.raise_for_status()
111112
try:

bin/export.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import config
12
import datetime
3+
import re
4+
import shutil
25
import sys
6+
import util
37
import yaml
4-
import re
58
import zipfile
6-
import config
7-
import util
89
from pathlib import Path
9-
from typing import Optional
10+
from typing import Any, Optional
1011

1112
from contest import *
1213
from latex import PdfType

bin/fuzz.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import run
44
import random
55
import generate
6+
import signal
67
import time
78
import threading
9+
from pathlib import Path
10+
from typing import Any, Optional
811

912
import parallel
1013
from util import *
@@ -22,9 +25,11 @@
2225

2326

2427
class GeneratorTask:
25-
def __init__(self, fuzz: "Fuzz", t, i, tmp_id):
28+
def __init__(self, fuzz: "Fuzz", t: generate.TestcaseRule, i: int, tmp_id: int):
2629
self.fuzz = fuzz
27-
self.generator = t.generator
30+
generator = t.generator
31+
assert generator is not None
32+
self.generator = generator
2833
self.solution = t.config.solution
2934
self.i = i
3035
self.tmp_id = tmp_id
@@ -37,13 +42,13 @@ def __init__(self, fuzz: "Fuzz", t, i, tmp_id):
3742
self.save_mutex = threading.Lock()
3843
self.saved = False
3944

40-
def run(self, bar):
45+
def run(self, bar: ProgressBar) -> None:
4146
if self._run(bar):
4247
self.fuzz.finish_task(self.tmp_id)
4348
else:
4449
self.fuzz.finish_task(self.tmp_id, 1 + len(self.fuzz.submissions))
4550

46-
def _run(self, bar):
51+
def _run(self, bar: ProgressBar) -> bool:
4752
# GENERATE THE TEST DATA
4853
dir = Path("fuzz") / f"tmp_id_{str(self.tmp_id)}"
4954
cwd = self.fuzz.problem.tmpdir / "tool_runs" / dir
@@ -102,7 +107,7 @@ def _run(self, bar):
102107
self.fuzz.queue.put(SubmissionTask(self, submission, testcase, self.tmp_id))
103108
return True
104109

105-
def save_test(self, bar):
110+
def save_test(self, bar: ProgressBar) -> None:
106111
if self.saved:
107112
return
108113
save = False
@@ -120,17 +125,23 @@ def save_test(self, bar):
120125

121126

122127
class SubmissionTask:
123-
def __init__(self, generator_task, submission, testcase, tmp_id):
128+
def __init__(
129+
self,
130+
generator_task: GeneratorTask,
131+
submission: run.Submission,
132+
testcase: Testcase,
133+
tmp_id: int,
134+
):
124135
self.generator_task = generator_task
125136
self.submission = submission
126137
self.testcase = testcase
127138
self.tmp_id = tmp_id
128139

129-
def run(self, bar):
140+
def run(self, bar: ProgressBar) -> None:
130141
self._run(bar)
131142
self.generator_task.fuzz.finish_task(self.tmp_id)
132143

133-
def _run(self, bar):
144+
def _run(self, bar: ProgressBar) -> None:
134145
r = run.Run(self.generator_task.fuzz.problem, self.submission, self.testcase)
135146
localbar = bar.start(f"{self.generator_task.i}: {self.submission.name}")
136147
result = r.run(localbar)
@@ -153,10 +164,11 @@ def __init__(self, problem: problem.Problem):
153164
# Filter to only keep valid rules depending on seed without duplicates from count
154165
added_testcase_rules = set()
155166

156-
def add_testcase(t):
167+
def add_testcase(t: generate.TestcaseRule) -> None:
157168
if (
158169
t.in_is_generated
159170
and t.parse_error is None
171+
and t.generator is not None
160172
and t.generator.uses_seed
161173
and t.generator.command_string.strip() not in added_testcase_rules
162174
):
@@ -175,7 +187,7 @@ def add_testcase(t):
175187
# SUBMISSIONS
176188
self.submissions = self.problem.selected_or_accepted_submissions()
177189

178-
def run(self):
190+
def run(self) -> bool:
179191
if not has_ryaml:
180192
error("Fuzzing needs the ruamel.yaml python3 library. Install python[3]-ruamel.yaml.")
181193
return False
@@ -190,7 +202,7 @@ def run(self):
190202

191203
message("Press CTRL+C to stop\n", "Fuzz", color_type=MessageType.LOG)
192204

193-
def runner(task: GeneratorTask):
205+
def runner(task: GeneratorTask | SubmissionTask) -> None:
194206
task.run(bar)
195207

196208
# config.args.no_bar = True
@@ -201,7 +213,7 @@ def runner(task: GeneratorTask):
201213
self.tasks = 0
202214
self.queue = parallel.new_queue(runner, pin=True)
203215

204-
def soft_exit(sig, frame):
216+
def soft_exit(sig: Any, frame: Any) -> None:
205217
if self.queue.aborted:
206218
fatal("Running interrupted", force=True)
207219
else:
@@ -238,7 +250,7 @@ def soft_exit(sig, frame):
238250

239251
# finish task from generator with tmp_id
240252
# also add new tasks if queue becomes too empty
241-
def finish_task(self, tmp_id=None, count=1):
253+
def finish_task(self, tmp_id: Optional[int] = None, count: int = 1) -> None:
242254
with self.queue:
243255
# return tmp_id (and reuse it if all submissions are finished)
244256
if tmp_id is not None:
@@ -257,18 +269,18 @@ def finish_task(self, tmp_id=None, count=1):
257269
self.iteration += 1
258270
# 1 new generator tasks which will also create one task per submission
259271
new_tasks = 1 + len(self.submissions)
260-
tmp_id = min(self.free_tmp_id)
261-
self.free_tmp_id.remove(tmp_id)
262-
self.tmp_id_count[tmp_id] = new_tasks
272+
new_tmp_id = min(self.free_tmp_id)
273+
self.free_tmp_id.remove(new_tmp_id)
274+
self.tmp_id_count[new_tmp_id] = new_tasks
263275
self.tasks += new_tasks
264276
self.queue.put(
265-
GeneratorTask(self, testcase_rule, self.iteration, tmp_id),
277+
GeneratorTask(self, testcase_rule, self.iteration, new_tmp_id),
266278
priority=1,
267279
)
268280

269281
# Write new rule to yaml
270282
# lock between read and write to ensure that no rule gets lost
271-
def save_test(self, command):
283+
def save_test(self, command: str) -> None:
272284
with self.generators_yaml_mutex:
273285
generators_yaml = self.problem.path / "generators/generators.yaml"
274286
data = None

0 commit comments

Comments
 (0)