Skip to content

Commit 71c45bf

Browse files
Finish typing support and enable mypy in CI
1 parent b1fed76 commit 71c45bf

File tree

11 files changed

+316
-180
lines changed

11 files changed

+316
-180
lines changed

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ repos:
4949
- id: check-useless-excludes
5050
- repo: local
5151
hooks:
52+
- id: mypy
53+
name: mypy
54+
entry: tox -e mypy --
55+
language: system
56+
require_serial: true
57+
exclude: ^tests/
58+
types: [python]
5259
- id: pylint
5360
name: pylint
5461
entry: tox -e pylint --

pyproject.toml

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,9 @@ known_first_party = "lithium"
2222
profile = "black"
2323

2424
[tool.mypy]
25-
check_untyped_defs = true
26-
disallow_any_generics = true
27-
disallow_incomplete_defs = true
28-
disallow_subclassing_any = true
29-
disallow_untyped_calls = true
30-
disallow_untyped_decorators = true
31-
disallow_untyped_defs = true
32-
implicit_reexport = false
33-
namespace_packages = true
34-
no_implicit_optional = true
35-
python_version = "3.6"
36-
show_error_codes = true
37-
strict_equality = true
38-
warn_redundant_casts = true
39-
warn_return_any = true
40-
warn_unused_configs = true
41-
warn_unused_ignores = true
42-
43-
[[tool.mypy.overrides]]
44-
module = [
45-
"Collector.Collector",
46-
"CovReporter",
47-
"distro",
48-
"FTB",
49-
"FTB.ProgramConfiguration",
50-
"FTB.Signatures",
51-
"FTB.Signatures.CrashInfo",
52-
"fasteners",
53-
"lithium", # Lithium *may* get types soon, as of mid-2021
54-
"lithium.interestingness",
55-
"lithium.interestingness.timed_run",
56-
"lithium.interestingness.utils",
57-
"Reporter.Reporter",
58-
]
25+
strict = true
5926
ignore_missing_imports = true
27+
show_error_codes = true
6028

