Kea2-python 0.1.2__py3-none-any.whl → 0.2.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/absDriver.py +1 -1
- kea2/adbUtils.py +273 -20
- kea2/bug_report_generator.py +333 -321
- kea2/fastbotManager.py +105 -47
- kea2/keaUtils.py +100 -95
- kea2/kea_launcher.py +15 -0
- kea2/resultSyncer.py +18 -8
- kea2/templates/bug_report_template.html +9 -50
- kea2/u2Driver.py +148 -137
- kea2/utils.py +1 -0
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/METADATA +44 -33
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/RECORD +16 -16
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/WHEEL +0 -0
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.1.2.dist-info → kea2_python-0.2.0.dist-info}/top_level.txt +0 -0
kea2/fastbotManager.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
from dataclasses import asdict
|
|
2
1
|
import subprocess
|
|
3
2
|
import threading
|
|
3
|
+
from dataclasses import asdict
|
|
4
4
|
import requests
|
|
5
5
|
from time import sleep
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from uiautomator2.core import HTTPResponse, _http_request
|
|
9
|
+
from kea2.adbUtils import ADBDevice, ADBStreamShell_V2
|
|
7
10
|
from pathlib import Path
|
|
8
|
-
from .utils import getLogger
|
|
11
|
+
from kea2.utils import getLogger
|
|
12
|
+
|
|
9
13
|
|
|
10
|
-
from typing import IO, TYPE_CHECKING
|
|
14
|
+
from typing import IO, TYPE_CHECKING, Dict
|
|
11
15
|
if TYPE_CHECKING:
|
|
12
16
|
from .keaUtils import Options
|
|
13
17
|
|
|
@@ -21,56 +25,49 @@ class FastbotManager:
|
|
|
21
25
|
self.log_file: str = log_file
|
|
22
26
|
self.port = None
|
|
23
27
|
self.thread = None
|
|
28
|
+
self._device_output_dir = None
|
|
29
|
+
ADBDevice.setDevice(options.serial, options.transport_id)
|
|
30
|
+
self.dev = ADBDevice()
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
def _activateFastbot(self) -> threading.Thread:
|
|
32
|
+
def _activateFastbot(self) -> ADBStreamShell_V2:
|
|
27
33
|
"""
|
|
28
34
|
activate fastbot.
|
|
29
35
|
:params: options: the running setting for fastbot
|
|
30
36
|
:params: port: the listening port for script driver
|
|
31
37
|
:return: the fastbot daemon thread
|
|
32
38
|
"""
|
|
33
|
-
options = self.options
|
|
34
39
|
cur_dir = Path(__file__).parent
|
|
35
|
-
|
|
40
|
+
self.dev.sync.push(
|
|
36
41
|
Path.joinpath(cur_dir, "assets/monkeyq.jar"),
|
|
37
|
-
"/sdcard/monkeyq.jar"
|
|
38
|
-
device=options.serial
|
|
42
|
+
"/sdcard/monkeyq.jar"
|
|
39
43
|
)
|
|
40
|
-
|
|
44
|
+
self.dev.sync.push(
|
|
41
45
|
Path.joinpath(cur_dir, "assets/fastbot-thirdpart.jar"),
|
|
42
46
|
"/sdcard/fastbot-thirdpart.jar",
|
|
43
|
-
device=options.serial,
|
|
44
47
|
)
|
|
45
|
-
|
|
48
|
+
self.dev.sync.push(
|
|
46
49
|
Path.joinpath(cur_dir, "assets/kea2-thirdpart.jar"),
|
|
47
50
|
"/sdcard/kea2-thirdpart.jar",
|
|
48
|
-
device=options.serial,
|
|
49
51
|
)
|
|
50
|
-
|
|
51
|
-
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
52
|
+
self.dev.sync.push(
|
|
53
|
+
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
52
54
|
"/sdcard/framework.jar",
|
|
53
|
-
device=options.serial
|
|
54
55
|
)
|
|
55
|
-
|
|
56
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a"),
|
|
57
|
-
"/data/local/tmp",
|
|
58
|
-
device=options.serial
|
|
56
|
+
self.dev.sync.push(
|
|
57
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a/libfastbot_native.so"),
|
|
58
|
+
"/data/local/tmp/arm64-v8a/libfastbot_native.so",
|
|
59
59
|
)
|
|
60
|
-
|
|
61
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a"),
|
|
62
|
-
"/data/local/tmp",
|
|
63
|
-
device=options.serial
|
|
60
|
+
self.dev.sync.push(
|
|
61
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a/libfastbot_native.so"),
|
|
62
|
+
"/data/local/tmp/armeabi-v7a/libfastbot_native.so",
|
|
64
63
|
)
|
|
65
|
-
|
|
66
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86"),
|
|
67
|
-
"/data/local/tmp",
|
|
68
|
-
device=options.serial
|
|
64
|
+
self.dev.sync.push(
|
|
65
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86/libfastbot_native.so"),
|
|
66
|
+
"/data/local/tmp/x86/libfastbot_native.so",
|
|
69
67
|
)
|
|
70
|
-
|
|
71
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64"),
|
|
72
|
-
"/data/local/tmp",
|
|
73
|
-
device=options.serial
|
|
68
|
+
self.dev.sync.push(
|
|
69
|
+
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64/libfastbot_native.so"),
|
|
70
|
+
"/data/local/tmp/x86_64/libfastbot_native.so",
|
|
74
71
|
)
|
|
75
72
|
|
|
76
73
|
t = self._startFastbotService()
|
|
@@ -79,29 +76,88 @@ class FastbotManager:
|
|
|
79
76
|
return t
|
|
80
77
|
|
|
81
78
|
|
|
82
|
-
def check_alive(self
|
|
79
|
+
def check_alive(self):
|
|
83
80
|
"""
|
|
84
81
|
check if the script driver and proxy server are alive.
|
|
85
82
|
"""
|
|
83
|
+
|
|
86
84
|
for _ in range(10):
|
|
87
85
|
sleep(2)
|
|
88
86
|
try:
|
|
89
|
-
|
|
87
|
+
_http_request(
|
|
88
|
+
dev=self.dev,
|
|
89
|
+
device_port=8090,
|
|
90
|
+
method="GET",
|
|
91
|
+
path="/ping",
|
|
92
|
+
)
|
|
90
93
|
return
|
|
91
94
|
except requests.ConnectionError:
|
|
92
95
|
logger.info("waiting for connection.")
|
|
93
96
|
pass
|
|
94
97
|
raise RuntimeError("Failed to connect fastbot")
|
|
95
98
|
|
|
99
|
+
def request(self, method: str, path: str, data: Dict=None, timeout: int=10) -> HTTPResponse:
|
|
100
|
+
return _http_request(self.dev, 8090, method, path, data, timeout)
|
|
101
|
+
|
|
102
|
+
def init(self, options: "Options", stamp):
|
|
103
|
+
post_data = {
|
|
104
|
+
"takeScreenshots": options.take_screenshots,
|
|
105
|
+
"Stamp": stamp,
|
|
106
|
+
"deviceOutputRoot": options.device_output_root,
|
|
107
|
+
}
|
|
108
|
+
r = _http_request(
|
|
109
|
+
self.dev,
|
|
110
|
+
device_port=8090,
|
|
111
|
+
method="POST",
|
|
112
|
+
path="/init",
|
|
113
|
+
data=post_data
|
|
114
|
+
)
|
|
115
|
+
print(f"[INFO] Init fastbot: {post_data}", flush=True)
|
|
116
|
+
import re
|
|
117
|
+
self._device_output_dir = re.match(r"outputDir:(.+)", r.text).group(1)
|
|
118
|
+
print(f"[INFO] Fastbot initiated. outputDir: {r.text}", flush=True)
|
|
119
|
+
|
|
120
|
+
def stepMonkey(self, monkeyStepInfo):
|
|
121
|
+
r = self.request(
|
|
122
|
+
method="POST",
|
|
123
|
+
path="/stepMonkey",
|
|
124
|
+
data=monkeyStepInfo
|
|
125
|
+
)
|
|
126
|
+
return r.json()["result"]
|
|
127
|
+
|
|
128
|
+
def stopMonkey(self):
|
|
129
|
+
"""
|
|
130
|
+
send a stop monkey request to the server.
|
|
131
|
+
"""
|
|
132
|
+
r = self.request(
|
|
133
|
+
method="GET",
|
|
134
|
+
path="/stopMonkey",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
print(f"[Server INFO] {r.text}", flush=True)
|
|
138
|
+
|
|
139
|
+
def logScript(self, execution_info: Dict):
|
|
140
|
+
r = self.request(
|
|
141
|
+
method="POST",
|
|
142
|
+
path="/logScript",
|
|
143
|
+
data=execution_info
|
|
144
|
+
)
|
|
145
|
+
res = r.text
|
|
146
|
+
if res != "OK":
|
|
147
|
+
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
96
148
|
|
|
97
|
-
|
|
149
|
+
@property
|
|
150
|
+
def device_output_dir(self):
|
|
151
|
+
return self._device_output_dir
|
|
152
|
+
|
|
153
|
+
def _startFastbotService(self) -> ADBStreamShell_V2:
|
|
98
154
|
shell_command = [
|
|
99
155
|
"CLASSPATH="
|
|
100
156
|
"/sdcard/monkeyq.jar:"
|
|
101
157
|
"/sdcard/framework.jar:"
|
|
102
158
|
"/sdcard/fastbot-thirdpart.jar:"
|
|
103
159
|
"/sdcard/kea2-thirdpart.jar",
|
|
104
|
-
|
|
160
|
+
|
|
105
161
|
"exec", "app_process",
|
|
106
162
|
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
107
163
|
"-p", *self.options.packageNames,
|
|
@@ -119,37 +175,39 @@ class FastbotManager:
|
|
|
119
175
|
|
|
120
176
|
full_cmd = ["adb"] + (["-s", self.options.serial] if self.options.serial else []) + ["shell"] + shell_command
|
|
121
177
|
|
|
178
|
+
|
|
122
179
|
outfile = open(self.log_file, "w", encoding="utf-8", buffering=1)
|
|
123
180
|
|
|
124
181
|
logger.info("Options info: {}".format(asdict(self.options)))
|
|
125
182
|
logger.info("Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)))
|
|
126
183
|
logger.info("Fastbot log will be saved to {}".format(outfile.name))
|
|
127
184
|
|
|
185
|
+
# stream = self.dev.shell(shell_command, encoding="utf-8", stream=True, timeout=float("inf"))
|
|
128
186
|
# process handler
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
t.
|
|
132
|
-
|
|
187
|
+
t = self.dev.stream_shell(shell_command, stdout=outfile, stderr=outfile)
|
|
188
|
+
# proc = subprocess.Popen(full_cmd, stdout=outfile, stderr=outfile)
|
|
189
|
+
# t = threading.Thread(target=self.close_on_exit, args=(proc, outfile), daemon=True)
|
|
190
|
+
# t.start()
|
|
133
191
|
return t
|
|
134
192
|
|
|
135
|
-
def close_on_exit(self, proc:
|
|
193
|
+
def close_on_exit(self, proc: ADBStreamShell_V2, f: IO):
|
|
136
194
|
self.return_code = proc.wait()
|
|
137
195
|
f.close()
|
|
138
196
|
if self.return_code != 0:
|
|
139
197
|
raise RuntimeError(f"Fastbot Error: Terminated with [code {self.return_code}] See {self.log_file} for details.")
|
|
140
198
|
|
|
141
199
|
def get_return_code(self):
|
|
142
|
-
if self.thread:
|
|
200
|
+
if self.thread.is_running():
|
|
143
201
|
logger.info("Waiting for Fastbot to exit.")
|
|
144
|
-
self.thread.
|
|
145
|
-
return self.
|
|
202
|
+
return self.thread.wait()
|
|
203
|
+
return self.thread.poll()
|
|
146
204
|
|
|
147
205
|
def start(self):
|
|
148
206
|
self.thread = self._activateFastbot()
|
|
149
207
|
|
|
150
208
|
def join(self):
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
self.thread.join()
|
|
210
|
+
|
|
153
211
|
|
|
154
212
|
|
|
155
213
|
|
kea2/keaUtils.py
CHANGED
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
import traceback
|
|
5
|
+
import time
|
|
5
6
|
from typing import Callable, Any, Dict, List, Literal, NewType, TypedDict, Union
|
|
6
7
|
from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult
|
|
7
8
|
import random
|
|
@@ -14,7 +15,7 @@ from .bug_report_generator import BugReportGenerator
|
|
|
14
15
|
from .resultSyncer import ResultSyncer
|
|
15
16
|
from .logWatcher import LogWatcher
|
|
16
17
|
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
17
|
-
from .u2Driver import StaticU2UiObject
|
|
18
|
+
from .u2Driver import StaticU2UiObject
|
|
18
19
|
from .fastbotManager import FastbotManager
|
|
19
20
|
import uiautomator2 as u2
|
|
20
21
|
import types
|
|
@@ -110,6 +111,8 @@ class Options:
|
|
|
110
111
|
packageNames: List[str]
|
|
111
112
|
# target device
|
|
112
113
|
serial: str = None
|
|
114
|
+
# target device with transport_id
|
|
115
|
+
transport_id: str = None
|
|
113
116
|
# test agent. "native" for stage 1 and "u2" for stage 1~3
|
|
114
117
|
agent: Literal["u2", "native"] = "u2"
|
|
115
118
|
# max step in exploration (availble in stage 2~3)
|
|
@@ -139,8 +142,13 @@ class Options:
|
|
|
139
142
|
def __post_init__(self):
|
|
140
143
|
import logging
|
|
141
144
|
logging.basicConfig(level=logging.DEBUG if self.debug else logging.INFO)
|
|
142
|
-
if self.
|
|
143
|
-
|
|
145
|
+
if self.Driver:
|
|
146
|
+
target_device = dict()
|
|
147
|
+
if self.serial:
|
|
148
|
+
target_device["serial"] = self.serial
|
|
149
|
+
if self.transport_id:
|
|
150
|
+
target_device["transport_id"] = self.transport_id
|
|
151
|
+
self.Driver.setDevice(target_device)
|
|
144
152
|
global LOGFILE, RESFILE, STAMP
|
|
145
153
|
if self.log_stamp:
|
|
146
154
|
illegal_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\n', '\r', '\t', '\0']
|
|
@@ -313,10 +321,11 @@ class KeaTestRunner(TextTestRunner):
|
|
|
313
321
|
result.flushResult(outfile=RESFILE)
|
|
314
322
|
# setUp for the u2 driver
|
|
315
323
|
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
316
|
-
fb.check_alive(
|
|
317
|
-
|
|
324
|
+
fb.check_alive()
|
|
325
|
+
|
|
326
|
+
fb.init(options=self.options, stamp=STAMP)
|
|
318
327
|
|
|
319
|
-
resultSyncer = ResultSyncer(
|
|
328
|
+
resultSyncer = ResultSyncer(fb.device_output_dir, self.options)
|
|
320
329
|
resultSyncer.run()
|
|
321
330
|
|
|
322
331
|
end_by_remote = False
|
|
@@ -331,7 +340,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
331
340
|
)
|
|
332
341
|
|
|
333
342
|
try:
|
|
334
|
-
xml_raw =
|
|
343
|
+
xml_raw = fb.stepMonkey(self._monkeyStepInfo)
|
|
335
344
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
336
345
|
except requests.ConnectionError:
|
|
337
346
|
logger.info("Connection refused by remote.")
|
|
@@ -344,8 +353,6 @@ class KeaTestRunner(TextTestRunner):
|
|
|
344
353
|
if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
|
|
345
354
|
resultSyncer.sync_event.set()
|
|
346
355
|
|
|
347
|
-
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
348
|
-
|
|
349
356
|
# Go to the next round if no precond satisfied
|
|
350
357
|
if len(propsSatisfiedPrecond) == 0:
|
|
351
358
|
continue
|
|
@@ -371,18 +378,18 @@ class KeaTestRunner(TextTestRunner):
|
|
|
371
378
|
print("execute property %s." % execPropName, flush=True)
|
|
372
379
|
|
|
373
380
|
result.addExcuted(test)
|
|
374
|
-
|
|
381
|
+
fb.logScript(result.lastExecutedInfo)
|
|
375
382
|
try:
|
|
376
383
|
test(result)
|
|
377
384
|
finally:
|
|
378
385
|
result.printErrors()
|
|
379
386
|
|
|
380
387
|
result.updateExectedInfo()
|
|
381
|
-
|
|
388
|
+
fb.logScript(result.lastExecutedInfo)
|
|
382
389
|
result.flushResult(outfile=RESFILE)
|
|
383
390
|
|
|
384
391
|
if not end_by_remote:
|
|
385
|
-
|
|
392
|
+
fb.stopMonkey()
|
|
386
393
|
result.flushResult(outfile=RESFILE)
|
|
387
394
|
resultSyncer.close()
|
|
388
395
|
|
|
@@ -426,40 +433,46 @@ class KeaTestRunner(TextTestRunner):
|
|
|
426
433
|
self.stream.flush()
|
|
427
434
|
return result
|
|
428
435
|
|
|
429
|
-
def stepMonkey(self) -> str:
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
# def stepMonkey(self) -> str:
|
|
437
|
+
# """
|
|
438
|
+
# send a step monkey request to the server and get the xml string.
|
|
439
|
+
# """
|
|
440
|
+
# block_dict = self._getBlockedWidgets()
|
|
441
|
+
# block_widgets: List[str] = block_dict['widgets']
|
|
442
|
+
# block_trees: List[str] = block_dict['trees']
|
|
443
|
+
# URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
|
|
444
|
+
# logger.debug(f"Sending request: {URL}")
|
|
445
|
+
# logger.debug(f"Blocking widgets: {block_widgets}")
|
|
446
|
+
# logger.debug(f"Blocking trees: {block_trees}")
|
|
447
|
+
# r = requests.post(
|
|
448
|
+
# url=URL,
|
|
449
|
+
# json={
|
|
450
|
+
# "steps_count": self.stepsCount,
|
|
451
|
+
# "block_widgets": block_widgets,
|
|
452
|
+
# "block_trees": block_trees
|
|
453
|
+
# }
|
|
454
|
+
# )
|
|
455
|
+
|
|
456
|
+
# res = json.loads(r.content)
|
|
457
|
+
# xml_raw = res["result"]
|
|
458
|
+
# return xml_raw
|
|
459
|
+
|
|
460
|
+
@property
|
|
461
|
+
def _monkeyStepInfo(self):
|
|
462
|
+
r = self._get_block_widgets()
|
|
463
|
+
r["steps_count"] = self.stepsCount
|
|
464
|
+
return r
|
|
465
|
+
|
|
466
|
+
def _get_block_widgets(self):
|
|
433
467
|
block_dict = self._getBlockedWidgets()
|
|
434
468
|
block_widgets: List[str] = block_dict['widgets']
|
|
435
469
|
block_trees: List[str] = block_dict['trees']
|
|
436
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
|
|
437
|
-
logger.debug(f"Sending request: {URL}")
|
|
438
470
|
logger.debug(f"Blocking widgets: {block_widgets}")
|
|
439
471
|
logger.debug(f"Blocking trees: {block_trees}")
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
"block_widgets": block_widgets,
|
|
445
|
-
"block_trees": block_trees
|
|
446
|
-
}
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
res = json.loads(r.content)
|
|
450
|
-
xml_raw = res["result"]
|
|
451
|
-
return xml_raw
|
|
452
|
-
|
|
453
|
-
def stopMonkey(self) -> str:
|
|
454
|
-
"""
|
|
455
|
-
send a stop monkey request to the server and get the xml string.
|
|
456
|
-
"""
|
|
457
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/stopMonkey"
|
|
458
|
-
logger.debug(f"Sending request: {URL}")
|
|
459
|
-
r = requests.get(URL)
|
|
460
|
-
|
|
461
|
-
res = r.content.decode(encoding="utf-8")
|
|
462
|
-
print(f"[Server INFO] {res}", flush=True)
|
|
472
|
+
return {
|
|
473
|
+
"block_widgets": block_widgets,
|
|
474
|
+
"block_trees": block_trees
|
|
475
|
+
}
|
|
463
476
|
|
|
464
477
|
def getValidProperties(self, xml_raw: str, result: JsonResult) -> PropertyStore:
|
|
465
478
|
|
|
@@ -478,46 +491,26 @@ class KeaTestRunner(TextTestRunner):
|
|
|
478
491
|
if not precond(test):
|
|
479
492
|
valid = False
|
|
480
493
|
break
|
|
494
|
+
except u2.UiObjectNotFoundError as e:
|
|
495
|
+
valid = False
|
|
496
|
+
break
|
|
481
497
|
except Exception as e:
|
|
482
|
-
|
|
498
|
+
logger.error(f"Error when checking precond: {getFullPropName(test)}")
|
|
483
499
|
traceback.print_exc()
|
|
484
500
|
valid = False
|
|
485
501
|
break
|
|
486
502
|
# if all the precond passed. make it the candidate prop.
|
|
487
503
|
if valid:
|
|
488
|
-
logger.debug(f"precond satisfied: {getFullPropName(test)}")
|
|
489
504
|
if result.getExcuted(test) >= getattr(prop, MAX_TRIES_MARKER, float("inf")):
|
|
490
|
-
|
|
505
|
+
print(f"{getFullPropName(test)} has reached its max_tries. Skip.", flush=True)
|
|
491
506
|
continue
|
|
492
507
|
validProps[propName] = test
|
|
493
|
-
return validProps
|
|
494
508
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
)
|
|
501
|
-
res = r.content.decode(encoding="utf-8")
|
|
502
|
-
if res != "OK":
|
|
503
|
-
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
504
|
-
|
|
505
|
-
def _init(self):
|
|
506
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/init"
|
|
507
|
-
data = {
|
|
508
|
-
"takeScreenshots": self.options.take_screenshots,
|
|
509
|
-
"Stamp": STAMP,
|
|
510
|
-
"deviceOutputRoot": self.options.device_output_root,
|
|
511
|
-
}
|
|
512
|
-
print(f"[INFO] Init fastbot: {data}", flush=True)
|
|
513
|
-
r = requests.post(
|
|
514
|
-
url=URL,
|
|
515
|
-
json=data
|
|
516
|
-
)
|
|
517
|
-
res = r.content.decode(encoding="utf-8")
|
|
518
|
-
import re
|
|
519
|
-
self.device_output_dir = re.match(r"outputDir:(.+)", res).group(1)
|
|
520
|
-
print(f"[INFO] Fastbot initiated. outputDir: {res}", flush=True)
|
|
509
|
+
print(f"{len(validProps)} precond satisfied.", flush=True)
|
|
510
|
+
if len(validProps) > 0:
|
|
511
|
+
print("[INFO] Valid properties:",flush=True)
|
|
512
|
+
print("\n".join([f' - {getFullPropName(p)}' for p in validProps.values()]), flush=True)
|
|
513
|
+
return validProps
|
|
521
514
|
|
|
522
515
|
def collectAllProperties(self, test: TestSuite):
|
|
523
516
|
"""collect all the properties to prepare for PBT
|
|
@@ -619,62 +612,74 @@ class KeaTestRunner(TextTestRunner):
|
|
|
619
612
|
"""
|
|
620
613
|
def _get_xpath_widgets(func):
|
|
621
614
|
blocked_set = set()
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
615
|
+
script_driver = self.options.Driver.getScriptDriver()
|
|
616
|
+
preconds = getattr(func, PRECONDITIONS_MARKER, [])
|
|
617
|
+
|
|
618
|
+
def preconds_pass(preconds):
|
|
619
|
+
try:
|
|
620
|
+
return all(precond(script_driver) for precond in preconds)
|
|
621
|
+
except u2.UiObjectNotFoundError as e:
|
|
622
|
+
return False
|
|
623
|
+
except Exception as e:
|
|
624
|
+
logger.error(f"Error processing precond. Check if precond: {e}")
|
|
625
|
+
traceback.print_exc()
|
|
626
|
+
return False
|
|
627
|
+
|
|
628
|
+
if preconds_pass(preconds):
|
|
629
|
+
try:
|
|
626
630
|
_widgets = func(self.options.Driver.getStaticChecker())
|
|
627
631
|
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
628
632
|
for w in _widgets:
|
|
629
633
|
if isinstance(w, StaticU2UiObject):
|
|
630
|
-
xpath = selector_to_xpath(w.selector
|
|
631
|
-
|
|
634
|
+
xpath = w.selector_to_xpath(w.selector)
|
|
635
|
+
if xpath != '//error':
|
|
636
|
+
blocked_set.add(xpath)
|
|
632
637
|
elif isinstance(w, u2.xpath.XPathSelector):
|
|
633
638
|
xpath = w._parent.xpath
|
|
634
639
|
blocked_set.add(xpath)
|
|
635
640
|
else:
|
|
636
|
-
logger.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
logger.error(f"block widget defined in {func.__name__} Not supported.")
|
|
642
|
+
except Exception as e:
|
|
643
|
+
logger.error(f"Error processing blocked widgets in: {func}")
|
|
644
|
+
logger.error(e)
|
|
645
|
+
traceback.print_exc()
|
|
640
646
|
return blocked_set
|
|
641
647
|
|
|
642
|
-
|
|
648
|
+
result = {
|
|
643
649
|
"widgets": set(),
|
|
644
650
|
"trees": set()
|
|
645
651
|
}
|
|
646
652
|
|
|
647
|
-
|
|
648
653
|
for func in self._blockWidgetFuncs["widgets"]:
|
|
649
654
|
widgets = _get_xpath_widgets(func)
|
|
650
|
-
|
|
651
|
-
|
|
655
|
+
result["widgets"].update(widgets)
|
|
652
656
|
|
|
653
657
|
for func in self._blockWidgetFuncs["trees"]:
|
|
654
658
|
trees = _get_xpath_widgets(func)
|
|
655
|
-
|
|
659
|
+
result["trees"].update(trees)
|
|
656
660
|
|
|
661
|
+
result["widgets"] = list(result["widgets"] - result["trees"])
|
|
662
|
+
result["trees"] = list(result["trees"])
|
|
657
663
|
|
|
658
|
-
|
|
659
|
-
res["trees"] = list(res["trees"])
|
|
660
|
-
|
|
661
|
-
return res
|
|
664
|
+
return result
|
|
662
665
|
|
|
663
666
|
|
|
664
667
|
def __del__(self):
|
|
665
668
|
"""tearDown method. Cleanup the env.
|
|
666
669
|
"""
|
|
667
|
-
try:
|
|
668
|
-
self.stopMonkey()
|
|
669
|
-
except Exception as e:
|
|
670
|
-
pass
|
|
671
|
-
|
|
672
670
|
if self.options.Driver:
|
|
673
671
|
self.options.Driver.tearDown()
|
|
674
672
|
|
|
675
673
|
try:
|
|
674
|
+
start_time = time.time()
|
|
676
675
|
logger.info("Generating bug report")
|
|
677
676
|
report_generator = BugReportGenerator(self.options.output_dir)
|
|
678
677
|
report_generator.generate_report()
|
|
678
|
+
|
|
679
|
+
end_time = time.time()
|
|
680
|
+
generation_time = end_time - start_time
|
|
681
|
+
|
|
682
|
+
logger.info(f"Bug report generation completed in {generation_time:.2f} seconds")
|
|
683
|
+
|
|
679
684
|
except Exception as e:
|
|
680
685
|
logger.error(f"Error generating bug report: {e}", flush=True)
|
kea2/kea_launcher.py
CHANGED
|
@@ -9,10 +9,22 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
|
|
|
9
9
|
"-s",
|
|
10
10
|
"--serial",
|
|
11
11
|
dest="serial",
|
|
12
|
+
required=False,
|
|
13
|
+
default=None,
|
|
12
14
|
type=str,
|
|
13
15
|
help="The serial of your device. Can be found with `adb devices`",
|
|
14
16
|
)
|
|
15
17
|
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"-t",
|
|
20
|
+
"--transport-id",
|
|
21
|
+
dest="transport_id",
|
|
22
|
+
required=False,
|
|
23
|
+
default=None,
|
|
24
|
+
type=str,
|
|
25
|
+
help="transport-id of your device, can be found with `adb devices -l`",
|
|
26
|
+
)
|
|
27
|
+
|
|
16
28
|
parser.add_argument(
|
|
17
29
|
"-p",
|
|
18
30
|
"--packages",
|
|
@@ -128,6 +140,8 @@ def driver_info_logger(args):
|
|
|
128
140
|
print("[INFO] Driver Settings:", flush=True)
|
|
129
141
|
if args.serial:
|
|
130
142
|
print(" serial:", args.serial, flush=True)
|
|
143
|
+
if args.transport_id:
|
|
144
|
+
print(" transport_id:", args.transport_id, flush=True)
|
|
131
145
|
if args.package_names:
|
|
132
146
|
print(" package_names:", args.package_names, flush=True)
|
|
133
147
|
if args.agent:
|
|
@@ -174,6 +188,7 @@ def run(args=None):
|
|
|
174
188
|
Driver=U2Driver,
|
|
175
189
|
packageNames=args.package_names,
|
|
176
190
|
serial=args.serial,
|
|
191
|
+
transport_id=args.transport_id,
|
|
177
192
|
running_mins=args.running_minutes,
|
|
178
193
|
maxStep=args.max_step,
|
|
179
194
|
throttle=args.throttle_ms,
|
kea2/resultSyncer.py
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
import threading
|
|
2
|
-
from .adbUtils import
|
|
3
|
+
from .adbUtils import ADBDevice
|
|
3
4
|
from .utils import getLogger
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .keaUtils import Options
|
|
4
8
|
|
|
5
9
|
logger = getLogger(__name__)
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
class ResultSyncer:
|
|
9
13
|
|
|
10
|
-
def __init__(self, device_output_dir,
|
|
14
|
+
def __init__(self, device_output_dir, options: "Options"):
|
|
11
15
|
self.device_output_dir = device_output_dir
|
|
12
|
-
self.output_dir = output_dir
|
|
16
|
+
self.output_dir = Path(options.output_dir) / Path(device_output_dir).name
|
|
13
17
|
self.running = False
|
|
14
18
|
self.thread = None
|
|
15
19
|
self.sync_event = threading.Event()
|
|
16
20
|
|
|
21
|
+
ADBDevice.setDevice(serial=options.serial, transport_id=options.transport_id)
|
|
22
|
+
self.dev = ADBDevice()
|
|
23
|
+
|
|
17
24
|
def run(self):
|
|
18
25
|
"""Start a background thread to sync device data when triggered"""
|
|
19
26
|
self.running = True
|
|
@@ -37,7 +44,8 @@ class ResultSyncer:
|
|
|
37
44
|
try:
|
|
38
45
|
logger.debug(f"Removing device output directory: {self.device_output_dir}")
|
|
39
46
|
remove_device_dir = ["rm", "-rf", self.device_output_dir]
|
|
40
|
-
adb_shell(remove_device_dir)
|
|
47
|
+
# adb_shell(remove_device_dir)
|
|
48
|
+
self.dev.shell(remove_device_dir)
|
|
41
49
|
except Exception as e:
|
|
42
50
|
logger.error(f"Error removing device output directory: {e}", flush=True)
|
|
43
51
|
|
|
@@ -48,9 +56,11 @@ class ResultSyncer:
|
|
|
48
56
|
try:
|
|
49
57
|
logger.debug("Syncing data")
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
self.dev.sync.pull_dir(self.device_output_dir, self.output_dir, exist_ok=True)
|
|
60
|
+
# pull_file(self.device_output_dir, str(self.output_dir))
|
|
52
61
|
|
|
53
|
-
remove_pulled_screenshots = ["find", self.device_output_dir, "-name", "
|
|
54
|
-
|
|
62
|
+
remove_pulled_screenshots = ["find", self.device_output_dir, "-name", '"*.png"', "-delete"]
|
|
63
|
+
self.dev.shell(remove_pulled_screenshots)
|
|
64
|
+
# adb_shell(remove_pulled_screenshots)
|
|
55
65
|
except Exception as e:
|
|
56
|
-
logger.error(f"Error in data sync: {e}"
|
|
66
|
+
logger.error(f"Error in data sync: {e}")
|