Kea2-python 0.0.1a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Kea2-python might be problematic. Click here for more details.
- kea2/__init__.py +4 -0
- kea2/absDriver.py +34 -0
- kea2/adbUtils.py +258 -0
- kea2/assets/fastbot-thirdpart.jar +0 -0
- kea2/assets/fastbot_configs/ADBKeyBoard.apk +0 -0
- kea2/assets/fastbot_configs/abl.strings +2 -0
- kea2/assets/fastbot_configs/awl.strings +3 -0
- kea2/assets/fastbot_configs/max.config +7 -0
- kea2/assets/fastbot_configs/max.fuzzing.strings +699 -0
- kea2/assets/fastbot_configs/max.schema.strings +1 -0
- kea2/assets/fastbot_configs/max.strings +3 -0
- kea2/assets/fastbot_configs/max.tree.pruning +27 -0
- kea2/assets/fastbot_configs/widget.block.py +22 -0
- kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- kea2/assets/framework.jar +0 -0
- kea2/assets/monkeyq.jar +0 -0
- kea2/assets/quickstart.py +90 -0
- kea2/assets/u2.jar +0 -0
- kea2/cli.py +176 -0
- kea2/keaUtils.py +535 -0
- kea2/kea_launcher.py +135 -0
- kea2/logWatcher.py +71 -0
- kea2/u2Driver.py +316 -0
- kea2/utils.py +53 -0
- kea2_python-0.0.1a0.dist-info/METADATA +433 -0
- kea2_python-0.0.1a0.dist-info/RECORD +33 -0
- kea2_python-0.0.1a0.dist-info/WHEEL +5 -0
- kea2_python-0.0.1a0.dist-info/entry_points.txt +2 -0
- kea2_python-0.0.1a0.dist-info/licenses/LICENSE +16 -0
- kea2_python-0.0.1a0.dist-info/top_level.txt +1 -0
kea2/keaUtils.py
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import threading
|
|
6
|
+
from typing import IO, Callable, Any, Dict, List, Literal, NewType, Optional, Union
|
|
7
|
+
from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult
|
|
8
|
+
import random
|
|
9
|
+
import warnings
|
|
10
|
+
from dataclasses import dataclass, asdict
|
|
11
|
+
import requests
|
|
12
|
+
from .absDriver import AbstractDriver
|
|
13
|
+
from functools import wraps
|
|
14
|
+
from time import sleep
|
|
15
|
+
from .adbUtils import push_file
|
|
16
|
+
from .logWatcher import LogWatcher
|
|
17
|
+
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
18
|
+
import types
|
|
19
|
+
PRECONDITIONS_MARKER = "preconds"
|
|
20
|
+
PROP_MARKER = "prop"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Class Typing
|
|
27
|
+
PropName = NewType("PropName", str)
|
|
28
|
+
PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
|
|
29
|
+
|
|
30
|
+
TIME_STAMP = TimeStamp().getTimeStamp()
|
|
31
|
+
LOGFILE = f"fastbot_{TIME_STAMP}.log"
|
|
32
|
+
RESFILE = f"result_{TIME_STAMP}.json"
|
|
33
|
+
|
|
34
|
+
def precondition(precond: Callable[[Any], bool]) -> Callable:
|
|
35
|
+
"""the decorator @precondition
|
|
36
|
+
|
|
37
|
+
The precondition specifies when the property could be executed.
|
|
38
|
+
A property could have multiple preconditions, each of which is specified by @precondition.
|
|
39
|
+
"""
|
|
40
|
+
def accept(f):
|
|
41
|
+
@wraps(f)
|
|
42
|
+
def precondition_wrapper(*args, **kwargs):
|
|
43
|
+
return f(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
preconds = getattr(f, PRECONDITIONS_MARKER, tuple())
|
|
46
|
+
|
|
47
|
+
setattr(precondition_wrapper, PRECONDITIONS_MARKER, preconds + (precond,))
|
|
48
|
+
|
|
49
|
+
return precondition_wrapper
|
|
50
|
+
|
|
51
|
+
return accept
|
|
52
|
+
|
|
53
|
+
def prob(p: float):
|
|
54
|
+
"""the decorator @prob
|
|
55
|
+
|
|
56
|
+
The prob specify the propbability of execution when a property is satisfied.
|
|
57
|
+
"""
|
|
58
|
+
p = float(p)
|
|
59
|
+
if not 0 < p <= 1.0:
|
|
60
|
+
raise ValueError("The propbability should between 0 and 1")
|
|
61
|
+
def accept(f):
|
|
62
|
+
@wraps(f)
|
|
63
|
+
def precondition_wrapper(*args, **kwargs):
|
|
64
|
+
return f(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
setattr(precondition_wrapper, PROP_MARKER, p)
|
|
67
|
+
|
|
68
|
+
return precondition_wrapper
|
|
69
|
+
|
|
70
|
+
return accept
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class Options:
|
|
75
|
+
"""
|
|
76
|
+
Kea and Fastbot configurations
|
|
77
|
+
"""
|
|
78
|
+
# the driver_name in script (if self.d, then d.)
|
|
79
|
+
driverName: str
|
|
80
|
+
# the driver (only U2Driver available now)
|
|
81
|
+
Driver: AbstractDriver
|
|
82
|
+
# list of package names. Specify the apps under test
|
|
83
|
+
packageNames: List[str]
|
|
84
|
+
# target device
|
|
85
|
+
serial: str = None
|
|
86
|
+
# test agent. "native" for stage 1 and "u2" for stage 1~3
|
|
87
|
+
agent: Literal["u2", "native"] = "u2"
|
|
88
|
+
# max step in exploration (availble in stage 2~3)
|
|
89
|
+
maxStep: Union[str, float] = float("inf")
|
|
90
|
+
# time(mins) for exploration
|
|
91
|
+
running_mins: int = 10
|
|
92
|
+
# time(ms) to wait when exploring the app
|
|
93
|
+
throttle: int = 200
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class PropStatistic:
|
|
98
|
+
precond_satisfied: int = 0
|
|
99
|
+
executed: int = 0
|
|
100
|
+
fail: int = 0
|
|
101
|
+
error: int = 0
|
|
102
|
+
|
|
103
|
+
class PBTTestResult(dict):
|
|
104
|
+
def __getitem__(self, key) -> PropStatistic:
|
|
105
|
+
return super().__getitem__(key)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def getFullPropName(testCase: TestCase):
|
|
109
|
+
return ".".join([
|
|
110
|
+
testCase.__module__,
|
|
111
|
+
testCase.__class__.__name__,
|
|
112
|
+
testCase._testMethodName
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
class JsonResult(TextTestResult):
|
|
116
|
+
res: PBTTestResult
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def setProperties(cls, allProperties: Dict):
|
|
120
|
+
cls.res = dict()
|
|
121
|
+
for testCase in allProperties.values():
|
|
122
|
+
cls.res[getFullPropName(testCase)] = PropStatistic()
|
|
123
|
+
|
|
124
|
+
def flushResult(self, outfile):
|
|
125
|
+
json_res = dict()
|
|
126
|
+
for propName, propStatitic in self.res.items():
|
|
127
|
+
json_res[propName] = asdict(propStatitic)
|
|
128
|
+
with open(outfile, "w", encoding="utf-8") as fp:
|
|
129
|
+
json.dump(json_res, fp, indent=4)
|
|
130
|
+
|
|
131
|
+
def addExcuted(self, test: TestCase):
|
|
132
|
+
self.res[getFullPropName(test)].executed += 1
|
|
133
|
+
|
|
134
|
+
def addPrecondSatisfied(self, test: TestCase):
|
|
135
|
+
self.res[getFullPropName(test)].precond_satisfied += 1
|
|
136
|
+
|
|
137
|
+
def addFailure(self, test, err):
|
|
138
|
+
super().addFailure(test, err)
|
|
139
|
+
self.res[getFullPropName(test)].fail += 1
|
|
140
|
+
|
|
141
|
+
def addError(self, test, err):
|
|
142
|
+
super().addError(test, err)
|
|
143
|
+
self.res[getFullPropName(test)].error += 1
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def activateFastbot(options: Options, port=None) -> threading.Thread:
|
|
147
|
+
"""
|
|
148
|
+
activate fastbot.
|
|
149
|
+
:params: options: the running setting for fastbot
|
|
150
|
+
:params: port: the listening port for script driver
|
|
151
|
+
:return: the fastbot daemon thread
|
|
152
|
+
"""
|
|
153
|
+
cur_dir = Path(__file__).parent
|
|
154
|
+
push_file(
|
|
155
|
+
Path.joinpath(cur_dir, "assets/monkeyq.jar"),
|
|
156
|
+
"/sdcard/monkeyq.jar",
|
|
157
|
+
device=options.serial
|
|
158
|
+
)
|
|
159
|
+
push_file(
|
|
160
|
+
Path.joinpath(cur_dir, "assets/fastbot-thirdpart.jar"),
|
|
161
|
+
"/sdcard/fastbot-thirdpart.jar",
|
|
162
|
+
device=options.serial,
|
|
163
|
+
)
|
|
164
|
+
push_file(
|
|
165
|
+
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
166
|
+
"/sdcard/framework.jar",
|
|
167
|
+
device=options.serial
|
|
168
|
+
)
|
|
169
|
+
push_file(
|
|
170
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a"),
|
|
171
|
+
"/data/local/tmp",
|
|
172
|
+
device=options.serial
|
|
173
|
+
)
|
|
174
|
+
push_file(
|
|
175
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a"),
|
|
176
|
+
"/data/local/tmp",
|
|
177
|
+
device=options.serial
|
|
178
|
+
)
|
|
179
|
+
push_file(
|
|
180
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86"),
|
|
181
|
+
"/data/local/tmp",
|
|
182
|
+
device=options.serial
|
|
183
|
+
)
|
|
184
|
+
push_file(
|
|
185
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64"),
|
|
186
|
+
"/data/local/tmp",
|
|
187
|
+
device=options.serial
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
t = startFastbotService(options)
|
|
191
|
+
print("[INFO] Running Fastbot...", flush=True)
|
|
192
|
+
|
|
193
|
+
return t
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def check_alive(port):
|
|
197
|
+
"""
|
|
198
|
+
check if the script driver and proxy server are alive.
|
|
199
|
+
"""
|
|
200
|
+
for _ in range(10):
|
|
201
|
+
sleep(2)
|
|
202
|
+
try:
|
|
203
|
+
requests.get(f"http://localhost:{port}/ping")
|
|
204
|
+
return
|
|
205
|
+
except requests.ConnectionError:
|
|
206
|
+
print("[INFO] waiting for connection.", flush=True)
|
|
207
|
+
pass
|
|
208
|
+
raise RuntimeError("Failed to connect fastbot")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def startFastbotService(options: Options) -> threading.Thread:
|
|
212
|
+
shell_command = [
|
|
213
|
+
"CLASSPATH=/sdcard/monkeyq.jar:/sdcard/framework.jar:/sdcard/fastbot-thirdpart.jar",
|
|
214
|
+
"exec", "app_process",
|
|
215
|
+
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
216
|
+
"-p", *options.packageNames,
|
|
217
|
+
"--agent-u2" if options.agent == "u2" else "--agent",
|
|
218
|
+
"reuseq",
|
|
219
|
+
"--running-minutes", f"{options.running_mins}",
|
|
220
|
+
"--throttle", f"{options.throttle}",
|
|
221
|
+
"-v", "-v", "-v"
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
full_cmd = ["adb"] + (["-s", options.serial] if options.serial else []) + ["shell"] + shell_command
|
|
225
|
+
|
|
226
|
+
outfile = open(LOGFILE, "w", encoding="utf-8", buffering=1)
|
|
227
|
+
|
|
228
|
+
print("[INFO] Options info: {}".format(asdict(options)), flush=True)
|
|
229
|
+
print("[INFO] Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)), flush=True)
|
|
230
|
+
print("[INFO] Fastbot log will be saved to {}".format(outfile.name), flush=True)
|
|
231
|
+
|
|
232
|
+
# process handler
|
|
233
|
+
proc = subprocess.Popen(full_cmd, stdout=outfile, stderr=outfile)
|
|
234
|
+
t = threading.Thread(target=close_on_exit, args=(proc, outfile), daemon=True)
|
|
235
|
+
t.start()
|
|
236
|
+
|
|
237
|
+
return t
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def close_on_exit(proc: subprocess.Popen, f: IO):
|
|
241
|
+
proc.wait()
|
|
242
|
+
f.close()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class KeaTestRunner(TextTestRunner):
|
|
246
|
+
|
|
247
|
+
resultclass: JsonResult
|
|
248
|
+
allProperties: PropertyStore
|
|
249
|
+
options: Options = None
|
|
250
|
+
_block_widgets_funcs = None
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def setOptions(cls, options: Options):
|
|
254
|
+
if not isinstance(options.packageNames, list) and len(options.packageNames) > 0:
|
|
255
|
+
raise ValueError("packageNames should be given in a list.")
|
|
256
|
+
if options.Driver is not None and options.agent == "native":
|
|
257
|
+
print("[Warning] Can not use any Driver when runing native mode.", flush=True)
|
|
258
|
+
options.Driver = None
|
|
259
|
+
cls.options = options
|
|
260
|
+
|
|
261
|
+
def run(self, test):
|
|
262
|
+
|
|
263
|
+
self.allProperties = dict()
|
|
264
|
+
self.collectAllProperties(test)
|
|
265
|
+
|
|
266
|
+
if len(self.allProperties) == 0:
|
|
267
|
+
print("[Warning] No property has been found.", flush=True)
|
|
268
|
+
|
|
269
|
+
JsonResult.setProperties(self.allProperties)
|
|
270
|
+
self.resultclass = JsonResult
|
|
271
|
+
|
|
272
|
+
result: JsonResult = self._makeResult()
|
|
273
|
+
registerResult(result)
|
|
274
|
+
result.failfast = self.failfast
|
|
275
|
+
result.buffer = self.buffer
|
|
276
|
+
result.tb_locals = self.tb_locals
|
|
277
|
+
|
|
278
|
+
with warnings.catch_warnings():
|
|
279
|
+
if self.warnings:
|
|
280
|
+
# if self.warnings is set, use it to filter all the warnings
|
|
281
|
+
warnings.simplefilter(self.warnings)
|
|
282
|
+
# if the filter is 'default' or 'always', special-case the
|
|
283
|
+
# warnings from the deprecated unittest methods to show them
|
|
284
|
+
# no more than once per module, because they can be fairly
|
|
285
|
+
# noisy. The -Wd and -Wa flags can be used to bypass this
|
|
286
|
+
# only when self.warnings is None.
|
|
287
|
+
if self.warnings in ["default", "always"]:
|
|
288
|
+
warnings.filterwarnings(
|
|
289
|
+
"module",
|
|
290
|
+
category=DeprecationWarning,
|
|
291
|
+
message=r"Please use assert\w+ instead.",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
t = activateFastbot(options=self.options)
|
|
295
|
+
log_watcher = LogWatcher(LOGFILE)
|
|
296
|
+
if self.options.agent == "native":
|
|
297
|
+
t.join()
|
|
298
|
+
else:
|
|
299
|
+
# initialize the result.json file
|
|
300
|
+
result.flushResult(outfile=RESFILE)
|
|
301
|
+
# setUp for the u2 driver
|
|
302
|
+
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
303
|
+
check_alive(port=self.scriptDriver.lport)
|
|
304
|
+
|
|
305
|
+
end_by_remote = False
|
|
306
|
+
step = 0
|
|
307
|
+
while step < self.options.maxStep:
|
|
308
|
+
|
|
309
|
+
step += 1
|
|
310
|
+
print("[INFO] Sending monkeyEvent {}".format(
|
|
311
|
+
f"({step} / {self.options.maxStep})" if self.options.maxStep != float("inf")
|
|
312
|
+
else f"({step})"
|
|
313
|
+
)
|
|
314
|
+
, flush=True)
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
propsSatisfiedPrecond = self.getValidProperties()
|
|
318
|
+
except requests.ConnectionError:
|
|
319
|
+
print(
|
|
320
|
+
"[INFO] Exploration times up (--running-minutes)."
|
|
321
|
+
, flush=True)
|
|
322
|
+
end_by_remote = True
|
|
323
|
+
break
|
|
324
|
+
|
|
325
|
+
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
326
|
+
|
|
327
|
+
# Go to the next round if no precond satisfied
|
|
328
|
+
if len(propsSatisfiedPrecond) == 0:
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
# get the random probability p
|
|
332
|
+
p = random.random()
|
|
333
|
+
propsNameFilteredByP = []
|
|
334
|
+
# filter the properties according to the given p
|
|
335
|
+
for propName, test in propsSatisfiedPrecond.items():
|
|
336
|
+
result.addPrecondSatisfied(test)
|
|
337
|
+
if getattr(test, "p", 1) >= p:
|
|
338
|
+
propsNameFilteredByP.append(propName)
|
|
339
|
+
|
|
340
|
+
if len(propsNameFilteredByP) == 0:
|
|
341
|
+
print("Not executed any property due to probability.", flush=True)
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
execPropName = random.choice(propsNameFilteredByP)
|
|
345
|
+
test = propsSatisfiedPrecond[execPropName]
|
|
346
|
+
# Dependency Injection. driver when doing scripts
|
|
347
|
+
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
348
|
+
setattr(test, self.options.driverName, self.scriptDriver)
|
|
349
|
+
print("execute property %s." % execPropName, flush=True)
|
|
350
|
+
|
|
351
|
+
result.addExcuted(test)
|
|
352
|
+
try:
|
|
353
|
+
test(result)
|
|
354
|
+
finally:
|
|
355
|
+
result.printErrors()
|
|
356
|
+
|
|
357
|
+
result.flushResult(outfile=RESFILE)
|
|
358
|
+
|
|
359
|
+
if not end_by_remote:
|
|
360
|
+
self.stopMonkey()
|
|
361
|
+
result.flushResult(outfile=RESFILE)
|
|
362
|
+
|
|
363
|
+
print(f"Finish sending monkey events.", flush=True)
|
|
364
|
+
log_watcher.close()
|
|
365
|
+
self.tearDown()
|
|
366
|
+
|
|
367
|
+
# Source code from unittest Runner
|
|
368
|
+
# process the result
|
|
369
|
+
expectedFails = unexpectedSuccesses = skipped = 0
|
|
370
|
+
try:
|
|
371
|
+
results = map(
|
|
372
|
+
len,
|
|
373
|
+
(result.expectedFailures, result.unexpectedSuccesses, result.skipped),
|
|
374
|
+
)
|
|
375
|
+
except AttributeError:
|
|
376
|
+
pass
|
|
377
|
+
else:
|
|
378
|
+
expectedFails, unexpectedSuccesses, skipped = results
|
|
379
|
+
|
|
380
|
+
infos = []
|
|
381
|
+
if not result.wasSuccessful():
|
|
382
|
+
self.stream.write("FAILED")
|
|
383
|
+
failed, errored = len(result.failures), len(result.errors)
|
|
384
|
+
if failed:
|
|
385
|
+
infos.append("failures=%d" % failed)
|
|
386
|
+
if errored:
|
|
387
|
+
infos.append("errors=%d" % errored)
|
|
388
|
+
else:
|
|
389
|
+
self.stream.write("OK")
|
|
390
|
+
if skipped:
|
|
391
|
+
infos.append("skipped=%d" % skipped)
|
|
392
|
+
if expectedFails:
|
|
393
|
+
infos.append("expected failures=%d" % expectedFails)
|
|
394
|
+
if unexpectedSuccesses:
|
|
395
|
+
infos.append("unexpected successes=%d" % unexpectedSuccesses)
|
|
396
|
+
if infos:
|
|
397
|
+
self.stream.writeln(" (%s)" % (", ".join(infos),))
|
|
398
|
+
else:
|
|
399
|
+
self.stream.write("\n")
|
|
400
|
+
self.stream.flush()
|
|
401
|
+
return result
|
|
402
|
+
|
|
403
|
+
def stepMonkey(self) -> str:
|
|
404
|
+
"""
|
|
405
|
+
send a step monkey request to the server and get the xml string.
|
|
406
|
+
"""
|
|
407
|
+
block_widgets = self._getBlockedWidgets()
|
|
408
|
+
r = requests.get(f"http://localhost:{self.scriptDriver.lport}/stepMonkey")
|
|
409
|
+
|
|
410
|
+
res = json.loads(r.content)
|
|
411
|
+
xml_raw = res["result"]
|
|
412
|
+
return xml_raw
|
|
413
|
+
|
|
414
|
+
def stopMonkey(self) -> str:
|
|
415
|
+
"""
|
|
416
|
+
send a stop monkey request to the server and get the xml string.
|
|
417
|
+
"""
|
|
418
|
+
r = requests.get(f"http://localhost:{self.scriptDriver.lport}/stopMonkey")
|
|
419
|
+
|
|
420
|
+
res = r.content.decode(encoding="utf-8")
|
|
421
|
+
print(f"[Server INFO] {res}", flush=True)
|
|
422
|
+
|
|
423
|
+
def getValidProperties(self) -> PropertyStore:
|
|
424
|
+
|
|
425
|
+
xml_raw = self.stepMonkey()
|
|
426
|
+
staticCheckerDriver = self.options.Driver.getStaticChecker(hierarchy=xml_raw)
|
|
427
|
+
|
|
428
|
+
validProps: PropertyStore = dict()
|
|
429
|
+
for propName, test in self.allProperties.items():
|
|
430
|
+
valid = True
|
|
431
|
+
prop = getattr(test, propName)
|
|
432
|
+
# check if all preconds passed
|
|
433
|
+
for precond in prop.preconds:
|
|
434
|
+
# Dependency injection. Static driver checker for precond
|
|
435
|
+
setattr(test, self.options.driverName, staticCheckerDriver)
|
|
436
|
+
# excecute the precond
|
|
437
|
+
if not precond(test):
|
|
438
|
+
valid = False
|
|
439
|
+
break
|
|
440
|
+
# if all the precond passed. make it the candidate prop.
|
|
441
|
+
if valid:
|
|
442
|
+
validProps[propName] = test
|
|
443
|
+
return validProps
|
|
444
|
+
|
|
445
|
+
def collectAllProperties(self, test: TestSuite):
|
|
446
|
+
"""collect all the properties to prepare for PBT
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
def remove_setUp(testCase: TestCase):
|
|
450
|
+
"""remove the setup function in PBT
|
|
451
|
+
"""
|
|
452
|
+
def setUp(self): ...
|
|
453
|
+
testCase.setUp = types.MethodType(setUp, testCase)
|
|
454
|
+
|
|
455
|
+
def remove_tearDown(testCase: TestCase):
|
|
456
|
+
"""remove the tearDown function in PBT
|
|
457
|
+
"""
|
|
458
|
+
def tearDown(self): ...
|
|
459
|
+
testCase = types.MethodType(tearDown, testCase)
|
|
460
|
+
|
|
461
|
+
def iter_tests(suite):
|
|
462
|
+
for test in suite:
|
|
463
|
+
if isinstance(test, TestSuite):
|
|
464
|
+
yield from iter_tests(test)
|
|
465
|
+
else:
|
|
466
|
+
yield test
|
|
467
|
+
|
|
468
|
+
# Traverse the TestCase to get all properties
|
|
469
|
+
for t in iter_tests(test):
|
|
470
|
+
testMethodName = t._testMethodName
|
|
471
|
+
# get the test method name and check if it's a property
|
|
472
|
+
testMethod = getattr(t, testMethodName)
|
|
473
|
+
if hasattr(testMethod, PRECONDITIONS_MARKER):
|
|
474
|
+
# remove the hook func in its TestCase
|
|
475
|
+
remove_setUp(t)
|
|
476
|
+
remove_tearDown(t)
|
|
477
|
+
# save it into allProperties for PBT
|
|
478
|
+
self.allProperties[testMethodName] = t
|
|
479
|
+
print(f"[INFO] Load property: {getFullPropName(t)}", flush=True)
|
|
480
|
+
|
|
481
|
+
@property
|
|
482
|
+
def blockList(self):
|
|
483
|
+
if self._block_widgets_funcs is None:
|
|
484
|
+
self._block_widgets_funcs = list()
|
|
485
|
+
root_dir = getProjectRoot()
|
|
486
|
+
if root_dir is None or not os.path.exists(
|
|
487
|
+
file_block_widgets := root_dir / "configs" / "widget.block.py"
|
|
488
|
+
):
|
|
489
|
+
print(f"[WARNING] widget.block.py not find", flush=True)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def __get_block_widgets_module():
|
|
493
|
+
import importlib.util
|
|
494
|
+
module_name = "block_widgets"
|
|
495
|
+
spec = importlib.util.spec_from_file_location(module_name, file_block_widgets)
|
|
496
|
+
mod = importlib.util.module_from_spec(spec)
|
|
497
|
+
spec.loader.exec_module(mod)
|
|
498
|
+
return mod
|
|
499
|
+
|
|
500
|
+
mod = __get_block_widgets_module()
|
|
501
|
+
|
|
502
|
+
import inspect
|
|
503
|
+
for func_name, func in inspect.getmembers(mod, inspect.isfunction):
|
|
504
|
+
if func_name.startswith("block_") or func_name == "global_block_widgets":
|
|
505
|
+
if getattr(func, PRECONDITIONS_MARKER, None) is None:
|
|
506
|
+
if func_name.startswith("block_"):
|
|
507
|
+
logger.warning(f"No precondition in block widget function: {func_name}. Default globally active.")
|
|
508
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True, ))
|
|
509
|
+
self._block_widgets_funcs.append(func)
|
|
510
|
+
|
|
511
|
+
return self._block_widgets_funcs
|
|
512
|
+
|
|
513
|
+
def _getBlockedWidgets(self):
|
|
514
|
+
blocked_widgets = list()
|
|
515
|
+
for func in self.blockList:
|
|
516
|
+
try:
|
|
517
|
+
script_driver = self.options.Driver.getScriptDriver()
|
|
518
|
+
preconds = getattr(func, PRECONDITIONS_MARKER)
|
|
519
|
+
if all([precond(script_driver) for precond in preconds]):
|
|
520
|
+
_widgets = func(self.options.Driver.getStaticChecker())
|
|
521
|
+
if not isinstance(_widgets, list):
|
|
522
|
+
_widgets = [_widgets]
|
|
523
|
+
blocked_widgets.extend([
|
|
524
|
+
w._getXPath(w.selector) for w in _widgets
|
|
525
|
+
])
|
|
526
|
+
except Exception as e:
|
|
527
|
+
logger.error(f"error when getting blocked widgets: {e}")
|
|
528
|
+
import traceback
|
|
529
|
+
traceback.print_exc()
|
|
530
|
+
|
|
531
|
+
return blocked_widgets
|
|
532
|
+
|
|
533
|
+
def tearDown(self):
|
|
534
|
+
# TODO Add tearDown method (remove local port, etc.)
|
|
535
|
+
pass
|
kea2/kea_launcher.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import List
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
def _set_driver_parser(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]"):
|
|
6
|
+
parser = subparsers.add_parser("driver", help="Driver Settings")
|
|
7
|
+
parser.add_argument(
|
|
8
|
+
"-s",
|
|
9
|
+
"--serial",
|
|
10
|
+
dest="serial",
|
|
11
|
+
type=str,
|
|
12
|
+
help="The serial of your device. Can be found with `adb devices`",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"-p",
|
|
17
|
+
"--packages",
|
|
18
|
+
dest="package_names",
|
|
19
|
+
nargs="+",
|
|
20
|
+
type=str,
|
|
21
|
+
required=True,
|
|
22
|
+
help="The target package names com.example.app",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--agent",
|
|
27
|
+
dest="agent",
|
|
28
|
+
type=str,
|
|
29
|
+
default="u2",
|
|
30
|
+
choices=["native", "u2"],
|
|
31
|
+
help="Running native fastbot or u2-fastbot. (Only u2-fastbot support PBT)",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--running-minutes",
|
|
36
|
+
dest="running_minutes",
|
|
37
|
+
type=int,
|
|
38
|
+
required=False,
|
|
39
|
+
help="Time to run fastbot",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--max-step",
|
|
44
|
+
dest="max_step",
|
|
45
|
+
type=int,
|
|
46
|
+
required=False,
|
|
47
|
+
help="maxium monkey events count to send",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--throttle",
|
|
52
|
+
dest="throttle_ms",
|
|
53
|
+
type=int,
|
|
54
|
+
required=False,
|
|
55
|
+
help="The pause between two monkey event.",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--driver-name",
|
|
60
|
+
dest="driver_name",
|
|
61
|
+
type=str,
|
|
62
|
+
required=False,
|
|
63
|
+
help="The name of driver in script.",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"extra",
|
|
68
|
+
nargs=argparse.REMAINDER,
|
|
69
|
+
help="Extra args for unittest <args>",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def unittest_info_logger(args):
|
|
74
|
+
if args.agent == "native":
|
|
75
|
+
print("[Warning] Property not availble in native agent.", flush=True)
|
|
76
|
+
if args.extra and args.extra[0] == "unittest":
|
|
77
|
+
print("Captured unittest args:", args.extra, flush=True)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def driver_info_logger(args):
|
|
81
|
+
print("[INFO] Driver Settings:", flush=True)
|
|
82
|
+
if args.serial:
|
|
83
|
+
print(" serial:", args.serial, flush=True)
|
|
84
|
+
if args.package_names:
|
|
85
|
+
print(" package_names:", args.package_names, flush=True)
|
|
86
|
+
if args.agent:
|
|
87
|
+
print(" agent:", args.agent, flush=True)
|
|
88
|
+
if args.running_minutes:
|
|
89
|
+
print(" running_minutes:", args.running_minutes, flush=True)
|
|
90
|
+
if args.throttle_ms:
|
|
91
|
+
print(" throttle_ms:", args.throttle_ms, flush=True)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def parse_args(argv: List):
|
|
95
|
+
parser = argparse.ArgumentParser(description="Kea2")
|
|
96
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
97
|
+
|
|
98
|
+
_set_driver_parser(subparsers)
|
|
99
|
+
if len(argv) == 0:
|
|
100
|
+
argv.append("-h")
|
|
101
|
+
args = parser.parse_args(argv)
|
|
102
|
+
driver_info_logger(args)
|
|
103
|
+
unittest_info_logger(args)
|
|
104
|
+
return args
|
|
105
|
+
|
|
106
|
+
def run(argv=None):
|
|
107
|
+
import sys
|
|
108
|
+
if argv is None:
|
|
109
|
+
argv = sys.argv
|
|
110
|
+
args = parse_args(argv[1:])
|
|
111
|
+
|
|
112
|
+
from kea2 import KeaTestRunner, Options
|
|
113
|
+
from kea2.u2Driver import U2Driver
|
|
114
|
+
options = Options(
|
|
115
|
+
agent=args.agent,
|
|
116
|
+
driverName=args.driver_name,
|
|
117
|
+
Driver=U2Driver,
|
|
118
|
+
packageNames=args.package_names,
|
|
119
|
+
serial=args.serial,
|
|
120
|
+
running_mins=args.running_minutes if args.running_minutes else 10,
|
|
121
|
+
maxStep=args.max_step if args.max_step else 500,
|
|
122
|
+
throttle=args.throttle_ms if args.throttle_ms else 200
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
KeaTestRunner.setOptions(options)
|
|
126
|
+
unittest_args = []
|
|
127
|
+
if args.extra and args.extra[0] == "unittest":
|
|
128
|
+
unittest_args = args.extra[1:]
|
|
129
|
+
sys.argv = ["python3 -m unittest"] + unittest_args
|
|
130
|
+
|
|
131
|
+
unittest.main(module=None, testRunner=KeaTestRunner)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
run()
|