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/utils.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import traceback
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Callable, Dict, Optional, Union
|
|
9
|
+
from unittest import TestCase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def singleton(cls):
|
|
13
|
+
_instance = {}
|
|
14
|
+
|
|
15
|
+
def inner():
|
|
16
|
+
if cls not in _instance:
|
|
17
|
+
_instance[cls] = cls()
|
|
18
|
+
return _instance[cls]
|
|
19
|
+
return inner
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LoggingLevel:
|
|
23
|
+
level = logging.INFO
|
|
24
|
+
_instance: Optional["LoggingLevel"] = None # 单例缓存
|
|
25
|
+
|
|
26
|
+
def __new__(cls):
|
|
27
|
+
if cls._instance is None:
|
|
28
|
+
cls._instance = super().__new__(cls)
|
|
29
|
+
return cls._instance
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def set_level(cls, level: int):
|
|
33
|
+
cls.level = level
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DynamicLevelFilter(logging.Filter):
|
|
37
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
38
|
+
return record.levelno >= LoggingLevel.level
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def getLogger(name: str) -> logging.Logger:
|
|
42
|
+
logger = logging.getLogger(name)
|
|
43
|
+
|
|
44
|
+
def enable_pretty_logging():
|
|
45
|
+
if not logger.handlers:
|
|
46
|
+
# Configure handler
|
|
47
|
+
handler = logging.StreamHandler()
|
|
48
|
+
handler.flush = lambda: handler.stream.flush()
|
|
49
|
+
handler.setFormatter(logging.Formatter('[%(levelname)1s][%(asctime)s %(module)s:%(lineno)d pid:%(process)d] %(message)s'))
|
|
50
|
+
handler.setLevel(logging.NOTSET)
|
|
51
|
+
handler.addFilter(DynamicLevelFilter())
|
|
52
|
+
logger.addHandler(handler)
|
|
53
|
+
logger.setLevel(logging.DEBUG)
|
|
54
|
+
logger.propagate = False
|
|
55
|
+
|
|
56
|
+
enable_pretty_logging()
|
|
57
|
+
return logger
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
logger = getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@singleton
|
|
66
|
+
class TimeStamp:
|
|
67
|
+
time_stamp = None
|
|
68
|
+
|
|
69
|
+
def getTimeStamp(cls):
|
|
70
|
+
if cls.time_stamp is None:
|
|
71
|
+
import datetime
|
|
72
|
+
cls.time_stamp = datetime.datetime.now().strftime('%Y%m%d%H_%M%S%f')
|
|
73
|
+
return cls.time_stamp
|
|
74
|
+
|
|
75
|
+
def getCurrentTimeStamp(cls):
|
|
76
|
+
import datetime
|
|
77
|
+
return datetime.datetime.now().strftime('%Y%m%d%H_%M%S%f')
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
from uiautomator2 import Device
|
|
81
|
+
d = Device
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_CUSTOM_PROJECT_ROOT: Optional[Path] = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def setCustomProjectRoot(configs_path: Optional[Union[str, Path]]):
|
|
88
|
+
"""
|
|
89
|
+
Set a custom project root directory (containing the configs directory). Passing None can restore the default behavior.
|
|
90
|
+
"""
|
|
91
|
+
global _CUSTOM_PROJECT_ROOT
|
|
92
|
+
|
|
93
|
+
if configs_path is None:
|
|
94
|
+
_CUSTOM_PROJECT_ROOT = None
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
candidate = Path(configs_path).expanduser()
|
|
98
|
+
if candidate.name == "configs":
|
|
99
|
+
candidate = candidate.parent
|
|
100
|
+
|
|
101
|
+
candidate = candidate.resolve()
|
|
102
|
+
_CUSTOM_PROJECT_ROOT = candidate
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def getProjectRoot():
|
|
106
|
+
if _CUSTOM_PROJECT_ROOT:
|
|
107
|
+
return _CUSTOM_PROJECT_ROOT
|
|
108
|
+
|
|
109
|
+
root = Path(Path.cwd().anchor)
|
|
110
|
+
cur_dir = Path.absolute(Path(os.curdir))
|
|
111
|
+
while not os.path.isdir(cur_dir / "configs"):
|
|
112
|
+
if cur_dir == root:
|
|
113
|
+
return None
|
|
114
|
+
cur_dir = cur_dir.parent
|
|
115
|
+
return cur_dir
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def timer(log_info: str=None):
|
|
119
|
+
""" ### Decorator to measure the execution time of a function.
|
|
120
|
+
|
|
121
|
+
This decorator can be used to wrap functions where you want to log the time taken for execution
|
|
122
|
+
|
|
123
|
+
### Usage:
|
|
124
|
+
- @timer("Function execution took %cost_time seconds.")
|
|
125
|
+
- @timer() # If no log_info is provided, it will print the function name and execution time.
|
|
126
|
+
|
|
127
|
+
`%cost_time` will be replaced with the actual time taken for execution.
|
|
128
|
+
"""
|
|
129
|
+
def accept(func):
|
|
130
|
+
@wraps(func)
|
|
131
|
+
def wrapper(*args, **kwargs):
|
|
132
|
+
start_time = time.time()
|
|
133
|
+
result = func(*args, **kwargs)
|
|
134
|
+
end_time = time.time()
|
|
135
|
+
if log_info:
|
|
136
|
+
logger.info(log_info.replace(r"%cost_time", f"{end_time - start_time:.4f}"))
|
|
137
|
+
else:
|
|
138
|
+
logger.info(f"Function '{func.__name__}' executed in {(end_time - start_time):.4f} seconds.")
|
|
139
|
+
return result
|
|
140
|
+
return wrapper
|
|
141
|
+
return accept
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def catchException(log_info: str):
|
|
145
|
+
""" ### Decorator to catch exceptions and print log info.
|
|
146
|
+
|
|
147
|
+
This decorator can be used to wrap functions that may raise exceptions,
|
|
148
|
+
allowing you to log a message when the exception is raised.
|
|
149
|
+
|
|
150
|
+
### Usage:
|
|
151
|
+
- @catchException("An error occurred in the function ****.")
|
|
152
|
+
"""
|
|
153
|
+
def accept(func):
|
|
154
|
+
@wraps(func)
|
|
155
|
+
def wrapper(*args, **kwargs):
|
|
156
|
+
try:
|
|
157
|
+
return func(*args, **kwargs)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.info(log_info)
|
|
160
|
+
tb = traceback.format_exception(type(e), e, e.__traceback__.tb_next)
|
|
161
|
+
print(''.join(tb), end='', flush=True)
|
|
162
|
+
return wrapper
|
|
163
|
+
return accept
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def loadFuncsFromFile(file_path: str) -> Dict[str, Callable]:
|
|
167
|
+
if not os.path.exists(file_path):
|
|
168
|
+
raise FileNotFoundError(f"{file_path} not found.")
|
|
169
|
+
|
|
170
|
+
def __get_module():
|
|
171
|
+
import importlib.util
|
|
172
|
+
module_name = Path(file_path).stem
|
|
173
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
|
174
|
+
mod = importlib.util.module_from_spec(spec)
|
|
175
|
+
spec.loader.exec_module(mod)
|
|
176
|
+
return mod
|
|
177
|
+
|
|
178
|
+
mod = __get_module()
|
|
179
|
+
|
|
180
|
+
import inspect
|
|
181
|
+
funcs = dict()
|
|
182
|
+
for func_name, func in inspect.getmembers(mod, inspect.isfunction):
|
|
183
|
+
funcs[func_name] = func
|
|
184
|
+
|
|
185
|
+
return funcs
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def getClassName(clazz):
|
|
189
|
+
return f'%s.%s' % (clazz.__module__, clazz.__qualname__)
|
|
190
|
+
|
|
191
|
+
def getFullPropName(testCase: TestCase):
|
|
192
|
+
return f"%s.%s" % (getClassName(testCase.__class__), testCase._testMethodName)
|
kea2/version_manager.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from packaging.version import Version
|
|
7
|
+
from typing import List, Set, TypedDict
|
|
8
|
+
from importlib.metadata import version
|
|
9
|
+
|
|
10
|
+
from .utils import getLogger, getProjectRoot
|
|
11
|
+
|
|
12
|
+
logger = getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
CompatibilityInfo = TypedDict('CompatibilityInfo', {
|
|
16
|
+
"name": str,
|
|
17
|
+
"description": str,
|
|
18
|
+
"from": str,
|
|
19
|
+
"to": str,
|
|
20
|
+
})
|
|
21
|
+
VersionInfo = TypedDict('VersionInfo', {
|
|
22
|
+
"compatibility infos": List[CompatibilityInfo],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def ls_files(dir_path: Path) -> Set[Path]:
|
|
27
|
+
"""list all files in the directory"""
|
|
28
|
+
return set(f for f in dir_path.rglob('*') if f.is_file())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check_config_compatibility():
|
|
32
|
+
config_version_sanitizer = ConfigVersionSanitizer()
|
|
33
|
+
config_version_sanitizer.check_config_compatibility()
|
|
34
|
+
config_version_sanitizer.config_auto_update()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_cur_version():
|
|
38
|
+
return version("Kea2-python")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ConfigVersionSanitizer:
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self._version_infos = None
|
|
44
|
+
self._config_version = None
|
|
45
|
+
self.user_config_path = getProjectRoot() / "configs"
|
|
46
|
+
self.kea2_assets_path = Path(__file__).parent / "assets"
|
|
47
|
+
self.kea2_version = get_cur_version()
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def version_infos(self) -> VersionInfo:
|
|
51
|
+
if self._version_infos is None:
|
|
52
|
+
with open(self.kea2_assets_path / "config_version.json") as fp:
|
|
53
|
+
self._version_infos = json.load(fp)
|
|
54
|
+
return self._version_infos
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def config_version(self):
|
|
58
|
+
if self._config_version is not None:
|
|
59
|
+
return self._config_version
|
|
60
|
+
|
|
61
|
+
user_version_json = self.user_config_path / "version.json"
|
|
62
|
+
if not user_version_json.exists():
|
|
63
|
+
self._config_version = "0.3.6"
|
|
64
|
+
else:
|
|
65
|
+
with open(user_version_json) as fp:
|
|
66
|
+
self._config_version = json.load(fp)["version"]
|
|
67
|
+
return self._config_version
|
|
68
|
+
|
|
69
|
+
def check_config_compatibility(self):
|
|
70
|
+
"""Check if the user config version is compatible with the current Kea2 version."""
|
|
71
|
+
update_infos = []
|
|
72
|
+
for info in self.version_infos["compatibility infos"]:
|
|
73
|
+
if Version(info["from"]) > Version(self.config_version):
|
|
74
|
+
update_infos.append(info)
|
|
75
|
+
|
|
76
|
+
if not update_infos:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
logger.warning("Configuration update required! Please update your configuration files.")
|
|
80
|
+
logger.warning(f"Current kea2 version {self.kea2_version}. Current config version {self.config_version}.")
|
|
81
|
+
for info in update_infos:
|
|
82
|
+
logger.info(
|
|
83
|
+
f"Since version {info['from']}: {info['description']}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def config_auto_update(self):
|
|
87
|
+
self._copy_new_configs()
|
|
88
|
+
|
|
89
|
+
def _copy_new_configs(self):
|
|
90
|
+
src = self.kea2_assets_path / "fastbot_configs"
|
|
91
|
+
dst = self.user_config_path
|
|
92
|
+
|
|
93
|
+
src_files = set(os.listdir(src))
|
|
94
|
+
dst_files = set(os.listdir(dst))
|
|
95
|
+
|
|
96
|
+
new_files = src_files - dst_files
|
|
97
|
+
|
|
98
|
+
for file in new_files:
|
|
99
|
+
src_path = src / file
|
|
100
|
+
dst_path = dst / file
|
|
101
|
+
logger.info(f"Copying new config file: {file}")
|
|
102
|
+
shutil.copy2(src_path, dst_path)
|