Kea2-python 0.2.1__py3-none-any.whl → 0.2.3__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/fastbotManager.py CHANGED
@@ -13,7 +13,7 @@ from kea2.utils import getLogger
13
13
 
14
14
  from typing import IO, TYPE_CHECKING, Dict
15
15
  if TYPE_CHECKING:
16
- from .keaUtils import Options
16
+ from .keaUtils import Options, PropertyExecutionInfo
17
17
 
18
18
 
19
19
  logger = getLogger(__name__)
@@ -70,6 +70,21 @@ class FastbotManager:
70
70
  "/data/local/tmp/x86_64/libfastbot_native.so",
71
71
  )
72
72
 
73
+ whitelist = self.options.act_whitelist_file
74
+ blacklist = self.options.act_blacklist_file
75
+ if bool(whitelist) ^ bool(blacklist):
76
+ if whitelist:
77
+ file_to_push = cur_dir.parent / 'configs' / 'awl.strings'
78
+ remote_path = whitelist
79
+ else:
80
+ file_to_push = cur_dir.parent / 'configs' / 'abl.strings'
81
+ remote_path = blacklist
82
+
83
+ self.dev.sync.push(
84
+ file_to_push,
85
+ remote_path
86
+ )
87
+
73
88
  t = self._startFastbotService()
74
89
  logger.info("Running Fastbot...")
75
90
 
@@ -132,11 +147,15 @@ class FastbotManager:
132
147
  print(f"[Server INFO] {r.text}", flush=True)
133
148
 
134
149
  @retry(Exception, tries=2, delay=2)
135
- def logScript(self, execution_info: Dict):
150
+ def logScript(self, execution_info: "PropertyExecutionInfo"):
136
151
  r = self.request(
137
152
  method="POST",
138
153
  path="/logScript",
139
- data=execution_info
154
+ data={
155
+ "propName": execution_info.propName,
156
+ "startStepsCount": execution_info.startStepsCount,
157
+ "state": execution_info.state,
158
+ }
140
159
  )
141
160
  res = r.text
142
161
  if res != "OK":
@@ -167,6 +186,14 @@ class FastbotManager:
167
186
  if self.options.profile_period:
168
187
  shell_command += ["--profile-period", f"{self.options.profile_period}"]
169
188
 
189
+ whitelist = self.options.act_whitelist_file
190
+ blacklist = self.options.act_blacklist_file
191
+ if bool(whitelist) ^ bool(blacklist):
192
+ if whitelist:
193
+ shell_command += ["--act-whitelist-file", f"{whitelist}"]
194
+ else:
195
+ shell_command += ["--act-blacklist-file", f"{blacklist}"]
196
+
170
197
  shell_command += ["-v", "-v", "-v"]
171
198
 
172
199
  full_cmd = ["adb"] + (["-s", self.options.serial] if self.options.serial else []) + ["shell"] + shell_command
kea2/keaUtils.py CHANGED
@@ -1,22 +1,23 @@
1
+ from collections import deque
1
2
  import json
2
3
  import os
3
4
  from pathlib import Path
4
5
  import traceback
5
6
  import time
6
- from typing import Callable, Any, Dict, List, Literal, NewType, TypedDict, Union
7
+ from typing import Callable, Any, Deque, Dict, List, Literal, NewType, TypedDict, Union
7
8
  from unittest import TextTestRunner, registerResult, TestSuite, TestCase, TextTestResult
8
9
  import random
9
10
  import warnings
10
11
  from dataclasses import dataclass, asdict
11
- import requests
12
- from .absDriver import AbstractDriver
12
+ from kea2.absDriver import AbstractDriver
13
13
  from functools import wraps
14
- from .bug_report_generator import BugReportGenerator
15
- from .resultSyncer import ResultSyncer
16
- from .logWatcher import LogWatcher
17
- from .utils import TimeStamp, getProjectRoot, getLogger
18
- from .u2Driver import StaticU2UiObject
19
- from .fastbotManager import FastbotManager
14
+ from kea2.bug_report_generator import BugReportGenerator
15
+ from kea2.resultSyncer import ResultSyncer
16
+ from kea2.logWatcher import LogWatcher
17
+ from kea2.utils import TimeStamp, getProjectRoot, getLogger
18
+ from kea2.u2Driver import StaticU2UiObject
19
+ from kea2.fastbotManager import FastbotManager
20
+ from kea2.adbUtils import ADBDevice
20
21
  import uiautomator2 as u2
21
22
  import types
22
23
 
