Kea2-python 0.0.1b3__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_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 +111 -141
- kea2/kea_launcher.py +16 -4
- kea2/logWatcher.py +4 -3
- 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.1b3.dist-info/METADATA +0 -506
- kea2_python-0.0.1b3.dist-info/RECORD +0 -32
- {kea2_python-0.0.1b3.dist-info → kea2_python-0.1.0.dist-info}/WHEEL +0 -0
- {kea2_python-0.0.1b3.dist-info → kea2_python-0.1.0.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.0.1b3.dist-info → kea2_python-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.0.1b3.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,129 +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
|
-
# _block_trees_funcs = None
|
|
310
242
|
|
|
311
243
|
@classmethod
|
|
312
244
|
def setOptions(cls, options: Options):
|
|
313
245
|
if not isinstance(options.packageNames, list) and len(options.packageNames) > 0:
|
|
314
246
|
raise ValueError("packageNames should be given in a list.")
|
|
315
247
|
if options.Driver is not None and options.agent == "native":
|
|
316
|
-
|
|
248
|
+
logger.warning("[Warning] Can not use any Driver when runing native mode.")
|
|
317
249
|
options.Driver = None
|
|
318
250
|
cls.options = options
|
|
319
251
|
|
|
@@ -332,7 +264,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
332
264
|
self.collectAllProperties(test)
|
|
333
265
|
|
|
334
266
|
if len(self.allProperties) == 0:
|
|
335
|
-
|
|
267
|
+
logger.warning("[Warning] No property has been found.")
|
|
336
268
|
|
|
337
269
|
self._setOuputDir()
|
|
338
270
|
|
|
@@ -361,38 +293,44 @@ class KeaTestRunner(TextTestRunner):
|
|
|
361
293
|
message=r"Please use assert\w+ instead.",
|
|
362
294
|
)
|
|
363
295
|
|
|
364
|
-
t = activateFastbot(options=self.options)
|
|
365
296
|
log_watcher = LogWatcher(LOGFILE)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
297
|
+
fb = FastbotManager(self.options, LOGFILE)
|
|
298
|
+
fb.start()
|
|
299
|
+
|
|
300
|
+
if self.options.agent == "u2":
|
|
369
301
|
# initialize the result.json file
|
|
370
302
|
result.flushResult(outfile=RESFILE)
|
|
371
303
|
# setUp for the u2 driver
|
|
372
304
|
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
373
|
-
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()
|
|
374
310
|
|
|
375
311
|
end_by_remote = False
|
|
376
312
|
self.stepsCount = 0
|
|
377
313
|
while self.stepsCount < self.options.maxStep:
|
|
378
314
|
|
|
379
315
|
self.stepsCount += 1
|
|
380
|
-
|
|
316
|
+
logger.info("Sending monkeyEvent {}".format(
|
|
381
317
|
f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf")
|
|
382
318
|
else f"({self.stepsCount})"
|
|
383
319
|
)
|
|
384
|
-
|
|
320
|
+
)
|
|
385
321
|
|
|
386
322
|
try:
|
|
387
323
|
xml_raw = self.stepMonkey()
|
|
388
|
-
stat = self._getStat()
|
|
389
324
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
390
325
|
except requests.ConnectionError:
|
|
391
|
-
|
|
392
|
-
"[INFO] Exploration times up (--running-minutes)."
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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()
|
|
396
334
|
|
|
397
335
|
print(f"{len(propsSatisfiedPrecond)} precond satisfied.", flush=True)
|
|
398
336
|
|
|
@@ -421,17 +359,22 @@ class KeaTestRunner(TextTestRunner):
|
|
|
421
359
|
print("execute property %s." % execPropName, flush=True)
|
|
422
360
|
|
|
423
361
|
result.addExcuted(test)
|
|
362
|
+
self._logScript(result.lastExecutedInfo)
|
|
424
363
|
try:
|
|
425
364
|
test(result)
|
|
426
365
|
finally:
|
|
427
366
|
result.printErrors()
|
|
428
367
|
|
|
368
|
+
result.updateExectedInfo()
|
|
369
|
+
self._logScript(result.lastExecutedInfo)
|
|
429
370
|
result.flushResult(outfile=RESFILE)
|
|
430
371
|
|
|
431
372
|
if not end_by_remote:
|
|
432
373
|
self.stopMonkey()
|
|
433
374
|
result.flushResult(outfile=RESFILE)
|
|
434
|
-
|
|
375
|
+
resultSyncer.close()
|
|
376
|
+
|
|
377
|
+
fb.join()
|
|
435
378
|
print(f"Finish sending monkey events.", flush=True)
|
|
436
379
|
log_watcher.close()
|
|
437
380
|
|
|
@@ -537,14 +480,31 @@ class KeaTestRunner(TextTestRunner):
|
|
|
537
480
|
validProps[propName] = test
|
|
538
481
|
return validProps
|
|
539
482
|
|
|
540
|
-
def
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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)
|
|
548
508
|
|
|
549
509
|
def collectAllProperties(self, test: TestSuite):
|
|
550
510
|
"""collect all the properties to prepare for PBT
|
|
@@ -560,8 +520,8 @@ class KeaTestRunner(TextTestRunner):
|
|
|
560
520
|
"""remove the tearDown function in PBT
|
|
561
521
|
"""
|
|
562
522
|
def tearDown(self): ...
|
|
563
|
-
testCase = types.MethodType(tearDown, testCase)
|
|
564
|
-
|
|
523
|
+
testCase.tearDown = types.MethodType(tearDown, testCase)
|
|
524
|
+
|
|
565
525
|
def iter_tests(suite):
|
|
566
526
|
for test in suite:
|
|
567
527
|
if isinstance(test, TestSuite):
|
|
@@ -654,8 +614,8 @@ class KeaTestRunner(TextTestRunner):
|
|
|
654
614
|
_widgets = _widgets if isinstance(_widgets, list) else [_widgets]
|
|
655
615
|
for w in _widgets:
|
|
656
616
|
if isinstance(w, StaticU2UiObject):
|
|
657
|
-
xpath =
|
|
658
|
-
blocked_set.add(xpath)
|
|
617
|
+
xpath = selector_to_xpath(w.selector, True)
|
|
618
|
+
blocked_set.add(xpath)
|
|
659
619
|
elif isinstance(w, u2.xpath.XPathSelector):
|
|
660
620
|
xpath = w._parent.xpath
|
|
661
621
|
blocked_set.add(xpath)
|
|
@@ -691,5 +651,15 @@ class KeaTestRunner(TextTestRunner):
|
|
|
691
651
|
def __del__(self):
|
|
692
652
|
"""tearDown method. Cleanup the env.
|
|
693
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
|
|
694
664
|
if self.options.Driver:
|
|
695
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:
|
|
@@ -65,9 +68,7 @@ class LogWatcher:
|
|
|
65
68
|
t.start()
|
|
66
69
|
|
|
67
70
|
def close(self):
|
|
68
|
-
time.sleep(0.2) # wait for the written logfile close
|
|
69
71
|
self.end_flag = True
|
|
70
|
-
self.read_log()
|
|
71
72
|
|
|
72
73
|
|
|
73
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)
|