Kea2-python 0.1.0b0__py3-none-any.whl → 0.1.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/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 +145 -0
- kea2/keaUtils.py +40 -131
- kea2/logWatcher.py +11 -4
- kea2/templates/bug_report_template.html +937 -0
- kea2/utils.py +1 -5
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.dist-info}/METADATA +12 -15
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.dist-info}/RECORD +15 -12
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.dist-info}/WHEEL +0 -0
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.dist-info}/entry_points.txt +0 -0
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {kea2_python-0.1.0b0.dist-info → kea2_python-0.1.1.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,12 +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 .adbUtils import push_file
|
|
13
|
+
from .bug_report_generator import BugReportGenerator
|
|
17
14
|
from .resultSyncer import ResultSyncer
|
|
18
15
|
from .logWatcher import LogWatcher
|
|
19
16
|
from .utils import TimeStamp, getProjectRoot, getLogger
|
|
20
17
|
from .u2Driver import StaticU2UiObject, selector_to_xpath
|
|
18
|
+
from .fastbotManager import FastbotManager
|
|
21
19
|
import uiautomator2 as u2
|
|
22
20
|
import types
|
|
23
21
|
|
|
@@ -31,6 +29,10 @@ logger = getLogger(__name__)
|
|
|
31
29
|
# Class Typing
|
|
32
30
|
PropName = NewType("PropName", str)
|
|
33
31
|
PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
|
|
32
|
+
PropertyExecutionInfo = TypedDict(
|
|
33
|
+
"PropertyExecutionInfo",
|
|
34
|
+
{"propName": PropName, "state": Literal["start", "pass", "fail", "error"]}
|
|
35
|
+
)
|
|
34
36
|
|
|
35
37
|
STAMP = TimeStamp().getTimeStamp()
|
|
36
38
|
LOGFILE: str
|
|
@@ -185,7 +187,7 @@ def getFullPropName(testCase: TestCase):
|
|
|
185
187
|
class JsonResult(TextTestResult):
|
|
186
188
|
res: PBTTestResult
|
|
187
189
|
|
|
188
|
-
lastExecutedInfo:
|
|
190
|
+
lastExecutedInfo: PropertyExecutionInfo = {
|
|
189
191
|
"propName": "",
|
|
190
192
|
"state": "",
|
|
191
193
|
}
|
|
@@ -223,128 +225,27 @@ class JsonResult(TextTestResult):
|
|
|
223
225
|
self.res[getFullPropName(test)].error += 1
|
|
224
226
|
self.lastExecutedInfo["state"] = "error"
|
|
225
227
|
|
|
228
|
+
def updateExectedInfo(self):
|
|
229
|
+
if self.lastExecutedInfo["state"] == "start":
|
|
230
|
+
self.lastExecutedInfo["state"] = "pass"
|
|
231
|
+
|
|
226
232
|
def getExcuted(self, test: TestCase):
|
|
227
233
|
return self.res[getFullPropName(test)].executed
|
|
228
234
|
|
|
229
235
|
|
|
230
|
-
def activateFastbot(options: Options, port=None) -> threading.Thread:
|
|
231
|
-
"""
|
|
232
|
-
activate fastbot.
|
|
233
|
-
:params: options: the running setting for fastbot
|
|
234
|
-
:params: port: the listening port for script driver
|
|
235
|
-
:return: the fastbot daemon thread
|
|
236
|
-
"""
|
|
237
|
-
cur_dir = Path(__file__).parent
|
|
238
|
-
push_file(
|
|
239
|
-
Path.joinpath(cur_dir, "assets/monkeyq.jar"),
|
|
240
|
-
"/sdcard/monkeyq.jar",
|
|
241
|
-
device=options.serial
|
|
242
|
-
)
|
|
243
|
-
push_file(
|
|
244
|
-
Path.joinpath(cur_dir, "assets/fastbot-thirdpart.jar"),
|
|
245
|
-
"/sdcard/fastbot-thirdpart.jar",
|
|
246
|
-
device=options.serial,
|
|
247
|
-
)
|
|
248
|
-
push_file(
|
|
249
|
-
Path.joinpath(cur_dir, "assets/framework.jar"),
|
|
250
|
-
"/sdcard/framework.jar",
|
|
251
|
-
device=options.serial
|
|
252
|
-
)
|
|
253
|
-
push_file(
|
|
254
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/arm64-v8a"),
|
|
255
|
-
"/data/local/tmp",
|
|
256
|
-
device=options.serial
|
|
257
|
-
)
|
|
258
|
-
push_file(
|
|
259
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/armeabi-v7a"),
|
|
260
|
-
"/data/local/tmp",
|
|
261
|
-
device=options.serial
|
|
262
|
-
)
|
|
263
|
-
push_file(
|
|
264
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86"),
|
|
265
|
-
"/data/local/tmp",
|
|
266
|
-
device=options.serial
|
|
267
|
-
)
|
|
268
|
-
push_file(
|
|
269
|
-
Path.joinpath(cur_dir, "assets/fastbot_libs/x86_64"),
|
|
270
|
-
"/data/local/tmp",
|
|
271
|
-
device=options.serial
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
t = startFastbotService(options)
|
|
275
|
-
print("[INFO] Running Fastbot...", flush=True)
|
|
276
|
-
|
|
277
|
-
return t
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def check_alive(port):
|
|
281
|
-
"""
|
|
282
|
-
check if the script driver and proxy server are alive.
|
|
283
|
-
"""
|
|
284
|
-
for _ in range(10):
|
|
285
|
-
sleep(2)
|
|
286
|
-
try:
|
|
287
|
-
requests.get(f"http://localhost:{port}/ping")
|
|
288
|
-
return
|
|
289
|
-
except requests.ConnectionError:
|
|
290
|
-
print("[INFO] waiting for connection.", flush=True)
|
|
291
|
-
pass
|
|
292
|
-
raise RuntimeError("Failed to connect fastbot")
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
def startFastbotService(options: Options) -> threading.Thread:
|
|
296
|
-
shell_command = [
|
|
297
|
-
"CLASSPATH=/sdcard/monkeyq.jar:/sdcard/framework.jar:/sdcard/fastbot-thirdpart.jar",
|
|
298
|
-
"exec", "app_process",
|
|
299
|
-
"/system/bin", "com.android.commands.monkey.Monkey",
|
|
300
|
-
"-p", *options.packageNames,
|
|
301
|
-
"--agent-u2" if options.agent == "u2" else "--agent",
|
|
302
|
-
"reuseq",
|
|
303
|
-
"--running-minutes", f"{options.running_mins}",
|
|
304
|
-
"--throttle", f"{options.throttle}",
|
|
305
|
-
"--bugreport",
|
|
306
|
-
]
|
|
307
|
-
|
|
308
|
-
if options.profile_period:
|
|
309
|
-
shell_command += ["--profile-period", f"{options.profile_period}"]
|
|
310
|
-
|
|
311
|
-
shell_command += ["-v", "-v", "-v"]
|
|
312
|
-
|
|
313
|
-
full_cmd = ["adb"] + (["-s", options.serial] if options.serial else []) + ["shell"] + shell_command
|
|
314
|
-
|
|
315
|
-
outfile = open(LOGFILE, "w", encoding="utf-8", buffering=1)
|
|
316
|
-
|
|
317
|
-
print("[INFO] Options info: {}".format(asdict(options)), flush=True)
|
|
318
|
-
print("[INFO] Launching fastbot with shell command:\n{}".format(" ".join(full_cmd)), flush=True)
|
|
319
|
-
print("[INFO] Fastbot log will be saved to {}".format(outfile.name), flush=True)
|
|
320
|
-
|
|
321
|
-
# process handler
|
|
322
|
-
proc = subprocess.Popen(full_cmd, stdout=outfile, stderr=outfile)
|
|
323
|
-
t = threading.Thread(target=close_on_exit, args=(proc, outfile), daemon=True)
|
|
324
|
-
t.start()
|
|
325
|
-
|
|
326
|
-
return t
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def close_on_exit(proc: subprocess.Popen, f: IO):
|
|
330
|
-
proc.wait()
|
|
331
|
-
f.close()
|
|
332
|
-
|
|
333
|
-
|
|
334
236
|
class KeaTestRunner(TextTestRunner):
|
|
335
237
|
|
|
336
238
|
resultclass: JsonResult
|
|
337
239
|
allProperties: PropertyStore
|
|
338
240
|
options: Options = None
|
|
339
241
|
_block_funcs: Dict[Literal["widgets", "trees"], List[Callable]] = None
|
|
340
|
-
# _block_trees_funcs = None
|
|
341
242
|
|
|
342
243
|
@classmethod
|
|
343
244
|
def setOptions(cls, options: Options):
|
|
344
245
|
if not isinstance(options.packageNames, list) and len(options.packageNames) > 0:
|
|
345
246
|
raise ValueError("packageNames should be given in a list.")
|
|
346
247
|
if options.Driver is not None and options.agent == "native":
|
|
347
|
-
|
|
248
|
+
logger.warning("[Warning] Can not use any Driver when runing native mode.")
|
|
348
249
|
options.Driver = None
|
|
349
250
|
cls.options = options
|
|
350
251
|
|
|
@@ -354,8 +255,8 @@ class KeaTestRunner(TextTestRunner):
|
|
|
354
255
|
global LOGFILE, RESFILE
|
|
355
256
|
LOGFILE = output_dir / Path(LOGFILE)
|
|
356
257
|
RESFILE = output_dir / Path(RESFILE)
|
|
357
|
-
logger.
|
|
358
|
-
logger.
|
|
258
|
+
logger.info(f"Log file: {LOGFILE}")
|
|
259
|
+
logger.info(f"Result file: {RESFILE}")
|
|
359
260
|
|
|
360
261
|
def run(self, test):
|
|
361
262
|
|
|
@@ -363,7 +264,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
363
264
|
self.collectAllProperties(test)
|
|
364
265
|
|
|
365
266
|
if len(self.allProperties) == 0:
|
|
366
|
-
|
|
267
|
+
logger.warning("[Warning] No property has been found.")
|
|
367
268
|
|
|
368
269
|
self._setOuputDir()
|
|
369
270
|
|
|
@@ -392,16 +293,17 @@ class KeaTestRunner(TextTestRunner):
|
|
|
392
293
|
message=r"Please use assert\w+ instead.",
|
|
393
294
|
)
|
|
394
295
|
|
|
395
|
-
|
|
296
|
+
fb = FastbotManager(self.options, LOGFILE)
|
|
297
|
+
fb.start()
|
|
298
|
+
|
|
396
299
|
log_watcher = LogWatcher(LOGFILE)
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
else:
|
|
300
|
+
|
|
301
|
+
if self.options.agent == "u2":
|
|
400
302
|
# initialize the result.json file
|
|
401
303
|
result.flushResult(outfile=RESFILE)
|
|
402
304
|
# setUp for the u2 driver
|
|
403
305
|
self.scriptDriver = self.options.Driver.getScriptDriver()
|
|
404
|
-
check_alive(port=self.scriptDriver.lport)
|
|
306
|
+
fb.check_alive(port=self.scriptDriver.lport)
|
|
405
307
|
self._init()
|
|
406
308
|
|
|
407
309
|
resultSyncer = ResultSyncer(self.device_output_dir, self.options.output_dir)
|
|
@@ -412,21 +314,21 @@ class KeaTestRunner(TextTestRunner):
|
|
|
412
314
|
while self.stepsCount < self.options.maxStep:
|
|
413
315
|
|
|
414
316
|
self.stepsCount += 1
|
|
415
|
-
|
|
317
|
+
logger.info("Sending monkeyEvent {}".format(
|
|
416
318
|
f"({self.stepsCount} / {self.options.maxStep})" if self.options.maxStep != float("inf")
|
|
417
319
|
else f"({self.stepsCount})"
|
|
418
320
|
)
|
|
419
|
-
|
|
321
|
+
)
|
|
420
322
|
|
|
421
323
|
try:
|
|
422
324
|
xml_raw = self.stepMonkey()
|
|
423
325
|
propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
|
|
424
326
|
except requests.ConnectionError:
|
|
425
|
-
|
|
426
|
-
"[INFO] Exploration times up (--running-minutes)."
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
327
|
+
if fb.get_return_code() == 0:
|
|
328
|
+
logger.info("[INFO] Exploration times up (--running-minutes).")
|
|
329
|
+
end_by_remote = True
|
|
330
|
+
break
|
|
331
|
+
raise RuntimeError("Fastbot Aborted.")
|
|
430
332
|
|
|
431
333
|
if self.options.profile_period and self.stepsCount % self.options.profile_period == 0:
|
|
432
334
|
resultSyncer.sync_event.set()
|
|
@@ -464,17 +366,18 @@ class KeaTestRunner(TextTestRunner):
|
|
|
464
366
|
finally:
|
|
465
367
|
result.printErrors()
|
|
466
368
|
|
|
369
|
+
result.updateExectedInfo()
|
|
467
370
|
self._logScript(result.lastExecutedInfo)
|
|
468
371
|
result.flushResult(outfile=RESFILE)
|
|
469
372
|
|
|
470
373
|
if not end_by_remote:
|
|
471
374
|
self.stopMonkey()
|
|
472
375
|
result.flushResult(outfile=RESFILE)
|
|
473
|
-
resultSyncer.close()
|
|
474
|
-
|
|
376
|
+
resultSyncer.close()
|
|
377
|
+
|
|
378
|
+
fb.join()
|
|
475
379
|
print(f"Finish sending monkey events.", flush=True)
|
|
476
380
|
log_watcher.close()
|
|
477
|
-
|
|
478
381
|
|
|
479
382
|
# Source code from unittest Runner
|
|
480
383
|
# process the result
|
|
@@ -619,7 +522,7 @@ class KeaTestRunner(TextTestRunner):
|
|
|
619
522
|
"""
|
|
620
523
|
def tearDown(self): ...
|
|
621
524
|
testCase.tearDown = types.MethodType(tearDown, testCase)
|
|
622
|
-
|
|
525
|
+
|
|
623
526
|
def iter_tests(suite):
|
|
624
527
|
for test in suite:
|
|
625
528
|
if isinstance(test, TestSuite):
|
|
@@ -749,6 +652,12 @@ class KeaTestRunner(TextTestRunner):
|
|
|
749
652
|
def __del__(self):
|
|
750
653
|
"""tearDown method. Cleanup the env.
|
|
751
654
|
"""
|
|
655
|
+
try:
|
|
656
|
+
logger.debug("Generating test bug report")
|
|
657
|
+
report_generator = BugReportGenerator(self.options.output_dir)
|
|
658
|
+
report_generator.generate_report()
|
|
659
|
+
except Exception as e:
|
|
660
|
+
logger.error(f"Error generating bug report: {e}", flush=True)
|
|
752
661
|
try:
|
|
753
662
|
self.stopMonkey()
|
|
754
663
|
except Exception as e:
|
kea2/logWatcher.py
CHANGED
|
@@ -2,6 +2,10 @@ import re
|
|
|
2
2
|
import os
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
+
from .utils import getLogger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
logger = getLogger(__name__)
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
PATTERN_EXCEPTION = re.compile(r"\[Fastbot\].+Internal\serror\n([\s\S]*)")
|
|
@@ -16,7 +20,7 @@ def thread_excepthook(args):
|
|
|
16
20
|
|
|
17
21
|
class LogWatcher:
|
|
18
22
|
|
|
19
|
-
def watcher(self, poll_interval=
|
|
23
|
+
def watcher(self, poll_interval=0.5):
|
|
20
24
|
self.buffer = ""
|
|
21
25
|
self.last_pos = 0
|
|
22
26
|
|
|
@@ -28,7 +32,6 @@ class LogWatcher:
|
|
|
28
32
|
self.read_log()
|
|
29
33
|
|
|
30
34
|
def read_log(self):
|
|
31
|
-
time.sleep(0.02)
|
|
32
35
|
with open(self.log_file, 'r', encoding='utf-8') as f:
|
|
33
36
|
f.seek(self.last_pos)
|
|
34
37
|
new_data = f.read()
|
|
@@ -60,15 +63,19 @@ class LogWatcher:
|
|
|
60
63
|
, flush=True)
|
|
61
64
|
|
|
62
65
|
def __init__(self, log_file):
|
|
66
|
+
logger.info(f"Watching log: {log_file}")
|
|
63
67
|
self.log_file = log_file
|
|
64
68
|
self.end_flag = False
|
|
65
69
|
|
|
66
70
|
threading.excepthook = thread_excepthook
|
|
67
|
-
t = threading.Thread(target=self.watcher, daemon=True)
|
|
68
|
-
t.start()
|
|
71
|
+
self.t = threading.Thread(target=self.watcher, daemon=True)
|
|
72
|
+
self.t.start()
|
|
69
73
|
|
|
70
74
|
def close(self):
|
|
75
|
+
logger.info("Close: LogWatcher")
|
|
71
76
|
self.end_flag = True
|
|
77
|
+
if self.t:
|
|
78
|
+
self.t.join()
|
|
72
79
|
|
|
73
80
|
|
|
74
81
|
if __name__ == "__main__":
|