Kea2-python 0.0.1b2__py3-none-any.whl → 0.1.0b0__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/monkeyq.jar +0 -0
- kea2/keaUtils.py +163 -52
- kea2/kea_launcher.py +16 -4
- kea2/logWatcher.py +15 -11
- kea2/resultSyncer.py +56 -0
- kea2/u2Driver.py +90 -0
- kea2/utils.py +8 -1
- kea2_python-0.1.0b0.dist-info/METADATA +257 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0b0.dist-info}/RECORD +16 -16
- kea2/assets/u2.jar +0 -0
- kea2_python-0.0.1b2.dist-info/METADATA +0 -464
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0b0.dist-info}/WHEEL +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0b0.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0b0.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.0.1b2.dist-info → kea2_python-0.1.0b0.dist-info}/top_level.txt +0 -0
kea2/__init__.py
CHANGED
kea2/adbUtils.py
CHANGED
|
@@ -4,15 +4,35 @@ from kea2.keaUtils import precondition
|
|
|
4
4
|
|
|
5
5
|
def global_block_widgets(d: "Device"):
|
|
6
6
|
"""
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
only available in
|
|
7
|
+
Specify UI widgets to be blocked globally during testing.
|
|
8
|
+
Returns a list of widgets that should be blocked from exploration.
|
|
9
|
+
This function is only available in 'u2 agent' mode.
|
|
10
10
|
"""
|
|
11
|
+
# return [d(text="widgets to block"), d.xpath(".//node[@text='widget to block']")]
|
|
11
12
|
return []
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
# conditional
|
|
15
|
-
@precondition(lambda d: d(text="In the home page").exists)
|
|
15
|
+
# Example of conditional blocking with precondition
|
|
16
|
+
# @precondition(lambda d: d(text="In the home page").exists)
|
|
17
|
+
@precondition(lambda d: False)
|
|
16
18
|
def block_sth(d: "Device"):
|
|
17
|
-
#
|
|
18
|
-
return [
|
|
19
|
+
# Note: Function name must start with "block_"
|
|
20
|
+
return []
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def global_block_tree(d: "Device"):
|
|
24
|
+
"""
|
|
25
|
+
Specify UI widget trees to be blocked globally during testing.
|
|
26
|
+
Returns a list of root nodes whose entire subtrees will be blocked from exploration.
|
|
27
|
+
This function is only available in 'u2 agent' mode.
|
|
28
|
+
"""
|
|
29
|
+
# return [d(text="trees to block"), d.xpath(".//node[@text='tree to block']")]
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Example of conditional tree blocking with precondition
|
|
34
|
+
# @precondition(lambda d: d(text="In the home page").exists)
|
|
35
|
+
@precondition(lambda d: False)
|
|
36
|
+
def block_tree_sth(d: "Device"):
|
|
37
|
+
# Note: Function name must start with "block_tree_"
|
|
38
|
+
return []
|
kea2/assets/monkeyq.jar
CHANGED
|
Binary file
|
kea2/keaUtils.py
CHANGED
|
@@ -14,9 +14,10 @@ from .absDriver import AbstractDriver
|
|
|
14
14
|
from functools import wraps
|
|
15
15
|
from time import sleep
|
|
16
16
|
from .adbUtils import push_file
|
|
17
|
+
from .resultSyncer import ResultSyncer
|
|
17
18
|
from .logWatcher import LogWatcher
|
|
18
19
|
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
19
|
-
from .u2Driver import StaticU2UiObject
|
|
20
|
+
from .u2Driver import StaticU2UiObject, selector_to_xpath
|
|
20
21
|
import uiautomator2 as u2
|
|
21
22
|
import types
|
|
22
23
|
|
|
@@ -32,8 +33,8 @@ PropName = NewType("PropName", str)
|
|
|
32
33
|
PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
|
|
33
34
|
|
|
34
35
|
STAMP = TimeStamp().getTimeStamp()
|
|
35
|
-
LOGFILE
|
|
36
|
-
RESFILE
|
|
36
|
+
LOGFILE: str
|
|
37
|
+
RESFILE: str
|
|
37
38
|
|
|
38
39
|
def precondition(precond: Callable[[Any], bool]) -> Callable:
|
|
39
40
|
"""the decorator @precondition
|
|
@@ -120,7 +121,11 @@ class Options:
|
|
|
120
121
|
# the stamp for log file and result file, default: current time stamp
|
|
121
122
|
log_stamp: str = None
|
|
122
123
|
# the profiling period to get the coverage result.
|
|
123
|
-
profile_period: int =
|
|
124
|
+
profile_period: int = 25
|
|
125
|
+
# take screenshots for every step
|
|
126
|
+
take_screenshots: bool = False
|
|
127
|
+
# the debug mode
|
|
128
|
+
debug: bool = False
|
|
124
129
|
|
|
125
130
|
def __setattr__(self, name, value):
|
|
126
131
|
if value is None:
|
|
@@ -130,10 +135,21 @@ class Options:
|
|
|
130
135
|
def __post_init__(self):
|
|
131
136
|
if self.serial and self.Driver:
|
|
132
137
|
self.Driver.setDeviceSerial(self.serial)
|
|
138
|
+
global LOGFILE, RESFILE, STAMP
|
|
133
139
|
if self.log_stamp:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
STAMP = self.log_stamp
|
|
141
|
+
self.output_dir = Path(self.output_dir).absolute() / f"res_{STAMP}"
|
|
142
|
+
LOGFILE = f"fastbot_{STAMP}.log"
|
|
143
|
+
RESFILE = f"result_{STAMP}.json"
|
|
144
|
+
|
|
145
|
+
self.profile_period = int(self.profile_period)
|
|
146
|
+
if self.profile_period < 1:
|
|
147
|
+
raise ValueError("--profile-period should be greater than 0")
|
|
148
|
+
|
|
149
|
+
self.throttle = int(self.throttle)
|
|
150
|
+
if self.throttle < 0:
|
|
151
|
+
raise ValueError("--throttle should be greater than or equal to 0")
|
|
152
|
+
|
|
137
153
|
_check_package_installation(self.serial, self.packageNames)
|
|
138
154
|
|
|
139
155
|
|
|
@@ -168,6 +184,11 @@ def getFullPropName(testCase: TestCase):
|
|
|
168
184
|
|
|
169
185
|
class JsonResult(TextTestResult):
|
|
170
186
|
res: PBTTestResult
|
|
187
|
+
|
|
188
|
+
lastExecutedInfo: Dict = {
|
|
189
|
+
"propName": "",
|
|
190
|
+
"state": "",
|
|
191
|
+
}
|
|
171
192
|
|
|
172
193
|
@classmethod
|
|
173
194
|
def setProperties(cls, allProperties: Dict):
|
|
@@ -184,6 +205,10 @@ class JsonResult(TextTestResult):
|
|
|
184
205
|
|
|
185
206
|
def addExcuted(self, test: TestCase):
|
|
186
207
|
self.res[getFullPropName(test)].executed += 1
|
|
208
|
+
self.lastExecutedInfo = {
|
|
209
|
+
"propName": getFullPropName(test),
|
|
210
|
+
"state": "start",
|
|
211
|
+
}
|
|
187
212
|
|
|
188
213
|
def addPrecondSatisfied(self, test: TestCase):
|
|
189
214
|
self.res[getFullPropName(test)].precond_satisfied += 1
|
|
@@ -191,10 +216,12 @@ class JsonResult(TextTestResult):
|
|
|
191
216
|
def addFailure(self, test, err):
|
|
192
217
|
super().addFailure(test, err)
|
|
193
218
|
self.res[getFullPropName(test)].fail += 1
|
|
219
|
+
self.lastExecutedInfo["state"] = "fail"
|
|
194
220
|
|
|
195
221
|
def addError(self, test, err):
|
|
196
222
|
super().addError(test, err)
|
|
197
223
|
self.res[getFullPropName(test)].error += 1
|
|
224
|
+
self.lastExecutedInfo["state"] = "error"
|
|
198
225
|
|
|
199
226
|
def getExcuted(self, test: TestCase):
|
|
200
227
|
return self.res[getFullPropName(test)].executed
|
|
@@ -271,14 +298,18 @@ def startFastbotService(options: Options) -> threading.Thread:
|
|
|
271
298
|
"exec", "app_process",
|
|
272
299
|
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
273
300
|
"-p", *options.packageNames,
|
|
274
|
-
"--agent-u2" if options.agent == "u2" else "--agent",
|
|
301
|
+
"--agent-u2" if options.agent == "u2" else "--agent",
|
|
275
302
|
"reuseq",
|
|
276
303
|
"--running-minutes", f"{options.running_mins}",
|
|
277
304
|
"--throttle", f"{options.throttle}",
|
|
278
|
-
"--bugreport",
|
|
279
|
-
"-v", "-v", "-v"
|
|
305
|
+
"--bugreport",
|
|
280
306
|
]
|
|
281
307
|
|
|
308
|
+
if options.profile_period:
|
|
309
|
+
shell_command += ["--profile-period", f"{options.profile_period}"]
|
|
310
|
+
|
|
311
|
+
shell_command += ["-v", "-v", "-v"]
|
|
312
|
+
|
|
282
313
|
full_cmd = ["adb"] + (["-s", options.serial] if options.serial else []) + ["shell"] + shell_command
|
|
283
314
|
|
|
284
315
|
outfile = open(LOGFILE, "w", encoding="utf-8", buffering=1)
|
|
@@ -298,14 +329,15 @@ def startFastbotService(options: Options) -> threading.Thread:
|
|
|
298
329
|
def close_on_exit(proc: subprocess.Popen, f: IO):
|
|
299
330
|
proc.wait()
|
|
300
331
|
f.close()
|
|
301
|
-
|
|
332
|
+
|
|
302
333
|
|
|
303
334
|
class KeaTestRunner(TextTestRunner):
|
|
304
335
|
|
|
305
336
|
resultclass: JsonResult
|
|
306
337
|
allProperties: PropertyStore
|
|
307
338
|
options: Options = None
|
|
308
|
-
|
|
339
|
+
_block_funcs: Dict[Literal["widgets", "trees"], List[Callable]] = None
|
|
340
|
+
# _block_trees_funcs = None
|
|
309
341
|
|
|
310
342
|
@classmethod
|
|
311
343
|
def setOptions(cls, options: Options):
|
|
@@ -370,6 +402,10 @@ class KeaTestRunner(TextTestRunner):
|
|
|
370
402
|
# setUp for the u2 driver
|
|
371
403
|
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
372
404
|
check_alive(port=self.scriptDriver.lport)
|
|
405
|
+
self._init()
|
|
406
|
+
|
|
407
|
+
resultSyncer = ResultSyncer(self.device_output_dir, self.options.output_dir)
|
|
408
|
+
resultSyncer.run()
|
|
373
409
|
|
|
374
410
|
end_by_remote = False
|
|
375
411
|
self.stepsCount = 0
|
|
@@ -384,7 +420,6 @@ class KeaTestRunner(TextTestRunner):
|
|
|
384
420
|
|
|
385
421
|
try:
|
|
386
422
|
xml_raw = self.stepMonkey()
|
|
387
|
-
stat = self._getStat()
|
|
388
423
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
389
424
|
except requests.ConnectionError:
|
|
390
425
|
print(
|
|
@@ -393,6 +428,9 @@ class KeaTestRunner(TextTestRunner):
|
|
|
393
428
|
end_by_remote = True
|
|
394
429
|
break
|
|
395
430
|
|
|
431
|
+
if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
|
|
432
|
+
resultSyncer.sync_event.set()
|
|
433
|
+
|
|
396
434
|
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
397
435
|
|
|
398
436
|
# Go to the next round if no precond satisfied
|
|
@@ -420,20 +458,23 @@ class KeaTestRunner(TextTestRunner):
|
|
|
420
458
|
print("execute property %s." % execPropName, flush=True)
|
|
421
459
|
|
|
422
460
|
result.addExcuted(test)
|
|
461
|
+
self._logScript(result.lastExecutedInfo)
|
|
423
462
|
try:
|
|
424
463
|
test(result)
|
|
425
464
|
finally:
|
|
426
465
|
result.printErrors()
|
|
427
466
|
|
|
467
|
+
self._logScript(result.lastExecutedInfo)
|
|
428
468
|
result.flushResult(outfile=RESFILE)
|
|
429
469
|
|
|
430
470
|
if not end_by_remote:
|
|
431
471
|
self.stopMonkey()
|
|
432
472
|
result.flushResult(outfile=RESFILE)
|
|
473
|
+
resultSyncer.close()
|
|
433
474
|
|
|
434
475
|
print(f"Finish sending monkey events.", flush=True)
|
|
435
476
|
log_watcher.close()
|
|
436
|
-
|
|
477
|
+
|
|
437
478
|
|
|
438
479
|
# Source code from unittest Runner
|
|
439
480
|
# process the result
|
|
@@ -475,15 +516,19 @@ class KeaTestRunner(TextTestRunner):
|
|
|
475
516
|
"""
|
|
476
517
|
send a step monkey request to the server and get the xml string.
|
|
477
518
|
"""
|
|
478
|
-
|
|
519
|
+
block_dict = self._getBlockedWidgets()
|
|
520
|
+
block_widgets: List[str] = block_dict['widgets']
|
|
521
|
+
block_trees: List[str] = block_dict['trees']
|
|
479
522
|
URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
|
|
480
523
|
logger.debug(f"Sending request: {URL}")
|
|
481
524
|
logger.debug(f"Blocking widgets: {block_widgets}")
|
|
525
|
+
logger.debug(f"Blocking trees: {block_trees}")
|
|
482
526
|
r = requests.post(
|
|
483
527
|
url=URL,
|
|
484
528
|
json={
|
|
485
529
|
"steps_count": self.stepsCount,
|
|
486
|
-
"block_widgets": block_widgets
|
|
530
|
+
"block_widgets": block_widgets,
|
|
531
|
+
"block_trees": block_trees
|
|
487
532
|
}
|
|
488
533
|
)
|
|
489
534
|
|
|
@@ -533,14 +578,31 @@ class KeaTestRunner(TextTestRunner):
|
|
|
533
578
|
validProps[propName] = test
|
|
534
579
|
return validProps
|
|
535
580
|
|
|
536
|
-
def
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
581
|
+
def _logScript(self, execution_info:Dict):
|
|
582
|
+
URL = f"http://localhost:{self.scriptDriver.lport}/logScript"
|
|
583
|
+
r = requests.post(
|
|
584
|
+
url=URL,
|
|
585
|
+
json=execution_info
|
|
586
|
+
)
|
|
587
|
+
res = r.content.decode(encoding="utf-8")
|
|
588
|
+
if res != "OK":
|
|
589
|
+
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
590
|
+
|
|
591
|
+
def _init(self):
|
|
592
|
+
URL = f"http://localhost:{self.scriptDriver.lport}/init"
|
|
593
|
+
data = {
|
|
594
|
+
"takeScreenshots": self.options.take_screenshots,
|
|
595
|
+
"Stamp": STAMP
|
|
596
|
+
}
|
|
597
|
+
print(f"[INFO] Init fastbot: {data}", flush=True)
|
|
598
|
+
r = requests.post(
|
|
599
|
+
url=URL,
|
|
600
|
+
json=data
|
|
601
|
+
)
|
|
602
|
+
res = r.content.decode(encoding="utf-8")
|
|
603
|
+
import re
|
|
604
|
+
self.device_output_dir = re.match(r"outputDir:(.+)", res).group(1)
|
|
605
|
+
print(f"[INFO] Fastbot initiated. Device outputDir: {res}", flush=True)
|
|
544
606
|
|
|
545
607
|
def collectAllProperties(self, test: TestSuite):
|
|
546
608
|
"""collect all the properties to prepare for PBT
|
|
@@ -556,7 +618,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
556
618
|
"""remove the tearDown function in PBT
|
|
557
619
|
"""
|
|
558
620
|
def tearDown(self): ...
|
|
559
|
-
testCase = types.MethodType(tearDown, testCase)
|
|
621
|
+
testCase.tearDown = types.MethodType(tearDown, testCase)
|
|
560
622
|
|
|
561
623
|
def iter_tests(suite):
|
|
562
624
|
for test in suite:
|
|
@@ -577,17 +639,24 @@ class KeaTestRunner(TextTestRunner):
|
|
|
577
639
|
# save it into allProperties for PBT
|
|
578
640
|
self.allProperties[testMethodName] = t
|
|
579
641
|
print(f"[INFO] Load property: {getFullPropName(t)}", flush=True)
|
|
580
|
-
|
|
642
|
+
|
|
581
643
|
@property
|
|
582
644
|
def _blockWidgetFuncs(self):
|
|
583
|
-
|
|
584
|
-
|
|
645
|
+
"""
|
|
646
|
+
load and process blocking functions from widget.block.py configuration file.
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
dict: A dictionary containing two lists:
|
|
650
|
+
- 'widgets': List of functions that block individual widgets
|
|
651
|
+
- 'trees': List of functions that block widget trees
|
|
652
|
+
"""
|
|
653
|
+
if self._block_funcs is None:
|
|
654
|
+
self._block_funcs = {"widgets": list(), "trees": list()}
|
|
585
655
|
root_dir = getProjectRoot()
|
|
586
656
|
if root_dir is None or not os.path.exists(
|
|
587
|
-
|
|
657
|
+
file_block_widgets := root_dir / "configs" / "widget.block.py"
|
|
588
658
|
):
|
|
589
659
|
print(f"[WARNING] widget.block.py not find", flush=True)
|
|
590
|
-
|
|
591
660
|
|
|
592
661
|
def __get_block_widgets_module():
|
|
593
662
|
import importlib.util
|
|
@@ -601,46 +670,88 @@ class KeaTestRunner(TextTestRunner):
|
|
|
601
670
|
|
|
602
671
|
import inspect
|
|
603
672
|
for func_name, func in inspect.getmembers(mod, inspect.isfunction):
|
|
604
|
-
if func_name
|
|
673
|
+
if func_name == "global_block_widgets":
|
|
674
|
+
self._block_funcs["widgets"].append(func)
|
|
675
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
676
|
+
continue
|
|
677
|
+
if func_name == "global_block_tree":
|
|
678
|
+
self._block_funcs["trees"].append(func)
|
|
679
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
680
|
+
continue
|
|
681
|
+
if func_name.startswith("block_") and not func_name.startswith("block_tree_"):
|
|
682
|
+
if getattr(func, PRECONDITIONS_MARKER, None) is None:
|
|
683
|
+
logger.warning(f"No precondition in block widget function: {func_name}. Default globally active.")
|
|
684
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
685
|
+
self._block_funcs["widgets"].append(func)
|
|
686
|
+
continue
|
|
687
|
+
if func_name.startswith("block_tree_"):
|
|
605
688
|
if getattr(func, PRECONDITIONS_MARKER, None) is None:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
689
|
+
logger.warning(f"No precondition in block tree function: {func_name}. Default globally active.")
|
|
690
|
+
setattr(func, PRECONDITIONS_MARKER, (lambda d: True,))
|
|
691
|
+
self._block_funcs["trees"].append(func)
|
|
692
|
+
|
|
693
|
+
return self._block_funcs
|
|
610
694
|
|
|
611
|
-
return self._block_widgets_funcs
|
|
612
695
|
|
|
613
696
|
def _getBlockedWidgets(self):
|
|
614
|
-
|
|
615
|
-
|
|
697
|
+
"""
|
|
698
|
+
Executes all blocking functions to get lists of widgets and trees to be blocked during testing.
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
dict: A dictionary containing:
|
|
702
|
+
- 'widgets': List of XPath strings for individual widgets to block
|
|
703
|
+
- 'trees': List of XPath strings for widget trees to block
|
|
704
|
+
"""
|
|
705
|
+
def _get_xpath_widgets(func):
|
|
706
|
+
blocked_set = set()
|
|
616
707
|
try:
|
|
617
708
|
script_driver = self.options.Driver.getScriptDriver()
|
|
618
|
-
preconds = getattr(func, PRECONDITIONS_MARKER)
|
|
619
|
-
if all(
|
|
709
|
+
preconds = getattr(func, PRECONDITIONS_MARKER, [])
|
|
710
|
+
if all(precond(script_driver) for precond in preconds):
|
|
620
711
|
_widgets = func(self.options.Driver.getStaticChecker())
|
|
621
|
-
if
|
|
622
|
-
_widgets = [_widgets]
|
|
712
|
+
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
623
713
|
for w in _widgets:
|
|
624
714
|
if isinstance(w, StaticU2UiObject):
|
|
625
|
-
|
|
715
|
+
xpath = selector_to_xpath(w.selector, True)
|
|
716
|
+
blocked_set.add(xpath)
|
|
626
717
|
elif isinstance(w, u2.xpath.XPathSelector):
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
blocked_widgets.append(getXPathRepr(w))
|
|
718
|
+
xpath = w._parent.xpath
|
|
719
|
+
blocked_set.add(xpath)
|
|
630
720
|
else:
|
|
631
721
|
logger.warning(f"{w} Not supported")
|
|
632
|
-
# blocked_widgets.extend([
|
|
633
|
-
# w._getXPath(w.selector) for w in _widgets
|
|
634
|
-
# ])
|
|
635
722
|
except Exception as e:
|
|
636
|
-
logger.error(f"
|
|
637
|
-
import traceback
|
|
723
|
+
logger.error(f"Error processing blocked widgets: {e}")
|
|
638
724
|
traceback.print_exc()
|
|
725
|
+
return blocked_set
|
|
726
|
+
|
|
727
|
+
res = {
|
|
728
|
+
"widgets": set(),
|
|
729
|
+
"trees": set()
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
for func in self._blockWidgetFuncs["widgets"]:
|
|
734
|
+
widgets = _get_xpath_widgets(func)
|
|
735
|
+
res["widgets"].update(widgets)
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
for func in self._blockWidgetFuncs["trees"]:
|
|
739
|
+
trees = _get_xpath_widgets(func)
|
|
740
|
+
res["trees"].update(trees)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
res["widgets"] = list(res["widgets"] - res["trees"])
|
|
744
|
+
res["trees"] = list(res["trees"])
|
|
745
|
+
|
|
746
|
+
return res
|
|
639
747
|
|
|
640
|
-
return blocked_widgets
|
|
641
748
|
|
|
642
749
|
def __del__(self):
|
|
643
750
|
"""tearDown method. Cleanup the env.
|
|
644
751
|
"""
|
|
752
|
+
try:
|
|
753
|
+
self.stopMonkey()
|
|
754
|
+
except Exception as e:
|
|
755
|
+
pass
|
|
645
756
|
if self.options.Driver:
|
|
646
757
|
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__":
|
kea2/resultSyncer.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from .adbUtils import adb_shell, pull_file
|
|
3
|
+
from .utils import getLogger
|
|
4
|
+
|
|
5
|
+
logger = getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResultSyncer:
|
|
9
|
+
|
|
10
|
+
def __init__(self, device_output_dir, output_dir):
|
|
11
|
+
self.device_output_dir = device_output_dir
|
|
12
|
+
self.output_dir = output_dir
|
|
13
|
+
self.running = False
|
|
14
|
+
self.thread = None
|
|
15
|
+
self.sync_event = threading.Event()
|
|
16
|
+
|
|
17
|
+
def run(self):
|
|
18
|
+
"""Start a background thread to sync device data when triggered"""
|
|
19
|
+
self.running = True
|
|
20
|
+
self.thread = threading.Thread(target=self._sync_thread, daemon=True)
|
|
21
|
+
self.thread.start()
|
|
22
|
+
|
|
23
|
+
def _sync_thread(self):
|
|
24
|
+
"""Thread function that waits for sync event and then syncs data"""
|
|
25
|
+
while self.running:
|
|
26
|
+
# Wait for sync event with a timeout to periodically check if still running
|
|
27
|
+
if self.sync_event.wait(timeout=3):
|
|
28
|
+
self._sync_device_data()
|
|
29
|
+
self.sync_event.clear()
|
|
30
|
+
|
|
31
|
+
def close(self):
|
|
32
|
+
self.running = False
|
|
33
|
+
self.sync_event.set()
|
|
34
|
+
if self.thread and self.thread.is_alive():
|
|
35
|
+
self.thread.join(timeout=10)
|
|
36
|
+
self._sync_device_data()
|
|
37
|
+
try:
|
|
38
|
+
logger.debug(f"Removing device output directory: {self.device_output_dir}")
|
|
39
|
+
remove_device_dir = ["rm", "-rf", self.device_output_dir]
|
|
40
|
+
adb_shell(remove_device_dir)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.error(f"Error removing device output directory: {e}", flush=True)
|
|
43
|
+
|
|
44
|
+
def _sync_device_data(self):
|
|
45
|
+
"""
|
|
46
|
+
Sync the device data to the local directory.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
logger.debug("Syncing data")
|
|
50
|
+
|
|
51
|
+
pull_file(self.device_output_dir, str(self.output_dir))
|
|
52
|
+
|
|
53
|
+
remove_pulled_screenshots = ["find", self.device_output_dir, "-name", "\"*.png\"", "-delete"]
|
|
54
|
+
adb_shell(remove_pulled_screenshots)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Error in data sync: {e}", flush=True)
|