6129
[tool.pylint.format]
6230
max-line-length = 88
@@ -66,6 +34,7 @@ disable = [
6634
"C0330",
6735
"C0326",
6836
"bad-continuation",
37+
"duplicate-code",
6938
"fixme",
7039
"import-error",
7140
"subprocess-run-check",

src/lithium/docs/examples/arithmetic/product_divides.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
"""This tests Lithium's main "minimize" algorithm."""
88

99
import sys
10+
from typing import List
1011

1112

12-
def interesting(args: str, _temp_prefix: str) -> bool:
13+
def interesting(args: List[str], _temp_prefix: str) -> bool:
1314
"""Interesting if the product of the numbers in the file divides the argument.
1415
1516
Args:
16-
args: The first parameter.
17-
_temp_prefix: The second parameter.
17+
args: Input arguments.
18+
_temp_prefix: Temp directory prefix.
1819
1920
Returns:
2021
True if successful, False otherwise.

src/lithium/interestingness/outputs.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
import logging
1414
import os
1515
from pathlib import Path
16-
from typing import List
17-
from typing import Union
16+
from typing import List, Union
1817

1918
from . import timed_run, utils
2019

src/lithium/interestingness/repeat.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
import argparse
3434
import logging
35-
from typing import List
35+
from typing import Any, List, cast
3636

3737
from .utils import rel_or_abs_import
3838

@@ -69,7 +69,7 @@ def interesting(cli_args: List[str], temp_prefix: str) -> bool:
6969
condition_args = args.cmd_with_flags[2:]
7070

7171
if hasattr(condition_script, "init"):
72-
condition_script.init(condition_args)
72+
cast(Any, condition_script).init(condition_args)
7373

7474
# Run the program over as many iterations as intended, with desired flags, replacing
7575
# REPEATNUM where necessary.
@@ -79,7 +79,9 @@ def interesting(cli_args: List[str], temp_prefix: str) -> bool:
7979
s.replace("REPEATNUM", str(i)) for s in condition_args
8080
]
8181
log.info("Repeat number %d:", i)
82-
if condition_script.interesting(replaced_condition_args, temp_prefix):
82+
if cast(Any, condition_script).interesting(
83+
replaced_condition_args, temp_prefix
84+
):
8385
return True
8486

8587
return False

src/lithium/interestingness/timed_run.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
import sys
1515
import time
1616
from pathlib import Path
17-
from typing import BinaryIO
18-
from typing import List
19-
from typing import Union
17+
from typing import BinaryIO, Callable, Dict, List, Optional, Union
2018

2119
(CRASHED, TIMED_OUT, NORMAL, ABNORMAL, NONE) = range(5)
2220

@@ -31,7 +29,7 @@
3129
class ArgumentParser(argparse.ArgumentParser):
3230
"""Argument parser with `timeout` and `cmd_with_args`"""
3331

34-
def __init__(self, *args, **kwds) -> None:
32+
def __init__(self, *args, **kwds) -> None: # type: ignore
3533
super().__init__(*args, **kwds)
3634
self.add_argument(
3735
"-t",
@@ -68,9 +66,9 @@ def timed_run(
6866
cmd_with_args: List[str],
6967
timeout: int,
7068
log_prefix: str = "",
71-
env=None,
69+
env: Optional[Dict[str, str]] = None,
7270
inp: str = "",
73-
preexec_fn=None,
71+
preexec_fn: Optional[Callable[[], None]] = None,
7472
) -> RunData:
7573
"""If log_prefix is None, uses pipes instead of files for all output.
7674

src/lithium/interestingness/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
import sys
1616
from pathlib import Path
1717
from types import ModuleType
18-
from typing import Tuple
19-
from typing import Union
18+
from typing import Tuple, Union
2019

2120

2221
def file_contains_str(
@@ -65,7 +64,7 @@ def file_contains_regex(
6564
if match was found, and matched string
6665
"""
6766

68-
matched_str = ""
67+
matched_str = b""
6968
found = False
7069
file_contents = Path(input_file).read_bytes()
7170
found_regex = re.search(regex, file_contents, flags=re.MULTILINE)

src/lithium/reducer.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99
import os
1010
import sys
1111
from pathlib import Path
12-
from typing import Dict
13-
from typing import List
14-
from typing import Optional
12+
from types import ModuleType
13+
from typing import Any, Dict, List, Optional, Type, cast
1514

1615
import pkg_resources
1716

1817
from .interestingness.utils import rel_or_abs_import
1918
from .strategies import DEFAULT as DEFAULT_STRATEGY
19+
from .strategies import Strategy
2020
from .testcases import DEFAULT as DEFAULT_TESTCASE
21+
from .testcases import Testcase
2122
from .util import LithiumError, quantity, summary_header
2223

2324
LOG = logging.getLogger(__name__)
@@ -28,22 +29,22 @@ class Lithium:
2829

2930
def __init__(self) -> None:
3031

31-
self.strategy = None
32+
self.strategy: Optional[Strategy] = None
3233

33-
self.condition_script = None
34-
self.condition_args = None
34+
self.condition_script: Optional[ModuleType] = None
35+
self.condition_args: Optional[List[str]] = None
3536

3637
self.test_count = 0
3738
self.test_total = 0
3839

39-
self.temp_dir: Path = Path()
40+
self.temp_dir: Optional[Path] = None
4041

41-
self.testcase = None
42-
self.last_interesting = None
42+
self.testcase: Optional[Testcase] = None
43+
self.last_interesting: Optional[Testcase] = None
4344

4445
self.temp_file_count = 1
4546

46-
def main(self, argv: Optional[List[str]]) -> int:
47+
def main(self, argv: Optional[List[str]] = None) -> int:
4748
"""Main entrypoint (parse args and call `run()`)
4849
4950
Args:
@@ -69,7 +70,7 @@ def run(self) -> int:
6970
0 for successful reduction
7071
"""
7172
if hasattr(self.condition_script, "init"):
72-
self.condition_script.init(self.condition_args)
73+
cast(Any, self.condition_script).init(self.condition_args)
7374

7475
try:
7576
if self.temp_dir is None:
@@ -78,6 +79,8 @@ def run(self) -> int:
7879
"Intermediate files will be stored in %s%s.", self.temp_dir, os.sep
7980
)
8081

82+
assert self.strategy is not None
83+
assert self.testcase is not None
8184
result = self.strategy.main(
8285
self.testcase, self.interesting, self.testcase_temp_filename
8386
)
@@ -89,13 +92,13 @@ def run(self) -> int:
8992

9093
finally:
9194
if hasattr(self.condition_script, "cleanup"):
92-
self.condition_script.cleanup(self.condition_args)
95+
cast(Any, self.condition_script).cleanup(self.condition_args)
9396

9497
# Make sure we exit with an interesting testcase
9598
if self.last_interesting is not None:
9699
self.last_interesting.dump()
97100

98-
def process_args(self, argv: Optional[List[str]]) -> None:
101+
def process_args(self, argv: Optional[List[str]] = None) -> None:
99102
"""Parse command-line args and initialize self.
100103
101104
Args:
@@ -106,10 +109,12 @@ def process_args(self, argv: Optional[List[str]]) -> None:
106109
class _ArgParseTry(argparse.ArgumentParser):
107110
# pylint: disable=arguments-differ,no-self-argument
108111

109-
def exit(_, **kwds) -> None:
112+
def exit( # type: ignore[override]
113+
self, status: int = 0, message: Optional[str] = None
114+
) -> None:
110115
pass
111116

112-
def error(_, message) -> None:
117+
def error(self, message: str) -> None: # type: ignore[override]
113118
pass
114119

115120
early_parser = _ArgParseTry(add_help=False)
@@ -122,8 +127,8 @@ def error(_, message) -> None:
122127
grp_opt = parser.add_argument_group(description="Lithium options")
123128
grp_atoms = grp_opt.add_mutually_exclusive_group()
124129

125-
strategies: Dict[str, str] = {}
126-
testcase_types: Dict[str, str] = {}
130+
strategies: Dict[str, Type[Strategy]] = {}
131+
testcase_types: Dict[str, Type[Testcase]] = {}
127132
for entry_point in pkg_resources.iter_entry_points("lithium_strategies"):
128133
try:
129134
strategy_cls = entry_point.load()
@@ -174,10 +179,10 @@ def error(_, message) -> None:
174179
early_parser.add_argument(
175180
"--strategy", default=DEFAULT_STRATEGY, choices=strategies.keys()
176181
)
177-
args = early_parser.parse_known_args(argv)
178-
atom = args[0].atom if args else DEFAULT_TESTCASE
182+
early_args = early_parser.parse_known_args(argv)
183+
atom = early_args[0].atom if early_args else DEFAULT_TESTCASE
179184
self.strategy = strategies.get(
180-
args[0].strategy if args else None, strategies[DEFAULT_STRATEGY]
185+
early_args[0].strategy if early_args else None, strategies[DEFAULT_STRATEGY]
181186
)()
182187

183188
grp_opt.add_argument(
@@ -192,6 +197,7 @@ def error(_, message) -> None:
192197
"-v", "--verbose", action="store_true", help="enable verbose debug logging"
193198
)
194199
# this has already been parsed above, it's only here for the help message
200+
assert self.strategy is not None
195201
grp_opt.add_argument(
196202
"--strategy",
197203
default=self.strategy.name,
@@ -250,6 +256,9 @@ def testcase_temp_filename(
250256
if use_number:
251257
filename_stem = "%d-%s" % (self.temp_file_count, filename_stem)
252258
self.temp_file_count += 1
259+
assert self.testcase is not None
260+
assert self.testcase.extension is not None
261+
assert self.temp_dir is not None
253262
return self.temp_dir / (filename_stem + self.testcase.extension)
254263

255264
def create_temp_dir(self) -> None:
@@ -269,7 +278,7 @@ def create_temp_dir(self) -> None:
269278

270279
# If the file is still interesting after the change, changes "parts" and returns
271280
# True.
272-
def interesting(self, testcase_suggestion, write_it: bool = True) -> bool:
281+
def interesting(self, testcase_suggestion: Testcase, write_it: bool = True) -> bool:
273282
"""Test whether a testcase suggestion is interesting.
274283
275284
Args:
@@ -286,9 +295,15 @@ def interesting(self, testcase_suggestion, write_it: bool = True) -> bool:
286295
self.test_count += 1
287296
self.test_total += len(testcase_suggestion)
288297

298+
assert self.temp_dir is not None
289299
temp_prefix = str(self.temp_dir / str(self.temp_file_count))
290300

291-
inter = self.condition_script.interesting(self.condition_args, temp_prefix)
301+
assert self.condition_script is not None
302+
inter = bool(
303+
cast(Any, self.condition_script).interesting(
304+
self.condition_args, temp_prefix
305+
)
306+
)
292307

293308
# Save an extra copy of the file inside the temp directory.
294309
# This is useful if you're reducing an assertion and encounter a crash:

0 commit comments

Comments
 (0)