Kea2-python 0.1.3__py3-none-any.whl → 0.2.1__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 +281 -20
- kea2/assets/monkeyq.jar +0 -0
- kea2/bug_report_generator.py +334 -322
- kea2/fastbotManager.py +110 -59
- kea2/keaUtils.py +61 -71
- kea2/kea_launcher.py +15 -0
- kea2/logWatcher.py +30 -33
- kea2/resultSyncer.py +18 -8
- kea2/templates/bug_report_template.html +15 -52
- kea2/u2Driver.py +27 -30
- kea2/utils.py +1 -0
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/METADATA +42 -32
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/RECORD +18 -18
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/WHEEL +0 -0
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.1.3.dist-info → kea2_python-0.2.1.dist-info}/top_level.txt +0 -0
kea2/fastbotManager.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
from retry import retry
|
|
2
|
+
from retry.api import retry_call
|
|
1
3
|
from dataclasses import asdict
|
|
2
|
-
import subprocess
|
|
3
|
-
import threading
|
|
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,84 @@ 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
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
def _check_alive_request():
|
|
84
|
+
_http_request(dev=self.dev, device_port=8090, method="GET", path="/ping")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
retry_call(_check_alive_request, tries=10, delay=2)
|
|
88
|
+
except requests.ConnectionError:
|
|
89
|
+
raise RuntimeError("Failed to connect fastbot")
|
|
90
|
+
|
|
91
|
+
def request(self, method: str, path: str, data: Dict=None, timeout: int=10) -> HTTPResponse:
|
|
92
|
+
return _http_request(self.dev, 8090, method, path, data, timeout)
|
|
93
|
+
|
|
94
|
+
@retry(Exception, tries=2, delay=2)
|
|
95
|
+
def init(self, options: "Options", stamp):
|
|
96
|
+
post_data = {
|
|
97
|
+
"takeScreenshots": options.take_screenshots,
|
|
98
|
+
"Stamp": stamp,
|
|
99
|
+
"deviceOutputRoot": options.device_output_root,
|
|
100
|
+
}
|
|
101
|
+
r = _http_request(
|
|
102
|
+
self.dev,
|
|
103
|
+
device_port=8090,
|
|
104
|
+
method="POST",
|
|
105
|
+
path="/init",
|
|
106
|
+
data=post_data
|
|
107
|
+
)
|
|
108
|
+
print(f"[INFO] Init fastbot: {post_data}", flush=True)
|
|
109
|
+
import re
|
|
110
|
+
self._device_output_dir = re.match(r"outputDir:(.+)", r.text).group(1)
|
|
111
|
+
print(f"[INFO] Fastbot initiated. outputDir: {r.text}", flush=True)
|
|
112
|
+
|
|
113
|
+
@retry(Exception, tries=2, delay=2)
|
|
114
|
+
def stepMonkey(self, monkeyStepInfo):
|
|
115
|
+
r = self.request(
|
|
116
|
+
method="POST",
|
|
117
|
+
path="/stepMonkey",
|
|
118
|
+
data=monkeyStepInfo
|
|
119
|
+
)
|
|
120
|
+
return r.json()["result"]
|
|
121
|
+
|
|
122
|
+
@retry(Exception, tries=2, delay=2)
|
|
123
|
+
def stopMonkey(self):
|
|
124
|
+
"""
|
|
125
|
+
send a stop monkey request to the server.
|
|
126
|
+
"""
|
|
127
|
+
r = self.request(
|
|
128
|
+
method="GET",
|
|
129
|
+
path="/stopMonkey",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
print(f"[Server INFO] {r.text}", flush=True)
|
|
133
|
+
|
|
134
|
+
@retry(Exception, tries=2, delay=2)
|
|
135
|
+
def logScript(self, execution_info: Dict):
|
|
136
|
+
r = self.request(
|
|
137
|
+
method="POST",
|
|
138
|
+
path="/logScript",
|
|
139
|
+
data=execution_info
|
|
140
|
+
)
|
|
141
|
+
res = r.text
|
|
142
|
+
if res != "OK":
|
|
143
|
+
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def device_output_dir(self):
|
|
147
|
+
return self._device_output_dir
|
|
148
|
+
|
|
149
|
+
def _startFastbotService(self) -> ADBStreamShell_V2:
|
|
98
150
|
shell_command = [
|
|
99
151
|
"CLASSPATH="
|
|
100
152
|
"/sdcard/monkeyq.jar:"
|
|
101
153
|
"/sdcard/framework.jar:"
|
|
102
154
|
"/sdcard/fastbot-thirdpart.jar:"
|
|
103
155
|
"/sdcard/kea2-thirdpart.jar",
|
|
104
|
-
|
|
156
|
+
|
|
105
157
|
"exec", "app_process",
|
|
106
158
|
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
107
159
|
"-p", *self.options.packageNames,
|
|
@@ -119,37 +171,36 @@ class FastbotManager:
|
|
|
119
171
|
|
|
120
172
|
full_cmd = ["adb"] + (["-s", self.options.serial] if self.options.serial else []) + ["shell"] + shell_command
|
|
121
173
|
|
|
174
|
+
|
|
122
175
|
outfile = open(self.log_file, "w", encoding="utf-8", buffering=1)
|
|
123
176
|
|
|
124
177
|
logger.info("Options info: {}".format(asdict(self.options)))
|
|
125
178
|
logger.info("Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)))
|
|
126
179
|
logger.info("Fastbot log will be saved to {}".format(outfile.name))
|
|
127
180
|
|
|
128
|
-
|
|
129
|
-
proc = subprocess.Popen(full_cmd, stdout=outfile, stderr=outfile)
|
|
130
|
-
t = threading.Thread(target=self.close_on_exit, args=(proc, outfile), daemon=True)
|
|
131
|
-
t.start()
|
|
132
|
-
|
|
181
|
+
t = self.dev.stream_shell(shell_command, stdout=outfile, stderr=outfile)
|
|
133
182
|
return t
|
|
134
183
|
|
|
135
|
-
def close_on_exit(self, proc:
|
|
184
|
+
def close_on_exit(self, proc: ADBStreamShell_V2, f: IO):
|
|
136
185
|
self.return_code = proc.wait()
|
|
137
186
|
f.close()
|
|
138
187
|
if self.return_code != 0:
|
|
139
188
|
raise RuntimeError(f"Fastbot Error: Terminated with [code {self.return_code}] See {self.log_file} for details.")
|
|
140
189
|
|
|
141
190
|
def get_return_code(self):
|
|
142
|
-
if self.thread:
|
|
191
|
+
if self.thread.is_running():
|
|
143
192
|
logger.info("Waiting for Fastbot to exit.")
|
|
144
|
-
self.thread.
|
|
145
|
-
return self.
|
|
193
|
+
return self.thread.wait()
|
|
194
|
+
return self.thread.poll()
|
|
146
195
|
|
|
147
196
|
def start(self):
|
|
197
|
+
# kill the fastbot process if runing.
|
|
198
|
+
self.dev.kill_proc("com.android.commands.monkey")
|
|
148
199
|
self.thread = self._activateFastbot()
|
|
149
200
|
|
|
150
201
|
def join(self):
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
self.thread.join()
|
|
203
|
+
|
|
153
204
|
|
|
154
205
|
|
|
155
206
|
|
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
|
|
@@ -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.")
|
|
@@ -369,18 +378,18 @@ class KeaTestRunner(TextTestRunner):
|
|
|
369
378
|
print("execute property %s." % execPropName, flush=True)
|
|
370
379
|
|
|
371
380
|
result.addExcuted(test)
|
|
372
|
-
|
|
381
|
+
fb.logScript(result.lastExecutedInfo)
|
|
373
382
|
try:
|
|
374
383
|
test(result)
|
|
375
384
|
finally:
|
|
376
385
|
result.printErrors()
|
|
377
386
|
|
|
378
387
|
result.updateExectedInfo()
|
|
379
|
-
|
|
388
|
+
fb.logScript(result.lastExecutedInfo)
|
|
380
389
|
result.flushResult(outfile=RESFILE)
|
|
381
390
|
|
|
382
391
|
if not end_by_remote:
|
|
383
|
-
|
|
392
|
+
fb.stopMonkey()
|
|
384
393
|
result.flushResult(outfile=RESFILE)
|
|
385
394
|
resultSyncer.close()
|
|
386
395
|
|
|
@@ -424,40 +433,46 @@ class KeaTestRunner(TextTestRunner):
|
|
|
424
433
|
self.stream.flush()
|
|
425
434
|
return result
|
|
426
435
|
|
|
427
|
-
def stepMonkey(self) -> str:
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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):
|
|
431
467
|
block_dict = self._getBlockedWidgets()
|
|
432
468
|
block_widgets: List[str] = block_dict['widgets']
|
|
433
469
|
block_trees: List[str] = block_dict['trees']
|
|
434
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/stepMonkey"
|
|
435
|
-
logger.debug(f"Sending request: {URL}")
|
|
436
470
|
logger.debug(f"Blocking widgets: {block_widgets}")
|
|
437
471
|
logger.debug(f"Blocking trees: {block_trees}")
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
"block_widgets": block_widgets,
|
|
443
|
-
"block_trees": block_trees
|
|
444
|
-
}
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
res = json.loads(r.content)
|
|
448
|
-
xml_raw = res["result"]
|
|
449
|
-
return xml_raw
|
|
450
|
-
|
|
451
|
-
def stopMonkey(self) -> str:
|
|
452
|
-
"""
|
|
453
|
-
send a stop monkey request to the server and get the xml string.
|
|
454
|
-
"""
|
|
455
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/stopMonkey"
|
|
456
|
-
logger.debug(f"Sending request: {URL}")
|
|
457
|
-
r = requests.get(URL)
|
|
458
|
-
|
|
459
|
-
res = r.content.decode(encoding="utf-8")
|
|
460
|
-
print(f"[Server INFO] {res}", flush=True)
|
|
472
|
+
return {
|
|
473
|
+
"block_widgets": block_widgets,
|
|
474
|
+
"block_trees": block_trees
|
|
475
|
+
}
|
|
461
476
|
|
|
462
477
|
def getValidProperties(self, xml_raw: str, result: JsonResult) -> PropertyStore:
|
|
463
478
|
|
|
@@ -490,40 +505,13 @@ class KeaTestRunner(TextTestRunner):
|
|
|
490
505
|
print(f"{getFullPropName(test)} has reached its max_tries. Skip.", flush=True)
|
|
491
506
|
continue
|
|
492
507
|
validProps[propName] = test
|
|
493
|
-
|
|
508
|
+
|
|
494
509
|
print(f"{len(validProps)} precond satisfied.", flush=True)
|
|
495
510
|
if len(validProps) > 0:
|
|
496
511
|
print("[INFO] Valid properties:",flush=True)
|
|
497
512
|
print("\n".join([f' - {getFullPropName(p)}' for p in validProps.values()]), flush=True)
|
|
498
513
|
return validProps
|
|
499
514
|
|
|
500
|
-
def _logScript(self, execution_info:Dict):
|
|
501
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/logScript"
|
|
502
|
-
r = requests.post(
|
|
503
|
-
url=URL,
|
|
504
|
-
json=execution_info
|
|
505
|
-
)
|
|
506
|
-
res = r.content.decode(encoding="utf-8")
|
|
507
|
-
if res != "OK":
|
|
508
|
-
print(f"[ERROR] Error when logging script: {execution_info}", flush=True)
|
|
509
|
-
|
|
510
|
-
def _init(self):
|
|
511
|
-
URL = f"http://localhost:{self.scriptDriver.lport}/init"
|
|
512
|
-
data = {
|
|
513
|
-
"takeScreenshots": self.options.take_screenshots,
|
|
514
|
-
"Stamp": STAMP,
|
|
515
|
-
"deviceOutputRoot": self.options.device_output_root,
|
|
516
|
-
}
|
|
517
|
-
print(f"[INFO] Init fastbot: {data}", flush=True)
|
|
518
|
-
r = requests.post(
|
|
519
|
-
url=URL,
|
|
520
|
-
json=data
|
|
521
|
-
)
|
|
522
|
-
res = r.content.decode(encoding="utf-8")
|
|
523
|
-
import re
|
|
524
|
-
self.device_output_dir = re.match(r"outputDir:(.+)", res).group(1)
|
|
525
|
-
print(f"[INFO] Fastbot initiated. outputDir: {res}", flush=True)
|
|
526
|
-
|
|
527
515
|
def collectAllProperties(self, test: TestSuite):
|
|
528
516
|
"""collect all the properties to prepare for PBT
|
|
529
517
|
"""
|
|
@@ -679,17 +667,19 @@ class KeaTestRunner(TextTestRunner):
|
|
|
679
667
|
def __del__(self):
|
|
680
668
|
"""tearDown method. Cleanup the env.
|
|
681
669
|
"""
|
|
682
|
-
try:
|
|
683
|
-
self.stopMonkey()
|
|
684
|
-
except Exception as e:
|
|
685
|
-
pass
|
|
686
|
-
|
|
687
670
|
if self.options.Driver:
|
|
688
671
|
self.options.Driver.tearDown()
|
|
689
672
|
|
|
690
673
|
try:
|
|
674
|
+
start_time = time.time()
|
|
691
675
|
logger.info("Generating bug report")
|
|
692
676
|
report_generator = BugReportGenerator(self.options.output_dir)
|
|
693
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
|
+
|
|
694
684
|
except Exception as e:
|
|
695
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/logWatcher.py
CHANGED
|
@@ -2,7 +2,8 @@ import re
|
|
|
2
2
|
import os
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
-
from
|
|
5
|
+
from typing import IO
|
|
6
|
+
from kea2.utils import getLogger
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
logger = getLogger(__name__)
|
|
@@ -20,47 +21,43 @@ def thread_excepthook(args):
|
|
|
20
21
|
|
|
21
22
|
class LogWatcher:
|
|
22
23
|
|
|
23
|
-
def watcher(self, poll_interval=
|
|
24
|
-
self.buffer = ""
|
|
24
|
+
def watcher(self, poll_interval=3):
|
|
25
25
|
self.last_pos = 0
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
self.
|
|
29
|
-
|
|
27
|
+
with open(self.log_file, "r") as fp:
|
|
28
|
+
while not self.end_flag:
|
|
29
|
+
self.read_log(fp)
|
|
30
|
+
time.sleep(poll_interval)
|
|
31
|
+
|
|
32
|
+
time.sleep(0.2)
|
|
33
|
+
self.read_log(fp)
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
self.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if new_data:
|
|
41
|
-
self.buffer += new_data
|
|
42
|
-
self.parse_log()
|
|
43
|
-
|
|
44
|
-
def parse_log(self):
|
|
45
|
-
buffer = self.buffer
|
|
46
|
-
exception_match = PATTERN_EXCEPTION.search(buffer)
|
|
35
|
+
def read_log(self, f: IO):
|
|
36
|
+
f.seek(self.last_pos)
|
|
37
|
+
buffer = f.read()
|
|
38
|
+
self.last_pos = f.tell()
|
|
39
|
+
|
|
40
|
+
self.parse_log(buffer)
|
|
41
|
+
|
|
42
|
+
def parse_log(self, content):
|
|
43
|
+
exception_match = PATTERN_EXCEPTION.search(content)
|
|
47
44
|
if exception_match:
|
|
48
45
|
exception_body = exception_match.group(1).strip()
|
|
49
46
|
if exception_body:
|
|
50
47
|
raise RuntimeError(
|
|
51
|
-
"[Error] Execption while running fastbot:\n" +
|
|
48
|
+
"[Error] Fatal Execption while running fastbot:\n" +
|
|
52
49
|
exception_body +
|
|
53
50
|
"\nSee fastbot.log for details."
|
|
54
51
|
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
|
|
53
|
+
statistic_match = PATTERN_STATISTIC.search(content)
|
|
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)
|
|
64
61
|
|
|
65
62
|
def __init__(self, log_file):
|
|
66
63
|
logger.info(f"Watching log: {log_file}")
|
|
@@ -79,4 +76,4 @@ class LogWatcher:
|
|
|
79
76
|
|
|
80
77
|
|
|
81
78
|
if __name__ == "__main__":
|
|
82
|
-
LogWatcher("
|
|
79
|
+
LogWatcher("/Users/atria/Desktop/coding/Kea2/output/res_2025062510_0420056539/fastbot_2025062510_0420056539.log")
|
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}")
|