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/fastbotManager.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
from time import sleep
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from retry import retry
|
|
9
|
+
from retry.api import retry_call
|
|
10
|
+
from uiautomator2.core import HTTPResponse, _http_request
|
|
11
|
+
from packaging.version import parse as parse_version
|
|
12
|
+
|
|
13
|
+
from .utils import getLogger, getProjectRoot
|
|
14
|
+
from .adbUtils import ADBDevice, ADBStreamShell_V2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from typing import IO, TYPE_CHECKING, Dict
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .keaUtils import Options
|
|
20
|
+
from .typedefs import PropertyExecutionInfo
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FastbotManager:
|
|
27
|
+
def __init__(self, options: "Options", log_file: str):
|
|
28
|
+
self.options:"Options" = options
|
|
29
|
+
self.log_file: str = log_file
|
|
30
|
+
self.port = None
|
|
31
|
+
self.thread = None
|
|
32
|
+
self._device_output_dir = None
|
|
33
|
+
ADBDevice.setDevice(options.serial, options.transport_id)
|
|
34
|
+
self.dev = ADBDevice()
|
|
35
|
+
self.android_release = parse_version(self.dev.getprop("ro.build.version.release"))
|
|
36
|
+
self.executed_prop = False
|
|
37
|
+
|
|
38
|
+
def _activateFastbot(self) -> ADBStreamShell_V2:
|
|
39
|
+
"""
|
|
40
|
+
activate fastbot.
|
|
41
|
+
:params: options: the running setting for fastbot
|
|
42
|
+
:params: port: the listening port for script driver
|
|
43
|
+
:return: the fastbot daemon thread
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
self._push_libs()
|
|
47
|
+
t = self._startFastbotService()
|
|
48
|
+
logger.info("Running Fastbot...")
|
|
49
|
+
return t
|
|
50
|
+
|
|
51
|
+
def check_alive(self):
|
|
52
|
+
"""
|
|
53
|
+
check if the script driver and proxy server are alive.
|
|
54
|
+
"""
|
|
55
|
+
def _check_alive_request():
|
|
56
|
+
_http_request(dev=self.dev, device_port=8090, method="GET", path="/ping")
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
logger.info("Connecting to fastbot server...")
|
|
60
|
+
retry_call(_check_alive_request, tries=10, delay=2, logger=logger)
|
|
61
|
+
logger.info("Connected to fastbot server.")
|
|
62
|
+
except requests.ConnectionError:
|
|
63
|
+
raise RuntimeError("Failed to connect fastbot")
|
|
64
|
+
|
|
65
|
+
def request(self, method: str, path: str, data: Dict=None, timeout: int=10) -> HTTPResponse:
|
|
66
|
+
return _http_request(self.dev, 8090, method, path, data, timeout)
|
|
67
|
+
|
|
68
|
+
@retry(Exception, tries=2, delay=2)
|
|
69
|
+
def init(self, options: "Options", stamp):
|
|
70
|
+
post_data = {
|
|
71
|
+
"takeScreenshots": options.take_screenshots,
|
|
72
|
+
"preFailureScreenshots": options.pre_failure_screenshots,
|
|
73
|
+
"postFailureScreenshots": options.post_failure_screenshots,
|
|
74
|
+
"logStamp": stamp,
|
|
75
|
+
"deviceOutputRoot": options.device_output_root,
|
|
76
|
+
}
|
|
77
|
+
r = _http_request(
|
|
78
|
+
self.dev,
|
|
79
|
+
device_port=8090,
|
|
80
|
+
method="POST",
|
|
81
|
+
path="/init",
|
|
82
|
+
data=post_data
|
|
83
|
+
)
|
|
84
|
+
print(f"[INFO] Init fastbot: {post_data}", flush=True)
|
|
85
|
+
import re
|
|
86
|
+
self._device_output_dir = re.match(r"outputDir:(.+)", r.text).group(1)
|
|
87
|
+
print(f"[INFO] Fastbot initiated. outputDir: {r.text}", flush=True)
|
|
88
|
+
|
|
89
|
+
@retry(Exception, tries=2, delay=2)
|
|
90
|
+
def stepMonkey(self, monkeyStepInfo):
|
|
91
|
+
r = self.request(
|
|
92
|
+
method="POST",
|
|
93
|
+
path="/stepMonkey",
|
|
94
|
+
data=monkeyStepInfo
|
|
95
|
+
)
|
|
96
|
+
return r.json()["result"]
|
|
97
|
+
|
|
98
|
+
@retry(Exception, tries=2, delay=2)
|
|
99
|
+
def stopMonkey(self):
|
|
100
|
+
"""
|
|
101
|
+
send a stop monkey request to the server.
|
|
102
|
+
"""
|
|
103
|
+
r = self.request(
|
|
104
|
+
method="GET",
|
|
105
|
+
path="/stopMonkey",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
print(f"[Server INFO] {r.text}", flush=True)
|
|
109
|
+
|
|
110
|
+
@retry(Exception, tries=2, delay=2)
|
|
111
|
+
def logScript(self, execution_info: "PropertyExecutionInfo"):
|
|
112
|
+
r = self.request(
|
|
113
|
+
method="POST",
|
|
114
|
+
path="/logScript",
|
|
115
|
+
data={
|
|
116
|
+
"propName": execution_info.propName,
|
|
117
|
+
"startStepsCount": execution_info.startStepsCount,
|
|
118
|
+
"kind": execution_info.kind,
|
|
119
|
+
"state": execution_info.state,
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
res = r.text
|
|
123
|
+
if res != "OK":
|
|
124
|
+
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
125
|
+
|
|
126
|
+
@retry(Exception, tries=2, delay=2)
|
|
127
|
+
def dumpHierarchy(self):
|
|
128
|
+
sleep(self.options.throttle / 1000)
|
|
129
|
+
r = self.request(
|
|
130
|
+
method="GET",
|
|
131
|
+
path="/dumpHierarchy",
|
|
132
|
+
)
|
|
133
|
+
return r.json()['result']
|
|
134
|
+
|
|
135
|
+
@retry(Exception, tries=2, delay=2)
|
|
136
|
+
def sendInfo(self, info: str):
|
|
137
|
+
r = self.request(
|
|
138
|
+
method="POST",
|
|
139
|
+
path="/sendInfo",
|
|
140
|
+
data=info
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def device_output_dir(self):
|
|
145
|
+
return self._device_output_dir
|
|
146
|
+
|
|
147
|
+
def _push_libs(self):
|
|
148
|
+
logger.info("Pushing Fastbot libraries to device...")
|
|
149
|
+
cur_dir = Path(__file__).parent
|
|
150
|
+
self.dev.sync.push(
|
|
151
|
+
Path.joinpath(cur_dir, "assets/monkeyq.jar"),
|
|
152
|
+
"/sdcard/monkeyq.jar"
|
|
153
|
+
)
|
|
154
|
+
self.dev.sync.push(
|
|
155
|
+
Path.joinpath(cur_dir, "assets/fastbot-thirdpart.jar"),
|
|
156
|
+
"/sdcard/fastbot-thirdpart.jar",
|
|
157
|
+
)
|
|
158
|
+
self.dev.sync.push(
|
|
159
|
+
Path.joinpath(cur_dir, "assets/kea2-thirdpart.jar"),
|
|
160
|
+
"/sdcard/kea2-thirdpart.jar",
|
|
161
|
+
)
|
|
162
|
+
self.dev.sync.push(
|
|
163
|
+
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
164
|
+
"/sdcard/framework.jar",
|
|
165
|
+
)
|
|
166
|
+
self.dev.sync.push(
|
|
167
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a/libfastbot_native.so"),
|
|
168
|
+
"/data/local/tmp/arm64-v8a/libfastbot_native.so",
|
|
169
|
+
)
|
|
170
|
+
self.dev.sync.push(
|
|
171
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a/libfastbot_native.so"),
|
|
172
|
+
"/data/local/tmp/armeabi-v7a/libfastbot_native.so",
|
|
173
|
+
)
|
|
174
|
+
self.dev.sync.push(
|
|
175
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86/libfastbot_native.so"),
|
|
176
|
+
"/data/local/tmp/x86/libfastbot_native.so",
|
|
177
|
+
)
|
|
178
|
+
self.dev.sync.push(
|
|
179
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64/libfastbot_native.so"),
|
|
180
|
+
"/data/local/tmp/x86_64/libfastbot_native.so",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
cwd = getProjectRoot()
|
|
184
|
+
whitelist = self.options.act_whitelist_file
|
|
185
|
+
blacklist = self.options.act_blacklist_file
|
|
186
|
+
if bool(whitelist) ^ bool(blacklist):
|
|
187
|
+
if whitelist:
|
|
188
|
+
file_to_push = cwd / 'configs' / 'awl.strings'
|
|
189
|
+
remote_path = whitelist
|
|
190
|
+
else:
|
|
191
|
+
file_to_push = cwd / 'configs' / 'abl.strings'
|
|
192
|
+
remote_path = blacklist
|
|
193
|
+
|
|
194
|
+
self.dev.sync.push(
|
|
195
|
+
file_to_push,
|
|
196
|
+
remote_path
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _startFastbotService(self) -> ADBStreamShell_V2:
|
|
200
|
+
shell_command = [
|
|
201
|
+
"CLASSPATH="
|
|
202
|
+
"/sdcard/monkeyq.jar:"
|
|
203
|
+
"/sdcard/framework.jar:"
|
|
204
|
+
"/sdcard/fastbot-thirdpart.jar:"
|
|
205
|
+
"/sdcard/kea2-thirdpart.jar",
|
|
206
|
+
"exec", "app_process",
|
|
207
|
+
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
208
|
+
"--agent-u2" if self.options.agent == "u2" else "--agent",
|
|
209
|
+
"reuseq",
|
|
210
|
+
"--running-minutes", f"{self.options.running_mins}",
|
|
211
|
+
"--throttle", f"{self.options.throttle}",
|
|
212
|
+
"--bugreport",
|
|
213
|
+
"--output-directory", f"{self.options.device_output_root}/output_{self.options.log_stamp}",
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
pkgs = itertools.chain.from_iterable(["-p", pkg] for pkg in self.options.packageNames)
|
|
217
|
+
shell_command.extend(pkgs)
|
|
218
|
+
|
|
219
|
+
if self.options.profile_period:
|
|
220
|
+
shell_command += ["--profile-period", f"{self.options.profile_period}"]
|
|
221
|
+
|
|
222
|
+
whitelist = self.options.act_whitelist_file
|
|
223
|
+
blacklist = self.options.act_blacklist_file
|
|
224
|
+
if bool(whitelist) ^ bool(blacklist):
|
|
225
|
+
if whitelist:
|
|
226
|
+
shell_command += ["--act-whitelist-file", f"{whitelist}"]
|
|
227
|
+
else:
|
|
228
|
+
shell_command += ["--act-blacklist-file", f"{blacklist}"]
|
|
229
|
+
|
|
230
|
+
shell_command += ["-v", "-v", "-v"]
|
|
231
|
+
|
|
232
|
+
if self.options.extra_args:
|
|
233
|
+
shell_command += self.options.extra_args
|
|
234
|
+
|
|
235
|
+
full_cmd = ["adb"] + (["-s", self.options.serial] if self.options.serial else []) + ["shell"] + shell_command
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
outfile = open(self.log_file, "w", encoding="utf-8", buffering=1)
|
|
239
|
+
|
|
240
|
+
logger.info("Options info: {}".format(asdict(self.options)))
|
|
241
|
+
logger.info("Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)))
|
|
242
|
+
logger.info("Fastbot log will be saved to {}".format(outfile.name))
|
|
243
|
+
|
|
244
|
+
t = self.dev.stream_shell(shell_command, stdout=outfile, stderr=outfile)
|
|
245
|
+
return t
|
|
246
|
+
|
|
247
|
+
def close_on_exit(self, proc: ADBStreamShell_V2, f: IO):
|
|
248
|
+
self.return_code = proc.wait()
|
|
249
|
+
f.close()
|
|
250
|
+
if self.return_code != 0:
|
|
251
|
+
raise RuntimeError(f"Fastbot Error: Terminated with [code {self.return_code}] See {self.log_file} for details.")
|
|
252
|
+
|
|
253
|
+
def get_return_code(self):
|
|
254
|
+
if self.thread.is_running():
|
|
255
|
+
logger.info("Waiting for Fastbot to exit.")
|
|
256
|
+
return self.thread.wait()
|
|
257
|
+
return self.thread.poll() if self.android_release >= parse_version("7.0") else 0
|
|
258
|
+
|
|
259
|
+
def start(self):
|
|
260
|
+
# kill the fastbot process if runing.
|
|
261
|
+
self.dev.kill_proc("com.android.commands.monkey")
|
|
262
|
+
self.thread = self._activateFastbot()
|
|
263
|
+
|
|
264
|
+
def join(self):
|
|
265
|
+
self.thread.join()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
kea2/kea2_api.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from typing import Optional, List, Callable, Any, Union, Dict
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import unittest
|
|
4
|
+
import os
|
|
5
|
+
import inspect
|
|
6
|
+
from .keaUtils import KeaTestRunner, Options
|
|
7
|
+
from .u2Driver import U2Driver
|
|
8
|
+
from .utils import getLogger, TimeStamp, setCustomProjectRoot
|
|
9
|
+
from .adbUtils import ADBDevice
|
|
10
|
+
|
|
11
|
+
logger = getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Kea2Tester:
|
|
15
|
+
"""
|
|
16
|
+
Kea2 property tester
|
|
17
|
+
|
|
18
|
+
This class allows users to directly launch Kea2 property tests in existing test scripts.
|
|
19
|
+
|
|
20
|
+
Environment Variables:
|
|
21
|
+
KEA2_HYBRID_MODE: Controls whether to enable Kea2 testing
|
|
22
|
+
- "kea2": Enable Kea2 testing, trigger a breakpoint after testing is completed
|
|
23
|
+
- Other values or not set: Skip Kea2 testing, continue executing the original script
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.options: Optional[Options] = None
|
|
29
|
+
self.properties: List[unittest.TestCase] = []
|
|
30
|
+
self._caller_info: Optional[Dict[str, str]] = None
|
|
31
|
+
|
|
32
|
+
def run_kea2_testing(self, option: Options, configs_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
|
|
33
|
+
"""
|
|
34
|
+
Launch kea2 property test
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
option: Kea2 and Fastbot configuration options
|
|
38
|
+
configs_path: Your configs directory (absolute or relative path)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
dict: Test result dictionary containing the following keys:
|
|
42
|
+
- executed (bool): Whether Kea2 testing was executed
|
|
43
|
+
- skipped (bool): Whether Kea2 testing was skipped (KEA2_HYBRID_MODE != kea2)
|
|
44
|
+
- caller_info (Dict|None): Caller information (file, class, method name)
|
|
45
|
+
- output_dir (Path|None): Test output directory
|
|
46
|
+
- bug_report (Path|None): Bug report HTML file path
|
|
47
|
+
- result_json (Path|None): Test result JSON file path
|
|
48
|
+
- log_file (Path|None): Fastbot log file path
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
self._caller_info = self._get_caller_info()
|
|
53
|
+
|
|
54
|
+
logger.info("Starting Kea2 property testing...")
|
|
55
|
+
logger.info(f"Kea2 test launch location:")
|
|
56
|
+
if self._caller_info:
|
|
57
|
+
logger.info(f" File: {self._caller_info['file']}")
|
|
58
|
+
logger.info(f" Class: {self._caller_info['class']}")
|
|
59
|
+
logger.info(f" Method: {self._caller_info['method']}")
|
|
60
|
+
|
|
61
|
+
self.options = option
|
|
62
|
+
if self.options is None:
|
|
63
|
+
raise ValueError("Please set up the option config first.")
|
|
64
|
+
|
|
65
|
+
from kea2.utils import getProjectRoot
|
|
66
|
+
previous_root = getProjectRoot()
|
|
67
|
+
if configs_path is not None:
|
|
68
|
+
configs_dir = Path(configs_path).expanduser()
|
|
69
|
+
if not configs_dir.exists() or not configs_dir.is_dir():
|
|
70
|
+
raise FileNotFoundError(f"Configs directory not found in the specified path: {configs_dir}")
|
|
71
|
+
else:
|
|
72
|
+
setCustomProjectRoot(configs_path)
|
|
73
|
+
|
|
74
|
+
KeaTestRunner.setOptions(self.options)
|
|
75
|
+
argv = ["python3 -m unittest"] + self.options.propertytest_args
|
|
76
|
+
|
|
77
|
+
logger.info("Starting Kea2 property test...")
|
|
78
|
+
runner = KeaTestRunner()
|
|
79
|
+
unittest.main(module=None, argv=argv, testRunner=runner, exit=False)
|
|
80
|
+
logger.info("Kea2 property test completed.")
|
|
81
|
+
|
|
82
|
+
if configs_path is not None:
|
|
83
|
+
setCustomProjectRoot(previous_root)
|
|
84
|
+
|
|
85
|
+
result = self._build_test_result()
|
|
86
|
+
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
def _build_test_result(self) -> Dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
build test result dict
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict: Dictionary containing output directory and paths to various report files
|
|
95
|
+
"""
|
|
96
|
+
if self.options is None:
|
|
97
|
+
return {
|
|
98
|
+
'executed': False,
|
|
99
|
+
'skipped': False,
|
|
100
|
+
'caller_info': self._caller_info,
|
|
101
|
+
'output_dir': None,
|
|
102
|
+
'bug_report': None,
|
|
103
|
+
'result_json': None,
|
|
104
|
+
'log_file': None
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
output_dir = self.options.output_dir
|
|
108
|
+
|
|
109
|
+
from .keaUtils import STAMP, LOGFILE, RESFILE
|
|
110
|
+
|
|
111
|
+
bug_report_path = output_dir / "bug_report.html"
|
|
112
|
+
result_json_path = output_dir / RESFILE.name if hasattr(RESFILE, 'name') else output_dir / f"result_{STAMP}.json"
|
|
113
|
+
log_file_path = output_dir / LOGFILE.name if hasattr(LOGFILE, 'name') else output_dir / f"fastbot_{STAMP}.log"
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
'executed': True,
|
|
117
|
+
'skipped': False,
|
|
118
|
+
'caller_info': self._caller_info,
|
|
119
|
+
'output_dir': output_dir,
|
|
120
|
+
'bug_report': bug_report_path if bug_report_path.exists() else None,
|
|
121
|
+
'result_json': result_json_path if result_json_path.exists() else None,
|
|
122
|
+
'log_file': log_file_path if log_file_path.exists() else None
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def _get_caller_info(self) -> Dict[str, str]:
|
|
126
|
+
"""
|
|
127
|
+
Get caller information (file, class, method name)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
dict: Dictionary containing file, class, method
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
frame = inspect.currentframe()
|
|
134
|
+
caller_frame = frame.f_back.f_back
|
|
135
|
+
|
|
136
|
+
while caller_frame:
|
|
137
|
+
frame_info = inspect.getframeinfo(caller_frame)
|
|
138
|
+
if 'kea2_api.py' not in frame_info.filename:
|
|
139
|
+
# find caller
|
|
140
|
+
file_path = frame_info.filename
|
|
141
|
+
method_name = frame_info.function
|
|
142
|
+
|
|
143
|
+
# get class name
|
|
144
|
+
class_name = None
|
|
145
|
+
if 'self' in caller_frame.f_locals:
|
|
146
|
+
class_name = caller_frame.f_locals['self'].__class__.__name__
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
'file': file_path,
|
|
150
|
+
'class': class_name or 'N/A',
|
|
151
|
+
'method': method_name
|
|
152
|
+
}
|
|
153
|
+
caller_frame = caller_frame.f_back
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
'file': 'Unknown',
|
|
157
|
+
'class': 'N/A',
|
|
158
|
+
'method': 'Unknown'
|
|
159
|
+
}
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.warning(f"Failed to get caller info: {e}")
|
|
162
|
+
return {
|
|
163
|
+
'file': 'Unknown',
|
|
164
|
+
'class': 'N/A',
|
|
165
|
+
'method': 'Unknown'
|
|
166
|
+
}
|