@@ -30,14 +31,12 @@ logger = getLogger(__name__)
30
31
  # Class Typing
31
32
  PropName = NewType("PropName", str)
32
33
  PropertyStore = NewType("PropertyStore", Dict[PropName, TestCase])
33
- PropertyExecutionInfo = TypedDict(
34
- "PropertyExecutionInfo",
35
- {"propName": PropName, "state": Literal["start", "pass", "fail", "error"]}
36
- )
34
+
37
35
 
38
36
  STAMP = TimeStamp().getTimeStamp()
39
37
  LOGFILE: str
40
38
  RESFILE: str
39
+ PROP_EXEC_RESFILE: str
41
40
 
42
41
  def precondition(precond: Callable[[Any], bool]) -> Callable:
43
42
  """the decorator @precondition
@@ -133,6 +132,10 @@ class Options:
133
132
  device_output_root: str = "/sdcard"
134
133
  # the debug mode
135
134
  debug: bool = False
135
+ # Activity WhiteList File
136
+ act_whitelist_file: str = None
137
+ # Activity BlackList File
138
+ act_blacklist_file: str = None
136
139
 
137
140
  def __setattr__(self, name, value):
138
141
  if value is None:
@@ -142,6 +145,7 @@ class Options:
142
145
  def __post_init__(self):
143
146
  import logging
144
147
  logging.basicConfig(level=logging.DEBUG if self.debug else logging.INFO)
148
+
145
149
  if self.Driver:
146
150
  target_device = dict()
147
151
  if self.serial:
@@ -149,7 +153,9 @@ class Options:
149
153
  if self.transport_id:
150
154
  target_device["transport_id"] = self.transport_id
151
155
  self.Driver.setDevice(target_device)
152
- global LOGFILE, RESFILE, STAMP
156
+ ADBDevice.setDevice(self.serial, self.transport_id)
157
+
158
+ global LOGFILE, RESFILE, PROP_EXEC_RESFILE, STAMP
153
159
  if self.log_stamp:
154
160
  illegal_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\n', '\r', '\t', '\0']
155
161
  for char in illegal_chars:
@@ -158,9 +164,11 @@ class Options:
158
164
  f"char: `{char}` is illegal in --log-stamp. current stamp: {self.log_stamp}"
159
165
  )
160
166
  STAMP = self.log_stamp
167
+
161
168
  self.output_dir = Path(self.output_dir).absolute() / f"res_{STAMP}"
162
169
  LOGFILE = f"fastbot_{STAMP}.log"
163
170
  RESFILE = f"result_{STAMP}.json"
171
+ PROP_EXEC_RESFILE = f"property_exec_info_{STAMP}.json"
164
172
 
165
173
  self.profile_period = int(self.profile_period)
166
174
  if self.profile_period < 1:
@@ -170,12 +178,11 @@ class Options:
170
178
  if self.throttle < 0:
171
179
  raise ValueError("--throttle should be greater than or equal to 0")
172
180
 
173
- _check_package_installation(self.serial, self.packageNames)
181
+ _check_package_installation(self.packageNames)
174
182
 
175
183
 
176
- def _check_package_installation(serial, packageNames):
177
- from .adbUtils import get_packages
178
- installed_packages = get_packages(device=serial)
184
+ def _check_package_installation(packageNames):
185
+ installed_packages = set(ADBDevice().list_packages())
179
186
 
180
187
  for package in packageNames:
181
188
  if package not in installed_packages:
@@ -190,6 +197,16 @@ class PropStatistic:
190
197
  fail: int = 0
191
198
  error: int = 0
192
199
 
200
+
201
+ PropertyExecutionInfoStore = NewType("PropertyExecutionInfoStore", Deque["PropertyExecutionInfo"])
202
+ @dataclass
203
+ class PropertyExecutionInfo:
204
+ startStepsCount: int
205
+ propName: PropName
206
+ state: Literal["start", "pass", "fail", "error"]
207
+ tb: str
208
+
209
+
193
210
  class PBTTestResult(dict):
194
211
  def __getitem__(self, key) -> PropStatistic:
195
212
  return super().__getitem__(key)
@@ -202,13 +219,12 @@ def getFullPropName(testCase: TestCase):
202
219
  testCase._testMethodName
203
220
  ])
204
221
 
222
+
205
223
  class JsonResult(TextTestResult):
206
- res: PBTTestResult
207
224
 
208
- lastExecutedInfo: PropertyExecutionInfo = {
209
- "propName": "",
210
- "state": "",
211
- }
225
+ res: PBTTestResult
226
+ lastExecutedInfo: PropertyExecutionInfo
227
+ executionInfoStore: PropertyExecutionInfoStore = deque()
212
228
 
213
229
  @classmethod
214
230
  def setProperties(cls, allProperties: Dict):
@@ -216,19 +232,28 @@ class JsonResult(TextTestResult):
216
232
  for testCase in allProperties.values():
217
233
  cls.res[getFullPropName(testCase)] = PropStatistic()
218
234
 
219
- def flushResult(self, outfile):
235
+ def flushResult(self):
236
+ global RESFILE, PROP_EXEC_RESFILE
220
237
  json_res = dict()
221
238
  for propName, propStatitic in self.res.items():
222
239
  json_res[propName] = asdict(propStatitic)
223
- with open(outfile, "w", encoding="utf-8") as fp:
240
+ with open(RESFILE, "w", encoding="utf-8") as fp:
224
241
  json.dump(json_res, fp, indent=4)
225
242
 
226
- def addExcuted(self, test: TestCase):
243
+ while self.executionInfoStore:
244
+ execInfo = self.executionInfoStore.popleft()
245
+ with open(PROP_EXEC_RESFILE, "a", encoding="utf-8") as fp:
246
+ fp.write(f"{json.dumps(asdict(execInfo))}\n")
247
+
248
+ def addExcuted(self, test: TestCase, stepsCount: int):
227
249
  self.res[getFullPropName(test)].executed += 1
228
- self.lastExecutedInfo = {
229
- "propName": getFullPropName(test),
230
- "state": "start",
231
- }
250
+
251
+ self.lastExecutedInfo = PropertyExecutionInfo(
252
+ propName=getFullPropName(test),
253
+ state="start",
254
+ tb="",
255
+ startStepsCount=stepsCount
256
+ )
232
257
 
233
258
  def addPrecondSatisfied(self, test: TestCase):
234
259
  self.res[getFullPropName(test)].precond_satisfied += 1
@@ -236,16 +261,21 @@ class JsonResult(TextTestResult):
236
261
  def addFailure(self, test, err):
237
262
  super().addFailure(test, err)
238
263
  self.res[getFullPropName(test)].fail += 1
239
- self.lastExecutedInfo["state"] = "fail"
264
+ self.lastExecutedInfo.state = "fail"
265
+ self.lastExecutedInfo.tb = self._exc_info_to_string(err, test)
240
266
 
241
267
  def addError(self, test, err):
242
268
  super().addError(test, err)
243
269
  self.res[getFullPropName(test)].error += 1
244
- self.lastExecutedInfo["state"] = "error"
270
+ self.lastExecutedInfo.state = "error"
271
+ self.lastExecutedInfo.tb = self._exc_info_to_string(err, test)
245
272
 
246
273
  def updateExectedInfo(self):
247
- if self.lastExecutedInfo["state"] == "start":
248
- self.lastExecutedInfo["state"] = "pass"
274
+ if self.lastExecutedInfo.state == "start":
275
+ self.lastExecutedInfo.state = "pass"
276
+
277
+ self.executionInfoStore.append(self.lastExecutedInfo)
278
+
249
279
 
250
280
  def getExcuted(self, test: TestCase):
251
281
  return self.res[getFullPropName(test)].executed
@@ -270,11 +300,13 @@ class KeaTestRunner(TextTestRunner):
270
300
  def _setOuputDir(self):
271
301
  output_dir = Path(self.options.output_dir).absolute()
272
302
  output_dir.mkdir(parents=True, exist_ok=True)
273
- global LOGFILE, RESFILE
303
+ global LOGFILE, RESFILE, PROP_EXEC_RESFILE
274
304
  LOGFILE = output_dir / Path(LOGFILE)
275
305
  RESFILE = output_dir / Path(RESFILE)
306
+ PROP_EXEC_RESFILE = output_dir / Path(PROP_EXEC_RESFILE)
276
307
  logger.info(f"Log file: {LOGFILE}")
277
308
  logger.info(f"Result file: {RESFILE}")
309
+ logger.info(f"Property execution info file: {PROP_EXEC_RESFILE}")
278
310
 
279
311
  def run(self, test):
280
312
 
@@ -318,7 +350,7 @@ class KeaTestRunner(TextTestRunner):
318
350
 
319
351
  if self.options.agent == "u2":
320
352
  # initialize the result.json file
321
- result.flushResult(outfile=RESFILE)
353
+ result.flushResult()
322
354
  # setUp for the u2 driver
323
355
  self.scriptDriver = self.options.Driver.getScriptDriver()
324
356
  fb.check_alive()
@@ -342,7 +374,7 @@ class KeaTestRunner(TextTestRunner):
342
374
  try:
343
375
  xml_raw = fb.stepMonkey(self._monkeyStepInfo)
344
376
  propsSatisfiedPrecond = self.getValidProperties(xml_raw, result)
345
- except requests.ConnectionError:
377
+ except u2.HTTPError:
346
378
  logger.info("Connection refused by remote.")
347
379
  if fb.get_return_code() == 0:
348
380
  logger.info("Exploration times up (--running-minutes).")
@@ -377,7 +409,7 @@ class KeaTestRunner(TextTestRunner):
377
409
  setattr(test, self.options.driverName, self.scriptDriver)
378
410
  print("execute property %s." % execPropName, flush=True)
379
411
 
380
- result.addExcuted(test)
412
+ result.addExcuted(test, self.stepsCount)
381
413
  fb.logScript(result.lastExecutedInfo)
382
414
  try:
383
415
  test(result)
@@ -386,11 +418,11 @@ class KeaTestRunner(TextTestRunner):
386
418
 
387
419
  result.updateExectedInfo()
388
420
  fb.logScript(result.lastExecutedInfo)
389
- result.flushResult(outfile=RESFILE)
421
+ result.flushResult()
390
422
 
391
423
  if not end_by_remote:
392
424
  fb.stopMonkey()
393
- result.flushResult(outfile=RESFILE)
425
+ result.flushResult()
394
426
  resultSyncer.close()
395
427
 
396
428
  fb.join()
@@ -433,30 +465,6 @@ class KeaTestRunner(TextTestRunner):
433
465
  self.stream.flush()
434
466
  return result
435
467
 
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
468
  @property
461
469
  def _monkeyStepInfo(self):
462
470
  r = self._get_block_widgets()
kea2/kea_launcher.py CHANGED
@@ -122,6 +122,22 @@ def _set_runner_parser(subparsers: "argparse._SubParsersAction[argparse.Argument
122
122
  help="Take screenshots for every step.",
123
123
  )
124
124
 
125
+ parser.add_argument(
126
+ "--act-whitelist-file",
127
+ dest="act_whitelist_file",
128
+ required=False,
129
+ type=str,
130
+ help="Add Activity Whitelist File.",
131
+ )
132
+
133
+ parser.add_argument(
134
+ "--act-blacklist-file",
135
+ dest="act_blacklist_file",
136
+ required=False,
137
+ type=str,
138
+ help="Add Activity Blacklist File.",
139
+ )
140
+
125
141
  parser.add_argument(
126
142
  "extra",
127
143
  nargs=argparse.REMAINDER,
@@ -195,7 +211,9 @@ def run(args=None):
195
211
  log_stamp=args.log_stamp,
196
212
  profile_period=args.profile_period,
197
213
  take_screenshots=args.take_screenshots,
198
- device_output_root=args.device_output_root
214
+ device_output_root=args.device_output_root,
215
+ act_whitelist_file = args.act_whitelist_file,
216
+ act_blacklist_file=args.act_blacklist_file
199
217
  )
200
218
 
201
219
  KeaTestRunner.setOptions(options)
kea2/logWatcher.py CHANGED
@@ -24,7 +24,7 @@ class LogWatcher:
24
24
  def watcher(self, poll_interval=3):
25
25
  self.last_pos = 0
26
26
 
27
- with open(self.log_file, "r") as fp:
27
+ with open(self.log_file, "r", encoding="utf-8") as fp:
28
28
  while not self.end_flag:
29
29
  self.read_log(fp)
30
30
  time.sleep(poll_interval)
@@ -51,9 +51,10 @@ class LogWatcher:
51
51
  )
52
52
 
53
53
  statistic_match = PATTERN_STATISTIC.search(content)
54
- if statistic_match:
54
+ if statistic_match and not self.statistic_printed:
55
55
  statistic_body = statistic_match.group(1).strip()
56
56
  if statistic_body:
57
+ self.statistic_printed = True
57
58
  print(
58
59
  "[INFO] Fastbot exit:\n" +
59
60
  statistic_body
@@ -63,6 +64,7 @@ class LogWatcher:
63
64
  logger.info(f"Watching log: {log_file}")
64
65
  self.log_file = log_file
65
66
  self.end_flag = False
67
+ self.statistic_printed = False
66
68
 
67
69
  threading.excepthook = thread_excepthook
68
70
  self.t = threading.Thread(target=self.watcher, daemon=True)