Kea2-python 0.0.1b2__py3-none-any.whl → 0.1.0__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 +1 -4
- kea2/adbUtils.py +0 -1
- kea2/assets/fastbot_configs/widget.block.py +27 -7
- kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- kea2/assets/monkeyq.jar +0 -0
- kea2/bug_report_generator.py +477 -0
- kea2/fastbotManager.py +144 -0
- kea2/keaUtils.py +188 -169
- kea2/kea_launcher.py +16 -4
- kea2/logWatcher.py +15 -11
- kea2/resultSyncer.py +56 -0
- kea2/templates/bug_report_template.html +937 -0
- kea2/u2Driver.py +90 -0
- kea2/utils.py +8 -1
- kea2_python-0.1.0.dist-info/METADATA +254 -0
- kea2_python-0.1.0.dist-info/RECORD +35 -0
- kea2/assets/u2.jar +0 -0
- kea2_python-0.0.1b2.dist-info/METADATA +0 -464
- kea2_python-0.0.1b2.dist-info/RECORD +0 -32
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0.dist-info}/WHEEL +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0.dist-info}/top_level.txt +0 -0
kea2/keaUtils.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
import subprocess
|
|
5
|
-
import threading
|
|
6
4
|
import traceback
|
|
7
|
-
from typing import
|
|
5
|
+
from typing import Callable, Any, Dict, List, Literal, NewType, TypedDict, Union
|
|
8
6
|
from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult
|
|
9
7
|
import random
|
|
10
8
|
import warnings
|
|
@@ -12,11 +10,12 @@ from dataclasses import dataclass, asdict
|
|
|
12
10
|
import requests
|
|
13
11
|
from .absDriver import AbstractDriver
|
|
14
12
|
from functools import wraps
|
|
15
|
-
from
|
|
16
|
-
from .
|
|
13
|
+
from .bug_report_generator import BugReportGenerator
|
|
14
|
+
from .resultSyncer import ResultSyncer
|
|
17
15
|
from .logWatcher import LogWatcher
|
|
18
16
|
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
19
|
-
from .u2Driver import StaticU2UiObject
|
|
17
|
+
from .u2Driver import StaticU2UiObject, selector_to_xpath
|
|
18
|
+
from .fastbotManager import FastbotManager
|
|
20
19
|
import uiautomator2 as u2
|
|
21
20
|
import types
|
|
22
21
|
|
|
@@ -30,10 +29,14 @@ logger = getLogger(__name__)
|
|
|
30
29
|
# Class Typing
|
|
31
30
|
PropName = NewType("PropName", str)
|
|
32
31
|
PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
|
|
32
|
+
PropertyExecutionInfo = TypedDict(
|
|
33
|
+
"PropertyExecutionInfo",
|
|
34
|
+
{"propName": PropName, "state": Literal["start", "pass", "fail", "error"]}
|
|
35
|
+
)
|
|
33
36
|
|
|
34
37
|
STAMP = TimeStamp().getTimeStamp()
|
|
35
|
-
LOGFILE
|
|
36
|
-
RESFILE
|
|
38
|
+
LOGFILE: str
|
|
39
|
+
RESFILE: str
|
|
37
40
|
|
|
38
41
|
def precondition(precond: Callable[[Any], bool]) -> Callable:
|
|
39
42
|
"""the decorator @precondition
|
|
@@ -120,7 +123,11 @@ class Options:
|
|
|
120
123
|
# the stamp for log file and result file, default: current time stamp
|
|
121
124
|
log_stamp: str = None
|
|
122
125
|
# the profiling period to get the coverage result.
|
|
123
|
-
profile_period: int =
|
|
126
|
+
profile_period: int = 25
|
|
127
|
+
# take screenshots for every step
|
|
128
|
+
take_screenshots: bool = False
|
|
129
|
+
# the debug mode
|
|
130
|
+
debug: bool = False
|
|
124
131
|
|
|
125
132
|
def __setattr__(self, name, value):
|
|
126
133
|
if value is None:
|
|
@@ -130,10 +137,21 @@ class Options:
|
|
|
130
137
|
def __post_init__(self):
|
|
131
138
|
if self.serial and self.Driver:
|
|
132
139
|
self.Driver.setDeviceSerial(self.serial)
|
|
140
|
+
global LOGFILE, RESFILE, STAMP
|
|
133
141
|
if self.log_stamp:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
STAMP = self.log_stamp
|
|
143
|
+
self.output_dir = Path(self.output_dir).absolute() / f"res_{STAMP}"
|
|
144
|
+
LOGFILE = f"fastbot_{STAMP}.log"
|
|
145
|
+
RESFILE = f"result_{STAMP}.json"
|
|
146
|
+
|
|
147
|
+
self.profile_period = int(self.profile_period)
|
|
148
|
+
if self.profile_period < 1:
|
|
149
|
+
raise ValueError("--profile-period should be greater than 0")
|
|
150
|
+
|
|
151
|
+
self.throttle = int(self.throttle)
|
|
152
|
+
if self.throttle < 0:
|
|
153
|
+
raise ValueError("--throttle should be greater than or equal to 0")
|
|
154
|
+
|
|
137
155
|
_check_package_installation(self.serial, self.packageNames)
|
|
138
156
|
|
|
139
157
|
|
|
@@ -168,6 +186,11 @@ def getFullPropName(testCase: TestCase):
|
|
|
168
186
|
|
|
169
187
|
class JsonResult(TextTestResult):
|
|
170
188
|
res: PBTTestResult
|
|
189
|
+
|
|
190
|
+
lastExecutedInfo: PropertyExecutionInfo = {
|
|
191
|
+
"propName": "",
|
|
192
|
+
"state": "",
|
|
193
|
+
}
|
|
171
194
|
|
|
172
195
|
@classmethod
|
|
173
196
|
def setProperties(cls, allProperties: Dict):
|
|
@@ -184,6 +207,10 @@ class JsonResult(TextTestResult):
|
|
|
184
207
|
|
|
185
208
|
def addExcuted(self, test: TestCase):
|
|
186
209
|
self.res[getFullPropName(test)].executed += 1
|
|
210
|
+
self.lastExecutedInfo = {
|
|
211
|
+
"propName": getFullPropName(test),
|
|
212
|
+
"state": "start",
|
|
213
|
+
}
|
|
187
214
|
|
|
188
215
|
def addPrecondSatisfied(self, test: TestCase):
|
|
189
216
|
self.res[getFullPropName(test)].precond_satisfied += 1
|
|
@@ -191,128 +218,34 @@ class JsonResult(TextTestResult):
|
|
|
191
218
|
def addFailure(self, test, err):
|
|
192
219
|
super().addFailure(test, err)
|
|
193
220
|
self.res[getFullPropName(test)].fail += 1
|
|
221
|
+
self.lastExecutedInfo["state"] = "fail"
|
|
194
222
|
|
|
195
223
|
def addError(self, test, err):
|
|
196
224
|
super().addError(test, err)
|
|
197
225
|
self.res[getFullPropName(test)].error += 1
|
|
226
|
+
self.lastExecutedInfo["state"] = "error"
|
|
227
|
+
|
|
228
|
+
def updateExectedInfo(self):
|
|
229
|
+
if self.lastExecutedInfo["state"] == "start":
|
|
230
|
+
self.lastExecutedInfo["state"] = "pass"
|
|
198
231
|
|
|
199
232
|
def getExcuted(self, test: TestCase):
|
|
200
233
|
return self.res[getFullPropName(test)].executed
|
|
201
234
|
|
|
202
235
|
|
|
203
|
-
def activateFastbot(options: Options, port=None) -> threading.Thread:
|
|
204
|
-
"""
|
|
205
|
-
activate fastbot.
|
|
206
|
-
:params: options: the running setting for fastbot
|
|
207
|
-
:params: port: the listening port for script driver
|
|
208
|
-
:return: the fastbot daemon thread
|
|
209
|
-
"""
|
|
210
|
-
cur_dir = Path(__file__).parent
|
|
211
|
-
push_file(
|
|
212
|
-
Path.joinpath(cur_dir, "assets/monkeyq.jar"),
|
|
213
|
-
"/sdcard/monkeyq.jar",
|
|
214
|
-
device=options.serial
|
|
215
|
-
)
|
|
216
|
-
push_file(
|
|
217
|
-
Path.joinpath(cur_dir, "assets/fastbot-thirdpart.jar"),
|
|
218
|
-
"/sdcard/fastbot-thirdpart.jar",
|
|
219
|
-
device=options.serial,
|
|
220
|
-
)
|
|
221
|
-
push_file(
|
|
222
|
-
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
223
|
-
"/sdcard/framework.jar",
|
|
224
|
-
device=options.serial
|
|
225
|
-
)
|
|
226
|
-
push_file(
|
|
227
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a"),
|
|
228
|
-
"/data/local/tmp",
|
|
229
|
-
device=options.serial
|
|
230
|
-
)
|
|
231
|
-
push_file(
|
|
232
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a"),
|
|
233
|
-
"/data/local/tmp",
|
|
234
|
-
device=options.serial
|
|
235
|
-
)
|
|
236
|
-
push_file(
|
|
237
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86"),
|
|
238
|
-
"/data/local/tmp",
|
|
239
|
-
device=options.serial
|
|
240
|
-
)
|
|
241
|
-
push_file(
|
|
242
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64"),
|
|
243
|
-
"/data/local/tmp",
|
|
244
|
-
device=options.serial
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
t = startFastbotService(options)
|
|
248
|
-
print("[INFO] Running Fastbot...", flush=True)
|
|
249
|
-
|
|
250
|
-
return t
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def check_alive(port):
|
|
254
|
-
"""
|
|
255
|
-
check if the script driver and proxy server are alive.
|
|
256
|
-
"""
|
|
257
|
-
for _ in range(10):
|
|
258
|
-
sleep(2)
|
|
259
|
-
try:
|
|
260
|
-
requests.get(f"http://localhost:{port}/ping")
|
|
261
|
-
return
|
|
262
|
-
except requests.ConnectionError:
|
|
263
|
-
print("[INFO] waiting for connection.", flush=True)
|
|
264
|
-
pass
|
|
265
|
-
raise RuntimeError("Failed to connect fastbot")
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def startFastbotService(options: Options) -> threading.Thread:
|
|
269
|
-
shell_command = [
|
|
270
|
-
"CLASSPATH=/sdcard/monkeyq.jar:/sdcard/framework.jar:/sdcard/fastbot-thirdpart.jar",
|
|
271
|
-
"exec", "app_process",
|
|
272
|
-
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
273
|
-
"-p", *options.packageNames,
|
|
274
|
-
"--agent-u2" if options.agent == "u2" else "--agent",
|
|
275
|
-
"reuseq",
|
|
276
|
-
"--running-minutes", f"{options.running_mins}",
|
|
277
|
-
"--throttle", f"{options.throttle}",
|
|
278
|
-
"--bugreport", "--output-directory", "/sdcard/fastbot_report",
|
|
279
|
-
"-v", "-v", "-v"
|
|
280
|
-
]
|
|
281
|
-
|
|
282
|
-
full_cmd = ["adb"] + (["-s", options.serial] if options.serial else []) + ["shell"] + shell_command
|
|
283
|
-
|
|
284
|
-
outfile = open(LOGFILE, "w", encoding="utf-8", buffering=1)
|
|
285
|
-
|
|
286
|
-
print("[INFO] Options info: {}".format(asdict(options)), flush=True)
|
|
287
|
-
print("[INFO] Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)), flush=True)
|
|
288
|
-
print("[INFO] Fastbot log will be saved to {}".format(outfile.name), flush=True)
|
|
289
|
-
|
|
290
|
-
# process handler
|
|
291
|
-
proc = subprocess.Popen(full_cmd, stdout=outfile, stderr=outfile)
|
|
292
|
-
t = threading.Thread(target=close_on_exit, args=(proc, outfile), daemon=True)
|
|
293
|
-
t.start()
|
|
294
|
-
|
|
295
|
-
return t
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def close_on_exit(proc: subprocess.Popen, f: IO):
|
|
299
|
-
proc.wait()
|
|
300
|
-
f.close()
|
|
301
|
-
|
|
302
|
-
|
|
303
236
|
class KeaTestRunner(TextTestRunner):
|
|
304
237
|
|
|
305
238
|
resultclass: JsonResult
|
|
306
239
|
allProperties: PropertyStore
|
|
307
240
|
options: Options = None
|
|
308
|
-
|
|
241
|
+
_block_funcs: Dict[Literal["widgets", "trees"], List[Callable]] = None
|
|
309
242
|
|
|
310
243
|
@classmethod
|
|
311
244
|
def setOptions(cls, options: Options):
|
|
312
245
|
if not isinstance(options.packageNames, list) and len(options.packageNames) > 0:
|
|
313
246
|
raise ValueError("packageNames should be given in a list.")
|
|
314
247
|
if options.Driver is not None and options.agent == "native":
|
|
315
|
-
|
|
248
|
+
logger.warning("[Warning] Can not use any Driver when runing native mode.")
|
|
316
249
|
options.Driver = None
|
|
317
250
|
cls.options = options
|
|
318
251
|
|
|
@@ -331,7 +264,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
331
264
|
self.collectAllProperties(test)
|
|
332
265
|
|
|
333
266
|
if len(self.allProperties) == 0:
|
|
334
|
-
|
|
267
|
+
logger.warning("[Warning] No property has been found.")
|
|
335
268
|
|
|
336
269
|
self._setOuputDir()
|
|
337
270
|
|
|
@@ -360,38 +293,44 @@ class KeaTestRunner(TextTestRunner):
|
|
|
360
293
|
message=r"Please use assert\w+ instead.",
|
|
361
294
|
)
|
|
362
295
|
|
|
363
|
-
t = activateFastbot(options=self.options)
|
|
364
296
|
log_watcher = LogWatcher(LOGFILE)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
297
|
+
fb = FastbotManager(self.options, LOGFILE)
|
|
298
|
+
fb.start()
|
|
299
|
+
|
|
300
|
+
if self.options.agent == "u2":
|
|
368
301
|
# initialize the result.json file
|
|
369
302
|
result.flushResult(outfile=RESFILE)
|
|
370
303
|
# setUp for the u2 driver
|
|
371
304
|
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
372
|
-
check_alive(port=self.scriptDriver.lport)
|
|
305
|
+
fb.check_alive(port=self.scriptDriver.lport)
|
|
306
|
+
self._init()
|
|
307
|
+
|
|
308
|
+
resultSyncer = ResultSyncer(self.device_output_dir, self.options.output_dir)
|
|
309
|
+
resultSyncer.run()
|
|
373
310
|
|
|
374
311
|
end_by_remote = False
|
|
375
312
|
self.stepsCount = 0
|
|
376
313
|
while self.stepsCount < self.options.maxStep:
|
|
377
314
|
|
|
378
315
|
self.stepsCount += 1
|
|
379
|
-
|
|
316
|
+
logger.info("Sending monkeyEvent {}".format(
|
|
380
317
|
f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf")
|
|
381
318
|
else f"({self.stepsCount})"
|
|
382
319
|
)
|
|
383
|
-
|
|
320
|
+
)
|
|
384
321
|
|
|
385
322
|
try:
|
|
386
323
|
xml_raw = self.stepMonkey()
|
|
387
|
-
stat = self._getStat()
|
|
388
324
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
389
325
|
except requests.ConnectionError:
|
|
390
|
-
|
|
391
|
-
"[INFO] Exploration times up (--running-minutes)."
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
326
|
+
if fb.get_return_code() == 0:
|
|
327
|
+
logger.info("[INFO] Exploration times up (--running-minutes).")
|
|
328
|
+
end_by_remote = True
|
|
329
|
+
break
|
|
330
|
+
raise RuntimeError("Fastbot Aborted.")
|
|
331
|
+
|
|
332
|
+
if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
|
|
333
|
+
resultSyncer.sync_event.set()
|
|
395
334
|
|
|
396
335
|
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
397
336
|
|
|
@@ -420,20 +359,24 @@ class KeaTestRunner(TextTestRunner):
|
|
|
420
359
|
print("execute property %s." % execPropName, flush=True)
|
|
421
360
|
|
|
422
361
|
result.addExcuted(test)
|
|
362
|
+
self._logScript(result.lastExecutedInfo)
|
|
423
363
|
try:
|
|
424
364
|
test(result)
|
|
425
365
|
finally:
|
|
426
366
|
result.printErrors()
|
|
427
367
|
|
|
368
|
+
result.updateExectedInfo()
|
|
369
|
+
self._logScript(result.lastExecutedInfo)
|
|
428
370
|
result.flushResult(outfile=RESFILE)
|
|
429
371
|
|
|
430
372
|
if not end_by_remote:
|
|
431
373
|
self.stopMonkey()
|
|
432
374
|
result.flushResult(outfile=RESFILE)
|
|
433
|
-
|
|
375
|
+
resultSyncer.close()
|
|
376
|
+
|
|
377
|
+
fb.join()
|
|
434
378
|
print(f"Finish sending monkey events.", flush=True)
|
|
435
379
|
log_watcher.close()
|
|
436
|
-
self.tearDown()
|
|
437
380
|
|
|
438
381
|
# Source code from unittest Runner
|
|
439
382
|
# process the result
|
|
@@ -475,15 +418,19 @@ class KeaTestRunner(TextTestRunner):
|
|
|
475
418
|
"""
|
|
476
419
|
send a step monkey request to the server and get the xml string.
|
|
477
420
|
"""
|
|
478
|
-
|
|
421
|
+
block_dict = self._getBlockedWidgets()
|
|
422
|
+
block_widgets: List[str] = block_dict['widgets']
|
|
423
|
+
block_trees: List[str] = block_dict['trees']
|
|
479
424
|
URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
|
|
480
425
|
logger.debug(f"Sending request: {URL}")
|
|
481
426
|
logger.debug(f"Blocking widgets: {block_widgets}")
|
|
427
|
+
logger.debug(f"Blocking trees: {block_trees}")
|
|
482
428
|
r = requests.post(
|
|
483
429
|
url=URL,
|
|
484
430
|
json={
|
|
485
431
|
"steps_count": self.stepsCount,
|
|
486
|
-
"block_widgets": block_widgets
|
|
432
|
+
"block_widgets": block_widgets,
|
|
433
|
+
"block_trees": block_trees
|
|
487
434
|
}
|
|
488
435
|
)
|
|
489
436
|
|
|
@@ -533,14 +480,31 @@ class KeaTestRunner(TextTestRunner):
|
|
|
533
480
|
validProps[propName] = test
|
|
534
481
|
return validProps
|
|
535
482
|
|
|
536
|
-
def
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
483
|
+
def _logScript(self, execution_info:Dict):
|
|
484
|
+
URL = f"http://localhost:{self.scriptDriver.lport}/logScript"
|
|
485
|
+
r = requests.post(
|
|
486
|
+
url=URL,
|
|
487
|
+
json=execution_info
|
|
488
|
+
)
|
|
489
|
+
res = r.content.decode(encoding="utf-8")
|
|
490
|
+
if res != "OK":
|
|
491
|
+
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
492
|
+
|
|
493
|
+
def _init(self):
|
|
494
|
+
URL = f"http://localhost:{self.scriptDriver.lport}/init"
|
|
495
|
+
data = {
|
|
496
|
+
"takeScreenshots": self.options.take_screenshots,
|
|
497
|
+
"Stamp": STAMP
|
|
498
|
+
}
|
|
499
|
+
print(f"[INFO] Init fastbot: {data}", flush=True)
|
|
500
|
+
r = requests.post(
|
|
501
|
+
url=URL,
|
|
502
|
+
json=data
|
|
503
|
+
)
|
|
504
|
+
res = r.content.decode(encoding="utf-8")
|
|
505
|
+
import re
|
|
506
|
+
self.device_output_dir = re.match(r"outputDir:(.+)", res).group(1)
|
|
507
|
+
print(f"[INFO] Fastbot initiated. Device outputDir: {res}", flush=True)
|
|
544
508
|
|
|
545
509
|
def collectAllProperties(self, test: TestSuite):
|
|
546
510
|
"""collect all the properties to prepare for PBT
|
|
@@ -556,8 +520,8 @@ class KeaTestRunner(TextTestRunner):
|
|
|
556
520
|
"""remove the tearDown function in PBT
|
|
557
521
|
"""
|
|
558
522
|
def tearDown(self): ...
|
|
559
|
-
testCase = types.MethodType(tearDown, testCase)
|
|
560
|
-
|
|
523
|
+
testCase.tearDown = types.MethodType(tearDown, testCase)
|
|
524
|
+
|
|
561
525
|
def iter_tests(suite):
|
|
562
526
|
for test in suite:
|
|
563
527
|
if isinstance(test, TestSuite):
|
|
@@ -577,17 +541,24 @@ class KeaTestRunner(TextTestRunner):
|
|
|
577
541
|
# save it into allProperties for PBT
|
|
578
542
|
self.allProperties[testMethodName] = t
|
|
579
543
|
print(f"[INFO] Load property: {getFullPropName(t)}", flush=True)
|
|
580
|
-
|
|
544
|
+
|
|
581
545
|
@property
|
|
582
546
|
def _blockWidgetFuncs(self):
|
|
583
|
-
|
|
584
|
-
|
|
547
|
+
"""
|
|
548
|
+
load and process blocking functions from widget.block.py configuration file.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
dict: A dictionary containing two lists:
|
|
552
|
+
- 'widgets': List of functions that block individual widgets
|
|
553
|
+
- 'trees': List of functions that block widget trees
|
|
554
|
+
"""
|
|
555
|
+
if self._block_funcs is None:
|
|
556
|
+
self._block_funcs = {"widgets": list(), "trees": list()}
|
|
585
557
|
root_dir = getProjectRoot()
|
|
586
558
|
if root_dir is None or not os.path.exists(
|
|
587
|
-
|
|
559
|
+
file_block_widgets := root_dir / "configs" / "widget.block.py"
|
|
588
560
|
):
|
|
589
561
|
print(f"[WARNING] widget.block.py not find", flush=True)
|
|
590
|
-
|
|
591
562
|
|
|
592
563
|
def __get_block_widgets_module():
|
|
593
564
|
import importlib.util
|
|
@@ -601,46 +572,94 @@ class KeaTestRunner(TextTestRunner):
|
|
|
601
572
|
|
|
602
573
|
import inspect
|
|
603
574
|
for func_name, func in inspect.getmembers(mod, inspect.isfunction):
|
|
604
|
-
if func_name
|
|
575
|
+
if func_name == "global_block_widgets":
|
|
576
|
+
self._block_funcs["widgets"].append(func)
|
|
577
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
578
|
+
continue
|
|
579
|
+
if func_name == "global_block_tree":
|
|
580
|
+
self._block_funcs["trees"].append(func)
|
|
581
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
582
|
+
continue
|
|
583
|
+
if func_name.startswith("block_") and not func_name.startswith("block_tree_"):
|
|
605
584
|
if getattr(func, PRECONDITIONS_MARKER, None) is None:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
585
|
+
logger.warning(f"No precondition in block widget function: {func_name}. Default globally active.")
|
|
586
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
587
|
+
self._block_funcs["widgets"].append(func)
|
|
588
|
+
continue
|
|
589
|
+
if func_name.startswith("block_tree_"):
|
|
590
|
+
if getattr(func, PRECONDITIONS_MARKER, None) is None:
|
|
591
|
+
logger.warning(f"No precondition in block tree function: {func_name}. Default globally active.")
|
|
592
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
593
|
+
self._block_funcs["trees"].append(func)
|
|
594
|
+
|
|
595
|
+
return self._block_funcs
|
|
610
596
|
|
|
611
|
-
return self._block_widgets_funcs
|
|
612
597
|
|
|
613
598
|
def _getBlockedWidgets(self):
|
|
614
|
-
|
|
615
|
-
|
|
599
|
+
"""
|
|
600
|
+
Executes all blocking functions to get lists of widgets and trees to be blocked during testing.
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
dict: A dictionary containing:
|
|
604
|
+
- 'widgets': List of XPath strings for individual widgets to block
|
|
605
|
+
- 'trees': List of XPath strings for widget trees to block
|
|
606
|
+
"""
|
|
607
|
+
def _get_xpath_widgets(func):
|
|
608
|
+
blocked_set = set()
|
|
616
609
|
try:
|
|
617
610
|
script_driver = self.options.Driver.getScriptDriver()
|
|
618
|
-
preconds = getattr(func, PRECONDITIONS_MARKER)
|
|
619
|
-
if all(
|
|
611
|
+
preconds = getattr(func, PRECONDITIONS_MARKER, [])
|
|
612
|
+
if all(precond(script_driver) for precond in preconds):
|
|
620
613
|
_widgets = func(self.options.Driver.getStaticChecker())
|
|
621
|
-
if
|
|
622
|
-
_widgets = [_widgets]
|
|
614
|
+
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
623
615
|
for w in _widgets:
|
|
624
616
|
if isinstance(w, StaticU2UiObject):
|
|
625
|
-
|
|
617
|
+
xpath = selector_to_xpath(w.selector, True)
|
|
618
|
+
blocked_set.add(xpath)
|
|
626
619
|
elif isinstance(w, u2.xpath.XPathSelector):
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
blocked_widgets.append(getXPathRepr(w))
|
|
620
|
+
xpath = w._parent.xpath
|
|
621
|
+
blocked_set.add(xpath)
|
|
630
622
|
else:
|
|
631
623
|
logger.warning(f"{w} Not supported")
|
|
632
|
-
# blocked_widgets.extend([
|
|
633
|
-
# w._getXPath(w.selector) for w in _widgets
|
|
634
|
-
# ])
|
|
635
624
|
except Exception as e:
|
|
636
|
-
logger.error(f"
|
|
637
|
-
import traceback
|
|
625
|
+
logger.error(f"Error processing blocked widgets: {e}")
|
|
638
626
|
traceback.print_exc()
|
|
627
|
+
return blocked_set
|
|
628
|
+
|
|
629
|
+
res = {
|
|
630
|
+
"widgets": set(),
|
|
631
|
+
"trees": set()
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
for func in self._blockWidgetFuncs["widgets"]:
|
|
636
|
+
widgets = _get_xpath_widgets(func)
|
|
637
|
+
res["widgets"].update(widgets)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
for func in self._blockWidgetFuncs["trees"]:
|
|
641
|
+
trees = _get_xpath_widgets(func)
|
|
642
|
+
res["trees"].update(trees)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
res["widgets"] = list(res["widgets"] - res["trees"])
|
|
646
|
+
res["trees"] = list(res["trees"])
|
|
647
|
+
|
|
648
|
+
return res
|
|
639
649
|
|
|
640
|
-
return blocked_widgets
|
|
641
650
|
|
|
642
651
|
def __del__(self):
|
|
643
652
|
"""tearDown method. Cleanup the env.
|
|
644
653
|
"""
|
|
654
|
+
try:
|
|
655
|
+
logger.debug("Generating test bug report")
|
|
656
|
+
report_generator = BugReportGenerator(self.options.output_dir)
|
|
657
|
+
report_generator.generate_report()
|
|
658
|
+
except Exception as e:
|
|
659
|
+
logger.error(f"Error generating bug report: {e}", flush=True)
|
|
660
|
+
try:
|
|
661
|
+
self.stopMonkey()
|
|
662
|
+
except Exception as e:
|
|
663
|
+
pass
|
|
645
664
|
if self.options.Driver:
|
|
646
665
|
self.options.Driver.tearDown()
|
kea2/kea_launcher.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import sys
|
|
3
2
|
import argparse
|
|
4
3
|
import unittest
|
|
5
|
-
from pathlib import Path
|
|
6
4
|
from typing import List
|
|
7
5
|
|
|
8
6
|
def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]"):
|
|
@@ -49,6 +47,7 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
|
|
|
49
47
|
dest="running_minutes",
|
|
50
48
|
type=int,
|
|
51
49
|
required=False,
|
|
50
|
+
default=10,
|
|
52
51
|
help="Time to run fastbot",
|
|
53
52
|
)
|
|
54
53
|
|
|
@@ -89,8 +88,18 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
|
|
|
89
88
|
dest="profile_period",
|
|
90
89
|
type=int,
|
|
91
90
|
required=False,
|
|
91
|
+
default=25,
|
|
92
92
|
help="Steps to profile the testing statistics.",
|
|
93
93
|
)
|
|
94
|
+
|
|
95
|
+
parser.add_argument(
|
|
96
|
+
"--take-screenshots",
|
|
97
|
+
dest="take_screenshots",
|
|
98
|
+
required=False,
|
|
99
|
+
action="store_true",
|
|
100
|
+
default=False,
|
|
101
|
+
help="Take screenshots for every step.",
|
|
102
|
+
)
|
|
94
103
|
|
|
95
104
|
parser.add_argument(
|
|
96
105
|
"extra",
|
|
@@ -118,6 +127,10 @@ def driver_info_logger(args):
|
|
|
118
127
|
print(" running_minutes:", args.running_minutes, flush=True)
|
|
119
128
|
if args.throttle_ms:
|
|
120
129
|
print(" throttle_ms:", args.throttle_ms, flush=True)
|
|
130
|
+
if args.log_stamp:
|
|
131
|
+
print(" log_stamp:", args.log_stamp, flush=True)
|
|
132
|
+
if args.take_screenshots:
|
|
133
|
+
print(" take_screenshots:", args.take_screenshots, flush=True)
|
|
121
134
|
|
|
122
135
|
|
|
123
136
|
def parse_args(argv: List):
|
|
@@ -140,9 +153,7 @@ def _sanitize_args(args):
|
|
|
140
153
|
def run(args=None):
|
|
141
154
|
if args is None:
|
|
142
155
|
args = parse_args(sys.argv[1:])
|
|
143
|
-
|
|
144
156
|
_sanitize_args(args)
|
|
145
|
-
|
|
146
157
|
driver_info_logger(args)
|
|
147
158
|
unittest_info_logger(args)
|
|
148
159
|
|
|
@@ -159,6 +170,7 @@ def run(args=None):
|
|
|
159
170
|
throttle=args.throttle_ms,
|
|
160
171
|
log_stamp=args.log_stamp,
|
|
161
172
|
profile_period=args.profile_period,
|
|
173
|
+
take_screenshots=args.take_screenshots,
|
|
162
174
|
)
|
|
163
175
|
|
|
164
176
|
KeaTestRunner.setOptions(options)
|
kea2/logWatcher.py
CHANGED
|
@@ -20,10 +20,13 @@ class LogWatcher:
|
|
|
20
20
|
self.buffer = ""
|
|
21
21
|
self.last_pos = 0
|
|
22
22
|
|
|
23
|
-
while
|
|
23
|
+
while not self.end_flag:
|
|
24
24
|
self.read_log()
|
|
25
25
|
time.sleep(poll_interval)
|
|
26
26
|
|
|
27
|
+
time.sleep(0.2)
|
|
28
|
+
self.read_log()
|
|
29
|
+
|
|
27
30
|
def read_log(self):
|
|
28
31
|
time.sleep(0.02)
|
|
29
32
|
with open(self.log_file, 'r', encoding='utf-8') as f:
|
|
@@ -46,25 +49,26 @@ class LogWatcher:
|
|
|
46
49
|
exception_body +
|
|
47
50
|
"\nSee fastbot.log for details."
|
|
48
51
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
if self.end_flag:
|
|
53
|
+
statistic_match = PATTERN_STATISTIC.search(buffer)
|
|
54
|
+
if statistic_match:
|
|
55
|
+
statistic_body = statistic_match.group(1).strip()
|
|
56
|
+
if statistic_body:
|
|
57
|
+
print(
|
|
58
|
+
"[INFO] Fastbot exit:\n" +
|
|
59
|
+
statistic_body
|
|
60
|
+
, flush=True)
|
|
57
61
|
|
|
58
62
|
def __init__(self, log_file):
|
|
59
63
|
self.log_file = log_file
|
|
64
|
+
self.end_flag = False
|
|
60
65
|
|
|
61
66
|
threading.excepthook = thread_excepthook
|
|
62
67
|
t = threading.Thread(target=self.watcher, daemon=True)
|
|
63
68
|
t.start()
|
|
64
69
|
|
|
65
70
|
def close(self):
|
|
66
|
-
|
|
67
|
-
self.read_log()
|
|
71
|
+
self.end_flag = True
|
|
68
72
|
|
|
69
73
|
|
|
70
74
|
if __name__ == "__main__":
|