Skip to content

Commit 76517b6

Browse files
author
Aleksey Nogin
committed
Merge PR #703: Fix mypy errors in pytest_ofrak and enforce it in CI
2 parents af14883 + d5e65aa commit 76517b6

12 files changed

Lines changed: 101 additions & 48 deletions

build_image.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -294,19 +294,26 @@ def create_dockerfile_finish(config: OfrakImageConfig) -> str:
294294
)
295295
dockerfile_finish_parts.append(f'RUN printf "{develop_makefile}" >> Makefile\n')
296296
dockerfile_finish_parts.append("RUN make $INSTALL_TARGET\n\n")
297-
test_names = " ".join([f"test_{package_name}" for package_name in package_names])
298-
finish_makefile = "\\n\\\n".join(
299-
[
300-
".PHONY: test " + test_names,
301-
"test: " + test_names,
302-
]
303-
+ [
304-
f"test_{package_name}:\\n\\\n\t\\$(MAKE) -C {package_name} test"
305-
for package_name in package_names
297+
if config.install_target is InstallTarget.DEVELOP:
298+
test_names = ["pytest_ofrak_inspect"] + [
299+
f"test_{package_name}" for package_name in package_names
306300
]
307-
+ ["\\n"]
308-
)
309-
dockerfile_finish_parts.append(f'RUN printf "{finish_makefile}" >> Makefile\n')
301+
test_names_str = " ".join(test_names)
302+
finish_makefile = "\\n\\\n".join(
303+
[
304+
".PHONY: test " + test_names_str,
305+
"test: " + test_names_str,
306+
"pytest_ofrak_inspect:",
307+
"\t\\$(MAKE) -C pytest_ofrak inspect",
308+
]
309+
+ [
310+
line
311+
for package_name in package_names
312+
for line in (f"test_{package_name}:", f"\t\\$(MAKE) -C {package_name} test")
313+
]
314+
+ ["\\n"]
315+
)
316+
dockerfile_finish_parts.append(f'RUN printf "{finish_makefile}" >> Makefile\n')
310317
if config.entrypoint is not None:
311318
dockerfile_finish_parts.append('SHELL ["/bin/bash", "-c"]\n')
312319
dockerfile_finish_parts.append(f"ENTRYPOINT {config.entrypoint}")

pytest_ofrak/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Changelog
2+
All notable changes to `pytest-ofrak` will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5+
6+
## [0.1.0](https://github.com/redballoonsecurity/ofrak/compare/pytest-ofrak-v0.0.0...pytest-ofrak-v0.1.0)
7+
8+
### Changed
9+
10+
- Fix mypy type errors across the package ([#703](https://github.com/redballoonsecurity/ofrak/pull/703))

pytest_ofrak/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ install:
88
.PHONY: develop
99
develop:
1010
$(PYTHON) -m pip install -e . --config-settings editable_mode=compat
11+
12+
.PHONY: inspect
13+
inspect:
14+
$(PYTHON) -m mypy

pytest_ofrak/mypy.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[mypy]
2+
files = src
3+
4+
[mypy-synthol.*]
5+
ignore_missing_imports = True
6+
7+
[mypy-xattr.*]
8+
ignore_missing_imports = True

pytest_ofrak/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def run(self):
2121

2222
setuptools.setup(
2323
name="pytest-ofrak",
24-
version="0.1.0rc0",
24+
version="0.1.0rc1",
2525
description="Pytest fixtures and tests for OFRAK",
2626
packages=setuptools.find_packages("src"),
2727
package_dir={"": "src"},

pytest_ofrak/src/pytest_ofrak/mark.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from typing import Mapping, Sequence, Type, Tuple
2+
from typing import Mapping, Sequence, Type
33
import sys
44
import pytest
55

@@ -16,7 +16,7 @@ async def _check_deps_installed(
1616
def _handle_skipif_missing_deps(
1717
deps_installed_data: Mapping[ComponentExternalTool, bool],
1818
components: Sequence[Type[AbstractComponent]],
19-
) -> Tuple[bool, str]:
19+
) -> "pytest.MarkDecorator":
2020
"""
2121
:param deps_installed_data: a precomputed mapping of all tools to their installed status
2222
:params components: the components a test case uses
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
from abc import ABC
2-
from typing import Tuple
2+
from typing import Any, Optional, Tuple, Type
33

44
from ofrak import Analyzer, Unpacker, Resource
5+
from ofrak.model.component_model import ComponentConfig
6+
from ofrak.resource_view import ResourceView
57

68

79
class MockAnalyzer(Analyzer[None, Tuple], ABC):
810
def __init__(self):
911
super().__init__(None, None, None)
1012

11-
async def analyze(self, resource: Resource, config=None) -> Tuple:
13+
async def analyze(self, resource: Resource, config: Optional[ComponentConfig] = None) -> Any:
1214
return ()
1315

1416

1517
class MockUnpacker(Unpacker[None]):
16-
targets = ()
17-
children = ()
18+
targets: Tuple[Type[ResourceView], ...] = ()
19+
children: Tuple[Type[ResourceView], ...] = ()
1820

1921
def __init__(self):
2022
super().__init__(None, None, None, None)
@@ -24,8 +26,8 @@ async def unpack(self, resource, config=None):
2426

2527

2628
class MockRunnableUnpacker(Unpacker[None]):
27-
targets = ()
28-
children = ()
29+
targets: Tuple[Type[ResourceView], ...] = ()
30+
children: Tuple[Type[ResourceView], ...] = ()
2931

3032
async def unpack(self, resource, config=None):
3133
pass

pytest_ofrak/src/pytest_ofrak/mock_library.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class ITargetsCommonOutputsA(Analyzer[None, Tuple[AbstractionAttributesA]], ABC)
9898
id = b"TargetsCommonOutputsA"
9999

100100
@abstractmethod
101-
def analyze(
101+
async def analyze(
102102
self, resource: Resource, config: Optional[ComponentConfig]
103103
) -> Tuple[AbstractionAttributesA]:
104104
raise NotImplementedError()
@@ -113,11 +113,14 @@ class ITargetsQOutputsABC(
113113
ABC,
114114
):
115115
targets = (AbstractionQ,)
116-
outputs = (AbstractionAttributesA, AbstractionAttributesB, AbstractionAttributesC)
116+
# Intentionally tests multi-element outputs tuple to exercise iteration logic
117+
# in Analyzer.get_outputs_as_attribute_types(), even though the base class
118+
# type annotation only specifies single-element tuples.
119+
outputs = (AbstractionAttributesA, AbstractionAttributesB, AbstractionAttributesC) # type: ignore[assignment]
117120
id = b"TargetsQOutputsABC"
118121

119122
@abstractmethod
120-
def analyze(
123+
async def analyze(
121124
self, resource: Resource, config: Optional[ComponentConfig]
122125
) -> Tuple[AbstractionAttributesA, AbstractionAttributesB, AbstractionAttributesC]:
123126
raise NotImplementedError()
@@ -133,19 +136,19 @@ class IWithoutImplementation(Analyzer[None, Tuple[AbstractionAttributesA]], ABC)
133136
id = b"WithoutImplementation"
134137

135138
@abstractmethod
136-
def analyze(
139+
async def analyze(
137140
self, resource: Resource, config: Optional[ComponentConfig]
138141
) -> Tuple[AbstractionAttributesA]:
139142
raise NotImplementedError()
140143

141144

142145
class AbstractionPUnpacker(MockUnpacker):
143-
targets = [AbstractionP]
146+
targets = (AbstractionP,)
144147

145148

146149
class AbstractionRUnpacker(MockUnpacker):
147-
targets = [AbstractionR]
150+
targets = (AbstractionR,)
148151

149152

150153
class AbstractionRRUnpacker(MockUnpacker):
151-
targets = [AbstractionRR]
154+
targets = (AbstractionRR,)

pytest_ofrak/src/pytest_ofrak/patterns/basic_block_unpacker.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
import os
99
from dataclasses import dataclass
10-
from typing import Dict, List, Union, Tuple
10+
from typing import Dict, List, Set, Union, Tuple
1111

1212
import pytest
1313
from ofrak.core.filesystem import File
@@ -37,9 +37,13 @@ class BasicBlockUnpackerTestCase(
3737
binary_filename: str
3838
binary_md5_digest: str
3939
basic_block_data_ranges_in_root: Dict[int, Range] # Used when created basic blocks manually
40+
# Workaround for https://github.com/python/mypy/issues/12633, fixed in mypy 0.950.
41+
# Remove these two lines once using a newer mypy.
42+
expected_results: Dict[int, List[ExpectedBasicBlockUnpackResult]]
43+
optional_results: Set[int]
4044

4145

42-
BASIC_BLOCK_UNPACKER_TEST_CASES = [
46+
BASIC_BLOCK_UNPACKER_TEST_CASES: List[BasicBlockUnpackerTestCase] = [
4347
BasicBlockUnpackerTestCase(
4448
"x64",
4549
{
@@ -1582,10 +1586,11 @@ async def unpack_verify_test_case(self, request) -> BasicBlockUnpackerTestCase:
15821586
@pytest.fixture
15831587
async def root_resource(
15841588
self,
1585-
unpack_verify_test_case: BasicBlockUnpackerTestCase,
1589+
unpack_verify_test_case: UnpackAndVerifyTestCase,
15861590
ofrak_context: OFRAKContext,
15871591
test_id: str,
15881592
) -> Resource:
1593+
assert isinstance(unpack_verify_test_case, BasicBlockUnpackerTestCase)
15891594
asset_path = os.path.join(ASSETS_DIR, unpack_verify_test_case.binary_filename)
15901595
with open(asset_path, "rb") as f:
15911596
binary_data = f.read()
@@ -1598,9 +1603,9 @@ async def verify_descendant(
15981603
instructions = await basic_block.get_instructions()
15991604

16001605
# Check that the parent complex blocks are extracted as expected
1601-
instructions_by_addr: Dict[int, ExpectedBasicBlockUnpackResult] = dict()
1606+
instructions_by_addr: Dict[int, Tuple[Instruction, ...]] = {}
16021607
for expected_instructions in specified_result:
1603-
if type(expected_instructions) is tuple:
1608+
if isinstance(expected_instructions, tuple):
16041609
instructions_by_addr[
16051610
expected_instructions[0].virtual_address
16061611
] = expected_instructions
@@ -1654,7 +1659,7 @@ async def verify_descendant(
16541659
if len(errors) > 0:
16551660
raise ValueError(*errors)
16561661

1657-
async def get_descendants_to_verify(self, unpacked_resource: Resource) -> Dict[int, Resource]:
1662+
async def get_descendants_to_verify(self, unpacked_resource: Resource) -> Dict[int, BasicBlock]:
16581663
elf = await unpacked_resource.view_as(Elf)
16591664
text_section = await elf.get_section_by_name(".text")
16601665
basic_blocks: List[BasicBlock] = list(

pytest_ofrak/src/pytest_ofrak/patterns/code_region_unpacker.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
import os.path
99
from dataclasses import dataclass
10-
from typing import List, Dict
10+
from typing import Dict, List, Set
1111

1212
import pytest
1313

@@ -30,9 +30,13 @@
3030
class CodeRegionUnpackerTestCase(UnpackAndVerifyTestCase[int, List[int]]):
3131
binary_filename: str
3232
binary_md5_digest: str
33+
# Workaround for https://github.com/python/mypy/issues/12633, fixed in mypy 0.950.
34+
# Remove these two lines once using a newer mypy.
35+
expected_results: Dict[int, List[int]]
36+
optional_results: Set[int]
3337

3438

35-
CODE_REGION_UNPACKER_TEST_CASES = [
39+
CODE_REGION_UNPACKER_TEST_CASES: List[CodeRegionUnpackerTestCase] = [
3640
CodeRegionUnpackerTestCase(
3741
"x64",
3842
{
@@ -147,10 +151,11 @@ async def unpack_verify_test_case(self, request) -> CodeRegionUnpackerTestCase:
147151
@pytest.fixture
148152
async def root_resource(
149153
self,
150-
unpack_verify_test_case: CodeRegionUnpackerTestCase,
154+
unpack_verify_test_case: UnpackAndVerifyTestCase,
151155
ofrak_context: OFRAKContext,
152156
test_id: str,
153157
) -> Resource:
158+
assert isinstance(unpack_verify_test_case, CodeRegionUnpackerTestCase)
154159
asset_path = os.path.join(ASSETS_DIR, unpack_verify_test_case.binary_filename)
155160
with open(asset_path, "rb") as f:
156161
binary_data = f.read()
@@ -169,7 +174,9 @@ async def unpack(self, root_resource: Resource):
169174
"""
170175
await root_resource.unpack_recursively(do_not_unpack=(BasicBlock,))
171176

172-
async def get_descendants_to_verify(self, unpacked_resource: Resource) -> Dict[int, Resource]:
177+
async def get_descendants_to_verify(
178+
self, unpacked_resource: Resource
179+
) -> Dict[int, ComplexBlock]:
173180
program = await unpacked_resource.view_as(Program)
174181
code_regions = await program.get_code_regions()
175182

0 commit comments

Comments
 (0)