Skip to content

Commit ada74b1

Browse files
committed
feat(target): add type hinting
1 parent f15ecab commit ada74b1

File tree

2 files changed

+74
-61
lines changed

2 files changed

+74
-61
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ install_requires =
2626
cssbeautifier
2727
fasteners
2828
ffpuppet >= 0.17.0
29+
fxpoppet >= 0.0.2
2930
FuzzManager
3031
jsbeautifier
3132
lithium-reducer >= 3.0.0

src/grizzly/target/adb_target.py

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# This Source Code Form is subject to the terms of the Mozilla Public
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4-
import os
4+
5+
from __future__ import annotations
6+
57
from logging import getLogger
68
from pathlib import Path
79
from tempfile import TemporaryDirectory, mkdtemp
10+
from typing import TYPE_CHECKING
811

912
from ffpuppet import BrowserTimeoutError, LaunchError
1013
from fxpoppet import ADBLaunchError, ADBProcess, ADBSession, Reason
@@ -16,12 +19,42 @@
1619
from .target import Result, Target, TargetLaunchError, TargetLaunchTimeout
1720
from .target_monitor import TargetMonitor
1821

22+
if TYPE_CHECKING:
23+
from collections.abc import Iterable, Mapping
24+
from typing import Any
25+
1926
__author__ = "Tyson Smith"
2027
__credits__ = ["Tyson Smith", "Jesse Schwartzentruber"]
2128

2229
LOG = getLogger("adb_target")
2330

2431

32+
class ADBMonitor(TargetMonitor):
33+
def __init__(self, proc: ADBProcess) -> None:
34+
self._proc = proc
35+
36+
def clone_log(self, log_id: str, offset: int = 0) -> None:
37+
# TODO: unused... remove
38+
return None
39+
40+
def is_healthy(self) -> bool:
41+
return self._proc.is_healthy()
42+
43+
def is_idle(self, threshold: int) -> bool:
44+
return False
45+
46+
def is_running(self) -> bool:
47+
return self._proc.is_running()
48+
49+
@property
50+
def launches(self) -> int:
51+
return self._proc.launches
52+
53+
def log_length(self, log_id: str) -> int:
54+
# TODO: This needs to be implemented
55+
return 0
56+
57+
2558
class ADBTarget(Target):
2659
SUPPORTED_ASSETS = ("prefs",)
2760

@@ -35,50 +68,58 @@ class ADBTarget(Target):
3568
"use_rr",
3669
)
3770

38-
def __init__(self, binary, launch_timeout, log_limit, memory_limit, **kwds):
71+
def __init__(
72+
self,
73+
binary: Path,
74+
launch_timeout: int,
75+
log_limit: int,
76+
memory_limit: int,
77+
**kwds: dict[str, Any],
78+
) -> None:
3979
super().__init__(binary, launch_timeout, log_limit, memory_limit)
40-
self._monitor = None
4180
# app will not close itself on Android
4281
self.forced_close = True
4382
self.use_rr = False
4483

4584
for unsupported in ("pernosco", "rr", "valgrind", "xvfb"):
46-
if kwds.pop(unsupported, False):
47-
LOG.warning("ADBTarget ignoring %r: not supported", unsupported)
85+
if kwds.pop(unsupported, None):
86+
LOG.warning("ADBTarget ignoring '%s': not supported", unsupported)
4887
if kwds:
4988
LOG.warning("ADBTarget ignoring unsupported arguments: %s", ", ".join(kwds))
5089

5190
LOG.debug("opening a session and setting up the environment")
52-
self._session = ADBSession.create(as_root=True, max_attempts=10, retry_delay=15)
53-
if self._session is None:
91+
session = ADBSession.create(as_root=True, max_attempts=10, retry_delay=15)
92+
if session is None:
5493
raise RuntimeError("Could not create ADB Session!")
94+
self._session = session
5595
self._package = ADBSession.get_package_name(self.binary)
56-
self._prefs = None
96+
if self._package is None:
97+
raise RuntimeError("Could not find package name.")
98+
self._prefs: Path | None = None
5799
self._proc = ADBProcess(self._package, self._session)
58-
self._session.symbols[self._package] = os.path.join(
59-
os.path.dirname(self.binary), "symbols"
60-
)
100+
self._monitor = ADBMonitor(self._proc)
101+
self._session.symbols[self._package] = self.binary.parent / "symbols"
61102

62-
def _cleanup(self):
103+
def _cleanup(self) -> None:
63104
with self._lock:
64105
if self._proc is not None:
65106
self._proc.cleanup()
66107
if self._session.connected:
67108
self._session.reverse_remove()
68109
self._session.disconnect()
69-
if self._prefs and os.path.isfile(self._prefs):
70-
os.remove(self._prefs)
110+
if self._prefs and self._prefs.is_file():
111+
self._prefs.unlink()
71112

