Kea2-python 1.1.0b1__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.
- kea2/__init__.py +8 -0
- kea2/absDriver.py +56 -0
- kea2/adbUtils.py +554 -0
- kea2/assets/config_version.json +16 -0
- kea2/assets/fastbot-thirdpart.jar +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/teardown.py +18 -0
- kea2/assets/fastbot_configs/widget.block.py +38 -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/kea2-thirdpart.jar +0 -0
- kea2/assets/monkeyq.jar +0 -0
- kea2/assets/quicktest.py +126 -0
- kea2/cli.py +216 -0
- kea2/fastbotManager.py +269 -0
- kea2/kea2_api.py +166 -0
- kea2/keaUtils.py +926 -0
- kea2/kea_launcher.py +299 -0
- kea2/logWatcher.py +92 -0
- kea2/mixin.py +0 -0
- kea2/report/__init__.py +0 -0
- kea2/report/bug_report_generator.py +879 -0
- kea2/report/mixin.py +496 -0
- kea2/report/report_merger.py +1066 -0
- kea2/report/templates/bug_report_template.html +4028 -0
- kea2/report/templates/merged_bug_report_template.html +3602 -0
- kea2/report/utils.py +10 -0
- kea2/result.py +257 -0
- kea2/resultSyncer.py +65 -0
- kea2/state.py +22 -0
- kea2/typedefs.py +32 -0
- kea2/u2Driver.py +612 -0
- kea2/utils.py +192 -0
- kea2/version_manager.py +102 -0
- kea2_python-1.1.0b1.dist-info/METADATA +447 -0
- kea2_python-1.1.0b1.dist-info/RECORD +49 -0
- kea2_python-1.1.0b1.dist-info/WHEEL +5 -0
- kea2_python-1.1.0b1.dist-info/entry_points.txt +2 -0
- kea2_python-1.1.0b1.dist-info/licenses/LICENSE +16 -0
- kea2_python-1.1.0b1.dist-info/top_level.txt +1 -0
kea2/report/utils.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
|
|
4
|
+
@contextmanager
|
|
5
|
+
def thread_pool(max_workers=128, wait=True, name_prefix="worker"):
|
|
6
|
+
executor = ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix=name_prefix)
|
|
7
|
+
try:
|
|
8
|
+
yield executor
|
|
9
|
+
finally:
|
|
10
|
+
executor.shutdown(wait=wait)
|
kea2/result.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from dataclasses import asdict
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import json
|
|
4
|
+
from typing import Deque, Dict
|
|
5
|
+
from unittest import TestCase, TextTestResult
|
|
6
|
+
from collections import deque
|
|
7
|
+
|
|
8
|
+
from .state import INVARIANT_MARKER
|
|
9
|
+
from .typedefs import PBTTestResult, PropertyExecutionInfo, PropStatistic
|
|
10
|
+
from .utils import getLogger, getFullPropName
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CheckKind(Enum):
|
|
17
|
+
PROPERTY = "property"
|
|
18
|
+
INVARIANT = "invariant"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_check_kind(test: TestCase) -> CheckKind:
|
|
22
|
+
if hasattr(test, INVARIANT_MARKER):
|
|
23
|
+
return CheckKind.INVARIANT
|
|
24
|
+
return CheckKind.PROPERTY
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class KeaJsonResult(TextTestResult):
|
|
28
|
+
|
|
29
|
+
# +------------------------------+
|
|
30
|
+
# | Setup utils |
|
|
31
|
+
# +------------------------------+
|
|
32
|
+
res: PBTTestResult = dict()
|
|
33
|
+
lastPropertyInfo: PropertyExecutionInfo
|
|
34
|
+
lastInvariantInfo: PropertyExecutionInfo
|
|
35
|
+
executionInfoBuffer: Deque["PropertyExecutionInfo"] = deque()
|
|
36
|
+
currentStepsCount: int
|
|
37
|
+
result_file: str
|
|
38
|
+
property_exection_result_file: str
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def setProperties(cls, allProperties: Dict):
|
|
42
|
+
for testCase in allProperties.values():
|
|
43
|
+
cls.res[getFullPropName(testCase)] = PropStatistic(kind=CheckKind.PROPERTY.value)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def setInvariants(cls, allInvariants: Dict):
|
|
47
|
+
for testCase in allInvariants.values():
|
|
48
|
+
cls.res[getFullPropName(testCase)] = PropStatistic(kind=CheckKind.INVARIANT.value)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def setOutputFile(cls, result_file, property_exec_result_file):
|
|
52
|
+
cls.result_file = result_file
|
|
53
|
+
cls.property_exection_result_file = property_exec_result_file
|
|
54
|
+
|
|
55
|
+
def __init__(self, stream, descriptions, verbosity):
|
|
56
|
+
super().__init__(stream, descriptions, verbosity)
|
|
57
|
+
self.showAll = True
|
|
58
|
+
self.currentStepsCount = 0
|
|
59
|
+
self.lastInvariantInfo = None
|
|
60
|
+
|
|
61
|
+
def startTest(self, test):
|
|
62
|
+
if get_check_kind(test) is CheckKind.INVARIANT:
|
|
63
|
+
self.lastInvariantInfo = PropertyExecutionInfo(
|
|
64
|
+
propName=getFullPropName(test),
|
|
65
|
+
kind=CheckKind.INVARIANT.value,
|
|
66
|
+
state="start",
|
|
67
|
+
tb="",
|
|
68
|
+
startStepsCount=self.currentStepsCount
|
|
69
|
+
)
|
|
70
|
+
super(TextTestResult, self).startTest(test)
|
|
71
|
+
if self.showAll:
|
|
72
|
+
self.stream.write(" - ")
|
|
73
|
+
self.stream.write(str(test))
|
|
74
|
+
self.stream.write(" ... ")
|
|
75
|
+
self.stream.flush()
|
|
76
|
+
self._newline = False
|
|
77
|
+
|
|
78
|
+
def setCurrentStepsCount(self, stepsCount: int):
|
|
79
|
+
self.currentStepsCount = stepsCount
|
|
80
|
+
|
|
81
|
+
# +------------------------------+
|
|
82
|
+
# | Property-specific execution |
|
|
83
|
+
# +------------------------------+
|
|
84
|
+
def addExcutedProperty(self, test: TestCase, stepsCount: int):
|
|
85
|
+
self.res[getFullPropName(test)].executed += 1
|
|
86
|
+
|
|
87
|
+
self.lastPropertyInfo = PropertyExecutionInfo(
|
|
88
|
+
propName=getFullPropName(test),
|
|
89
|
+
kind=CheckKind.PROPERTY.value,
|
|
90
|
+
state="start",
|
|
91
|
+
tb="",
|
|
92
|
+
startStepsCount=stepsCount
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def addPropertyPrecondSatisfied(self, test: TestCase):
|
|
96
|
+
self.res[getFullPropName(test)].precond_satisfied += 1
|
|
97
|
+
|
|
98
|
+
def updateExecutionInfo(self, test):
|
|
99
|
+
# if the test is a property, and it is still in "start" state, set it to "pass"
|
|
100
|
+
# then record the last executed property info
|
|
101
|
+
if get_check_kind(test) is CheckKind.PROPERTY:
|
|
102
|
+
if self.lastPropertyInfo.state == "start":
|
|
103
|
+
self.lastPropertyInfo.state = "pass"
|
|
104
|
+
self.executionInfoBuffer.append(self.lastPropertyInfo)
|
|
105
|
+
# if the test is an invariant, and the last invariant failed or errored, record it
|
|
106
|
+
# (only record failed/errored invariants)
|
|
107
|
+
if get_check_kind(test) is CheckKind.INVARIANT:
|
|
108
|
+
if self.lastInvariantInfo.state in {"fail", "error"}:
|
|
109
|
+
self.executionInfoBuffer.append(self.lastInvariantInfo)
|
|
110
|
+
|
|
111
|
+
def getExcutedProperty(self, test: TestCase):
|
|
112
|
+
return self.res[getFullPropName(test)].executed
|
|
113
|
+
|
|
114
|
+
# +------------------------------+
|
|
115
|
+
# | Shared result updates |
|
|
116
|
+
# +------------------------------+
|
|
117
|
+
def addFailure(self, test, err):
|
|
118
|
+
super().addFailure(test, err)
|
|
119
|
+
self.res[getFullPropName(test)].fail += 1
|
|
120
|
+
self._record_exception(test, err, "fail")
|
|
121
|
+
|
|
122
|
+
def addError(self, test, err):
|
|
123
|
+
super().addError(test, err)
|
|
124
|
+
self.res[getFullPropName(test)].error += 1
|
|
125
|
+
self._record_exception(test, err, "error")
|
|
126
|
+
|
|
127
|
+
# +------------------------------+
|
|
128
|
+
# | Result utils |
|
|
129
|
+
# +------------------------------+
|
|
130
|
+
def printError(self, test):
|
|
131
|
+
self._print_property_error(test)
|
|
132
|
+
self._print_invariant_error(test)
|
|
133
|
+
|
|
134
|
+
def logSummary(self):
|
|
135
|
+
property_fails = sum(_.fail for _ in self.res.values() if _.kind == "property")
|
|
136
|
+
property_errors = sum(_.error for _ in self.res.values() if _.kind == "property")
|
|
137
|
+
invariant_fails = sum(_.fail for _ in self.res.values() if _.kind == "invariant")
|
|
138
|
+
invariant_errors = sum(_.error for _ in self.res.values() if _.kind == "invariant")
|
|
139
|
+
logger.info(f"[Property Execution Summary] Errors:{property_errors}, Fails:{property_fails}")
|
|
140
|
+
if invariant_fails > 0 or invariant_errors > 0:
|
|
141
|
+
logger.info(f"[Invariant Execution Summary] Errors:{invariant_errors}, Fails:{invariant_fails}")
|
|
142
|
+
|
|
143
|
+
def flushResult(self):
|
|
144
|
+
json_res = dict()
|
|
145
|
+
for propName, propStatitic in self.res.items():
|
|
146
|
+
json_res[propName] = asdict(propStatitic)
|
|
147
|
+
with open(self.result_file, "w", encoding="utf-8") as fp:
|
|
148
|
+
json.dump(json_res, fp, indent=4)
|
|
149
|
+
|
|
150
|
+
with open(self.property_exection_result_file, "a", encoding="utf-8") as fp:
|
|
151
|
+
while self.executionInfoBuffer:
|
|
152
|
+
execInfo = self.executionInfoBuffer.popleft()
|
|
153
|
+
fp.write(f"{json.dumps(asdict(execInfo))}\n")
|
|
154
|
+
|
|
155
|
+
# +------------------------------+
|
|
156
|
+
# | Property/invariant helpers |
|
|
157
|
+
# +------------------------------+
|
|
158
|
+
def _record_exception(self, test, err, state):
|
|
159
|
+
if get_check_kind(test) is CheckKind.PROPERTY:
|
|
160
|
+
self.lastPropertyInfo.state = state
|
|
161
|
+
self.lastPropertyInfo.tb = self._exc_info_to_string(err, test)
|
|
162
|
+
return
|
|
163
|
+
if get_check_kind(test) is CheckKind.INVARIANT:
|
|
164
|
+
self.lastInvariantInfo = PropertyExecutionInfo(
|
|
165
|
+
propName=getFullPropName(test),
|
|
166
|
+
kind=CheckKind.INVARIANT.value,
|
|
167
|
+
state=state,
|
|
168
|
+
tb=self._exc_info_to_string(err, test),
|
|
169
|
+
startStepsCount=self.currentStepsCount,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def _print_property_error(self, test):
|
|
173
|
+
if get_check_kind(test) is not CheckKind.PROPERTY:
|
|
174
|
+
return
|
|
175
|
+
if self.lastPropertyInfo.state not in ["fail", "error"]:
|
|
176
|
+
return
|
|
177
|
+
flavour = self.lastPropertyInfo.state.upper()
|
|
178
|
+
self.stream.writeln("")
|
|
179
|
+
self.stream.writeln(self.separator1)
|
|
180
|
+
self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
|
|
181
|
+
self.stream.writeln(self.separator2)
|
|
182
|
+
self.stream.writeln("%s" % self.lastPropertyInfo.tb)
|
|
183
|
+
self.stream.writeln(self.separator1)
|
|
184
|
+
self.stream.flush()
|
|
185
|
+
|
|
186
|
+
def _print_invariant_error(self, test):
|
|
187
|
+
if get_check_kind(test) is not CheckKind.INVARIANT:
|
|
188
|
+
return
|
|
189
|
+
if not self.lastInvariantInfo or self.lastInvariantInfo.state not in {"fail", "error"}:
|
|
190
|
+
return
|
|
191
|
+
self.stream.writeln("")
|
|
192
|
+
self.stream.writeln(self.separator1)
|
|
193
|
+
self.stream.writeln("%s: %s" % (self.lastInvariantInfo.state.upper(), self.lastInvariantInfo.tb))
|
|
194
|
+
self.stream.writeln(self.separator1)
|
|
195
|
+
self.stream.flush()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class KeaTextTestResult(TextTestResult):
|
|
199
|
+
|
|
200
|
+
# +------------------------------+
|
|
201
|
+
# | Setup and output formatting |
|
|
202
|
+
# +------------------------------+
|
|
203
|
+
def __init__(self, stream, descriptions, verbosity):
|
|
204
|
+
super().__init__(stream, descriptions, verbosity)
|
|
205
|
+
self.showAll = True
|
|
206
|
+
|
|
207
|
+
def getDescription(self: "KeaJsonResult", test: "TestCase"):
|
|
208
|
+
doc_first_line = test.shortDescription()
|
|
209
|
+
if get_check_kind(test) is CheckKind.INVARIANT:
|
|
210
|
+
return getFullPropName(test)
|
|
211
|
+
if self.descriptions and doc_first_line:
|
|
212
|
+
doc_first_line = "# " + doc_first_line
|
|
213
|
+
return '\n'.join((str(test), doc_first_line))
|
|
214
|
+
else:
|
|
215
|
+
return str(test)
|
|
216
|
+
|
|
217
|
+
def startTest(self: "KeaJsonResult", test):
|
|
218
|
+
if get_check_kind(test) is CheckKind.INVARIANT:
|
|
219
|
+
self.stream.writeln(f"[INFO] Invariant: {getFullPropName(test)}")
|
|
220
|
+
self.stream.flush()
|
|
221
|
+
self._newline = True
|
|
222
|
+
else:
|
|
223
|
+
self.stream.write("[INFO] Start executing property: ")
|
|
224
|
+
self.stream.writeln(self.getDescription(test))
|
|
225
|
+
self.stream.flush()
|
|
226
|
+
self._newline = True
|
|
227
|
+
|
|
228
|
+
# +------------------------------+
|
|
229
|
+
# | Shared status updates |
|
|
230
|
+
# +------------------------------+
|
|
231
|
+
@property
|
|
232
|
+
def wasFail(self):
|
|
233
|
+
return self._wasFail
|
|
234
|
+
|
|
235
|
+
def addError(self, test, err):
|
|
236
|
+
self._wasFail = True
|
|
237
|
+
return super().addError(test, err)
|
|
238
|
+
|
|
239
|
+
def addFailure(self, test, err):
|
|
240
|
+
self._wasFail = True
|
|
241
|
+
return super().addFailure(test, err)
|
|
242
|
+
|
|
243
|
+
def addSuccess(self, test):
|
|
244
|
+
self._wasFail = False
|
|
245
|
+
return super().addSuccess(test)
|
|
246
|
+
|
|
247
|
+
def addSkip(self, test, reason):
|
|
248
|
+
self._wasFail = False
|
|
249
|
+
return super().addSkip(test, reason)
|
|
250
|
+
|
|
251
|
+
def addExpectedFailure(self, test, err):
|
|
252
|
+
self._wasFail = False
|
|
253
|
+
return super().addExpectedFailure(test, err)
|
|
254
|
+
|
|
255
|
+
def addUnexpectedSuccess(self, test):
|
|
256
|
+
self._wasFail = False
|
|
257
|
+
return super().addUnexpectedSuccess(test)
|
kea2/resultSyncer.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .adbUtils import ADBDevice
|
|
6
|
+
from .utils import getLogger, catchException, timer
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .keaUtils import Options
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResultSyncer:
|
|
16
|
+
|
|
17
|
+
def __init__(self, device_output_dir, options: "Options"):
|
|
18
|
+
self.device_output_dir = device_output_dir
|
|
19
|
+
self.output_dir = options.output_dir / Path(device_output_dir).name
|
|
20
|
+
self.running = False
|
|
21
|
+
self.thread = None
|
|
22
|
+
self.sync_event = threading.Event()
|
|
23
|
+
|
|
24
|
+
ADBDevice.setDevice(serial=options.serial, transport_id=options.transport_id)
|
|
25
|
+
self.dev = ADBDevice()
|
|
26
|
+
|
|
27
|
+
def run(self):
|
|
28
|
+
"""Start a background thread to sync device data when triggered"""
|
|
29
|
+
self.running = True
|
|
30
|
+
self.thread = threading.Thread(target=self._sync_thread, daemon=True)
|
|
31
|
+
self.thread.start()
|
|
32
|
+
|
|
33
|
+
def _sync_thread(self):
|
|
34
|
+
"""Thread function that waits for sync event and then syncs data"""
|
|
35
|
+
while self.running:
|
|
36
|
+
# Wait for sync event with a timeout to periodically check if still running
|
|
37
|
+
if self.sync_event.wait(timeout=1):
|
|
38
|
+
self._sync_device_data()
|
|
39
|
+
self.sync_event.clear()
|
|
40
|
+
|
|
41
|
+
@timer("Data Sync cost %cost_time seconds")
|
|
42
|
+
def close(self):
|
|
43
|
+
self.running = False
|
|
44
|
+
self.sync_event.set()
|
|
45
|
+
if self.thread and self.thread.is_alive():
|
|
46
|
+
logger.info("Syncing result data from device. Please wait...")
|
|
47
|
+
self.thread.join(timeout=10)
|
|
48
|
+
self._sync_device_data()
|
|
49
|
+
try:
|
|
50
|
+
logger.debug(f"Removing device output directory: {self.device_output_dir}")
|
|
51
|
+
remove_device_dir = ["rm", "-rf", self.device_output_dir]
|
|
52
|
+
self.dev.shell(remove_device_dir)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"Error removing device output directory: {e}", flush=True)
|
|
55
|
+
|
|
56
|
+
@catchException("Error during device data sync.")
|
|
57
|
+
def _sync_device_data(self):
|
|
58
|
+
"""
|
|
59
|
+
Sync the device data to the local directory.
|
|
60
|
+
"""
|
|
61
|
+
logger.debug("Syncing data")
|
|
62
|
+
self.dev.sync.pull_dir(self.device_output_dir, self.output_dir, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
remove_pulled_screenshots = ["find", self.device_output_dir, "-name", '"*.png"', "-delete"]
|
|
65
|
+
self.dev.shell(remove_pulled_screenshots)
|
kea2/state.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# state.py is used for stateful testing
|
|
2
|
+
#
|
|
3
|
+
# Supports defining complex state that can be shared across multiple test cases
|
|
4
|
+
# to control test flow and dependencies
|
|
5
|
+
INVARIANT_MARKER = "_is_invariant"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class State(dict):
|
|
9
|
+
_instance = None
|
|
10
|
+
|
|
11
|
+
def __new__(cls):
|
|
12
|
+
if cls._instance is None:
|
|
13
|
+
cls._instance = super().__new__(cls)
|
|
14
|
+
return cls._instance
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
state = State()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def invariant(f):
|
|
21
|
+
setattr(f, INVARIANT_MARKER, True)
|
|
22
|
+
return f
|
kea2/typedefs.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Class Typing
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Deque, Dict, Literal, NewType
|
|
4
|
+
from unittest import TestCase
|
|
5
|
+
|
|
6
|
+
PRECONDITIONS_MARKER = "preconds"
|
|
7
|
+
PROB_MARKER = "prob"
|
|
8
|
+
MAX_TRIES_MARKER = "max_tries"
|
|
9
|
+
INTERRUPTABLE_MARKER = "interruptable"
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class PropStatistic:
|
|
13
|
+
kind: Literal["property", "invariant"] = "unknown"
|
|
14
|
+
precond_satisfied: int = 0
|
|
15
|
+
executed: int = 0
|
|
16
|
+
fail: int = 0
|
|
17
|
+
error: int = 0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class PropertyExecutionInfo:
|
|
22
|
+
startStepsCount: int
|
|
23
|
+
propName: "PropName"
|
|
24
|
+
kind: Literal["property", "invariant"]
|
|
25
|
+
state: Literal["start", "pass", "fail", "error"]
|
|
26
|
+
tb: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
PropName = NewType("PropName", str)
|
|
30
|
+
PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
|
|
31
|
+
|
|
32
|
+
PBTTestResult = NewType("PBTTestResult", Dict[PropName, PropStatistic])
|