72-
def close(self, force_close=False):
113+
def close(self, force_close: bool = False) -> None:
73114
with self._lock:
74115
if self._proc is not None:
75116
self._proc.close()
76117

77118
@property
78-
def closed(self):
119+
def closed(self) -> bool:
79120
return self._proc.reason is not None
80121

81-
def check_result(self, _ignored):
122+
def check_result(self, _ignored: Iterable[str]) -> Result:
82123
status = Result.NONE
83124
if not self._proc.is_healthy():
84125
self._proc.close()
@@ -92,24 +133,26 @@ def check_result(self, _ignored):
92133
status = Result.FOUND
93134
return status
94135

95-
def create_report(self, is_hang=False, unstable=False):
136+
def create_report(self, is_hang: bool = False, unstable: bool = False) -> Report:
96137
logs = Path(mkdtemp(prefix="logs_", dir=grz_tmp("logs")))
97138
self.save_logs(logs)
98139
return Report(logs, self.binary, is_hang=is_hang, unstable=unstable)
99140

100-
def dump_coverage(self, timeout=0):
101-
return NotImplementedError("Not supported")
141+
def dump_coverage(self, timeout: int = 0) -> None:
142+
raise NotImplementedError()
102143

103-
def handle_hang(self, ignore_idle=True, ignore_timeout=False):
144+
def handle_hang(
145+
self, ignore_idle: bool = True, ignore_timeout: bool = False
146+
) -> bool:
104147
# TODO: attempt to detect idle hangs?
105148
self.close()
106149
return False
107150

108-
def https(self):
151+
def https(self) -> bool:
109152
# HTTPS support is not currently supported
110153
return False
111154

112-
def launch(self, location):
155+
def launch(self, location: str) -> None:
113156
env_mod = dict(self.environ)
114157
# disabled external network connections during testing
115158
env_mod["MOZ_IN_AUTOMATION"] = "1"
@@ -133,66 +176,35 @@ def launch(self, location):
133176
raise TargetLaunchTimeout(str(exc)) from None
134177
raise TargetLaunchError(str(exc), self.create_report()) from None
135178

136-
def log_size(self):
179+
def log_size(self) -> int:
137180
LOG.debug("log_size not currently implemented")
138181
return 0
139182

140-
def merge_environment(self, extra):
183+
def merge_environment(self, extra: Mapping[str, str]) -> None:
141184
output = dict(extra)
142185
output.update(self.environ)
143186
output.update(merge_sanitizer_options(self.environ, extra=extra))
144187
self.environ = output
145188

146189
@property
147-
def monitor(self):
148-
if self._monitor is None:
149-
150-
class _ADBMonitor(TargetMonitor):
151-
# pylint: disable=no-self-argument,protected-access
152-
def clone_log(_, *_a, **_k): # pylint: disable=arguments-differ
153-
log_file = self._proc.clone_log()
154-
if log_file is None:
155-
return None
156-
try:
157-
with open(log_file, "rb") as log_fp:
158-
return log_fp.read()
159-
finally:
160-
os.remove(log_file)
161-
162-
def is_healthy(_):
163-
return self._proc.is_healthy()
164-
165-
def is_idle(self, threshold):
166-
return False
167-
168-
def is_running(_):
169-
return self._proc.is_running()
170-
171-
@property
172-
def launches(_):
173-
return self._proc.launches
174-
175-
def log_length(_, *_a): # pylint: disable=arguments-differ
176-
# TODO: This needs to be implemented
177-
return 0
178-
179-
self._monitor = _ADBMonitor()
190+
def monitor(self) -> ADBMonitor:
180191
return self._monitor
181192

182-
def process_assets(self):
193+
def process_assets(self) -> None:
183194
self._prefs = self.asset_mgr.get("prefs")
184195
# generate temporary prefs.js with prefpicker
185196
if self._prefs is None:
186197
LOG.debug("using prefpicker to generate prefs.js")
187198
with TemporaryDirectory(dir=grz_tmp("target")) as tmp_path:
188199
prefs = Path(tmp_path) / "prefs.js"
189200
template = PrefPicker.lookup_template("browser-fuzzing.yml")
201+
assert template is not None
190202
PrefPicker.load_template(template).create_prefsjs(prefs)
191203
self._prefs = self.asset_mgr.add("prefs", prefs, copy=False)
192204

193-
def reverse(self, remote, local):
205+
def reverse(self, remote: int, local: int) -> None:
194206
# remote->device, local->desktop
195207
self._session.reverse(remote, local)
196208

197-
def save_logs(self, dst):
209+
def save_logs(self, dst: Path) -> None:
198210
self._proc.save_logs(dst)

0 commit comments

Comments
 (0)