Kea2-python 1.1.0b1__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.
Files changed (49) hide show
  1. kea2/__init__.py +8 -0
  2. kea2/absDriver.py +56 -0
  3. kea2/adbUtils.py +554 -0
  4. kea2/assets/config_version.json +16 -0
  5. kea2/assets/fastbot-thirdpart.jar +0 -0
  6. kea2/assets/fastbot_configs/abl.strings +2 -0
  7. kea2/assets/fastbot_configs/awl.strings +3 -0
  8. kea2/assets/fastbot_configs/max.config +7 -0
  9. kea2/assets/fastbot_configs/max.fuzzing.strings +699 -0
  10. kea2/assets/fastbot_configs/max.schema.strings +1 -0
  11. kea2/assets/fastbot_configs/max.strings +3 -0
  12. kea2/assets/fastbot_configs/max.tree.pruning +27 -0
  13. kea2/assets/fastbot_configs/teardown.py +18 -0
  14. kea2/assets/fastbot_configs/widget.block.py +38 -0
  15. kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  16. kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  17. kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  18. kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  19. kea2/assets/framework.jar +0 -0
  20. kea2/assets/kea2-thirdpart.jar +0 -0
  21. kea2/assets/monkeyq.jar +0 -0
  22. kea2/assets/quicktest.py +126 -0
  23. kea2/cli.py +216 -0
  24. kea2/fastbotManager.py +269 -0
  25. kea2/kea2_api.py +166 -0
  26. kea2/keaUtils.py +926 -0
  27. kea2/kea_launcher.py +299 -0
  28. kea2/logWatcher.py +92 -0
  29. kea2/mixin.py +0 -0
  30. kea2/report/__init__.py +0 -0
  31. kea2/report/bug_report_generator.py +879 -0
  32. kea2/report/mixin.py +496 -0
  33. kea2/report/report_merger.py +1066 -0
  34. kea2/report/templates/bug_report_template.html +4028 -0
  35. kea2/report/templates/merged_bug_report_template.html +3602 -0
  36. kea2/report/utils.py +10 -0
  37. kea2/result.py +257 -0
  38. kea2/resultSyncer.py +65 -0
  39. kea2/state.py +22 -0
  40. kea2/typedefs.py +32 -0
  41. kea2/u2Driver.py +612 -0
  42. kea2/utils.py +192 -0
  43. kea2/version_manager.py +102 -0
  44. kea2_python-1.1.0b1.dist-info/METADATA +447 -0
  45. kea2_python-1.1.0b1.dist-info/RECORD +49 -0
  46. kea2_python-1.1.0b1.dist-info/WHEEL +5 -0
  47. kea2_python-1.1.0b1.dist-info/entry_points.txt +2 -0
  48. kea2_python-1.1.0b1.dist-info/licenses/LICENSE +16 -0
  49. kea2_python-1.1.0b1.dist-info/top_level.txt +1 -0
kea2/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ from .keaUtils import KeaTestRunner, HybridTestRunner, keaTestLoader
2
+ from .keaUtils import Options
3
+ from .keaUtils import precondition, prob, max_tries, interruptable
4
+ from .keaUtils import kea2_breakpoint
5
+ from .state import state, invariant
6
+ from .kea2_api import Kea2Tester
7
+ from .u2Driver import U2Driver
8
+ from .state import state
kea2/absDriver.py ADDED
@@ -0,0 +1,56 @@
1
+ import abc
2
+
3
+
4
+ class AbstractScriptDriver(abc.ABC):
5
+ _instances = {}
6
+ def __new__(cls, *args, **kwargs):
7
+ if cls not in cls._instances:
8
+ cls._instances[cls] = super().__new__(cls)
9
+ return cls._instances[cls]
10
+
11
+ @abc.abstractmethod
12
+ def getInstance(self):
13
+ pass
14
+
15
+
16
+ class AbstractStaticChecker(abc.ABC):
17
+ _instances = {}
18
+ def __new__(cls, *args, **kwargs):
19
+ if cls not in cls._instances:
20
+ cls._instances[cls] = super().__new__(cls)
21
+ return cls._instances[cls]
22
+
23
+ @abc.abstractmethod
24
+ def getInstance(self):
25
+ pass
26
+
27
+ @abc.abstractmethod
28
+ def setHierarchy(hierarchy):
29
+ pass
30
+
31
+
32
+ class AbstractDriver(abc.ABC):
33
+ _instances = {}
34
+ def __new__(cls, *args, **kwargs):
35
+ if cls not in cls._instances:
36
+ cls._instances[cls] = super().__new__(cls)
37
+ return cls._instances[cls]
38
+
39
+ @classmethod
40
+ @abc.abstractmethod
41
+ def setDevice(self):
42
+ pass
43
+
44
+ @classmethod
45
+ @abc.abstractmethod
46
+ def getScriptDriver(self) -> AbstractScriptDriver:
47
+ pass
48
+
49
+ @classmethod
50
+ @abc.abstractmethod
51
+ def getStaticChecker(self, hierarchy) -> AbstractStaticChecker:
52
+ pass
53
+
54
+ @classmethod
55
+ @abc.abstractmethod
56
+ def tearDown(self): ...
kea2/adbUtils.py ADDED
@@ -0,0 +1,554 @@
1
+ import sys
2
+ import subprocess
3
+ import threading
4
+
5
+ from typing import IO, Generator, Optional, List, Union, List, Optional, Set, Tuple
6
+
7
+ from adbutils import AdbDevice, adb
8
+
9
+ from .utils import getLogger
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ class ADBDevice(AdbDevice):
15
+ _instance = None
16
+ serial: Optional[str] = None
17
+ transport_id: Optional[str] = None
18
+
19
+ def __new__(cls):
20
+ if cls._instance is None:
21
+ cls._instance = super().__new__(cls)
22
+ return cls._instance
23
+
24
+ @classmethod
25
+ def setDevice(cls, serial: Optional[str] = None, transport_id: Optional[str] = None):
26
+ ADBDevice.serial = serial or ADBDevice.serial
27
+ ADBDevice.transport_id = transport_id or ADBDevice.transport_id
28
+
29
+ def __init__(self) -> AdbDevice:
30
+ """
31
+ Initializes the ADBDevice instance.
32
+
33
+ Parameters:
34
+ device (str, optional): The device serial number. If None, it is resolved automatically when only one device is connected.
35
+ transport_id (str, optional): The transport ID for the device.
36
+ """
37
+ if not ADBDevice.serial and not ADBDevice.transport_id:
38
+ devices = [d.serial for d in adb.list() if d.state == "device"]
39
+ if len(devices) > 1:
40
+ raise RuntimeError("Multiple devices connected. Please specify a device")
41
+ if len(devices) == 0:
42
+ raise RuntimeError("No device connected.")
43
+ ADBDevice.serial = devices[0]
44
+ super().__init__(client=adb, serial=ADBDevice.serial, transport_id=ADBDevice.transport_id)
45
+
46
+ @property
47
+ def stream_shell(self) -> "StreamShell":
48
+ if "shell_v2" in self.get_features():
49
+ return ADBStreamShell_V2(session=self)
50
+ logger.warning("Using ADBStreamShell_V1. All output will be printed to stdout.")
51
+ return ADBStreamShell_V1(session=self)
52
+
53
+ def kill_proc(self, proc_name):
54
+ r = self.shell(f"ps -ef")
55
+ pids = [l for l in r.splitlines() if proc_name in l]
56
+ if pids:
57
+ logger.info(f"{proc_name} running, trying to kill it.")
58
+ pid = pids[0].split()[1]
59
+ self.shell(f"kill {pid}")
60
+
61
+
62
+ class StreamShell:
63
+ def __init__(self, session: "ADBDevice"):
64
+ self.dev: ADBDevice = session
65
+ self._thread: threading.Thread = None
66
+ self._exit_code = 255
67
+ self.stdout = sys.stdout
68
+ self.stderr = sys.stderr
69
+ self._finished = False
70
+
71
+ def __call__(self, cmdargs: Union[List[str], str], stdout: IO = None,
72
+ stderr: IO = None, timeout: Union[float, None] = None) -> "StreamShell":
73
+ pass
74
+
75
+ def _write_stdout(self, data: bytes, decode=True):
76
+ text = data.decode('utf-8', errors='ignore') if decode else data
77
+ self.stdout.write(text)
78
+ self.stdout.flush()
79
+
80
+ def _write_stderr(self, data: bytes, decode=True):
81
+ text = data.decode('utf-8', errors='ignore') if decode else data
82
+ self.stderr.write(text)
83
+ self.stderr.flush()
84
+
85
+ def wait(self):
86
+ """ Wait for the shell command to finish and return the exit code.
87
+ Returns:
88
+ int: The exit code of the shell command.
89
+ """
90
+ if self._thread:
91
+ self._thread.join()
92
+ return self._exit_code
93
+
94
+ def is_running(self) -> bool:
95
+ """ Check if the shell command is still running.
96
+ Returns:
97
+ bool: True if the command is still running, False otherwise.
98
+ """
99
+ return not self._finished and self._thread and self._thread.is_alive()
100
+
101
+ def poll(self):
102
+ """
103
+ Check if the shell command is still running.
104
+ Returns:
105
+ int: The exit code if the command has finished, None otherwise.
106
+ """
107
+ if self._thread and self._thread.is_alive():
108
+ return None
109
+ return self._exit_code
110
+
111
+ def join(self):
112
+ if self._thread and self._thread.is_alive():
113
+ self._thread.join()
114
+
115
+
116
+ class ADBStreamShell_V1(StreamShell):
117
+
118
+ def __call__(
119
+ self, cmdargs: Union[List[str], str], stdout: IO = None,
120
+ stderr: IO = None, timeout: Union[float, None] = None
121
+ ) -> "StreamShell":
122
+ return self.shell_v1(cmdargs, stdout, stderr, timeout)
123
+
124
+ def shell_v1(
125
+ self, cmdargs: Union[List[str], str],
126
+ stdout: IO = None, stderr: IO = None,
127
+ timeout: Union[float, None] = None
128
+ ):
129
+ self._finished = False
130
+ self.stdout: IO = stdout if stdout else sys.stdout
131
+ self.stderr: IO = stdout if stderr else sys.stdout
132
+
133
+ cmd = " ".join(cmdargs) if isinstance(cmdargs, list) else cmdargs
134
+ self._generator = self._shell_v1(cmd, timeout)
135
+ self._thread = threading.Thread(target=self._process_output, daemon=True)
136
+ self._thread.start()
137
+ return self
138
+
139
+
140
+ def _shell_v1(self, cmdargs: str, timeout: Optional[float] = None) -> Generator[Tuple[str, str], None, None]:
141
+ if not isinstance(cmdargs, str):
142
+ raise RuntimeError("_shell_v1 args must be str")
143
+ MAGIC = "X4EXIT:"
144
+ newcmd = cmdargs + f"; echo {MAGIC}$?"
145
+ with self.dev.open_transport(timeout=timeout) as c:
146
+ c.send_command(f"shell:{newcmd}")
147
+ c.check_okay()
148
+ with c.conn.makefile("r", encoding="utf-8") as f:
149
+ for line in f:
150
+ rindex = line.rfind(MAGIC)
151
+ if rindex == -1:
152
+ yield "output", line
153
+ continue
154
+
155
+ yield "exit", line[rindex + len(MAGIC):]
156
+ return
157
+
158
+ def _process_output(self):
159
+ try:
160
+ for msg_type, data in self._generator:
161
+
162
+ if msg_type == 'output':
163
+ self._write_stdout(data, decode=False)
164
+ elif msg_type == 'exit':
165
+ # TODO : handle exit code properly
166
+ # self._exit_code = int(data.strip())
167
+ self._exit_code = 0
168
+ break
169
+
170
+ except Exception as e:
171
+ print(f"ADBStreamShell execution error: {e}")
172
+ self._exit_code = -1
173
+
174
+
175
+ class ADBStreamShell_V2(StreamShell):
176
+ def __init__(self, session: "ADBDevice"):
177
+ self.dev: ADBDevice = session
178
+ self._thread = None
179
+ self._exit_code = 255
180
+
181
+ def __call__(
182
+ self, cmdargs: Union[List[str], str], stdout: IO = None,
183
+ stderr: IO = None, timeout: Union[float, None] = None
184
+ ) -> "StreamShell":
185
+ return self.shell_v2(cmdargs, stdout, stderr, timeout)
186
+
187
+ def shell_v2(
188
+ self, cmdargs: Union[List[str], str],
189
+ stdout: IO = None, stderr: IO = None,
190
+ timeout: Union[float, None] = None
191
+ ):
192
+ """ Start a shell command on the device and stream its output.
193
+ Args:
194
+ cmdargs (Union[List[str], str]): The command to execute, either as a list of arguments or a single string.
195
+ stdout (IO, optional): The output stream for standard output. Defaults to sys.stdout.
196
+ stderr (IO, optional): The output stream for standard error. Defaults to sys.stderr.
197
+ timeout (Union[float, None], optional): Timeout for the command execution. Defaults to None.
198
+ Returns:
199
+ ADBStreamShell: An instance of ADBStreamShell that can be used to interact with the shell command.
200
+ """
201
+ self._finished = False
202
+ self.stdout: IO = stdout if stdout else sys.stdout
203
+ self.stderr: IO = stderr if stderr else sys.stderr
204
+
205
+ cmd = " ".join(cmdargs) if isinstance(cmdargs, list) else cmdargs
206
+ self._generator = self._shell_v2(cmd, timeout)
207
+ self._thread = threading.Thread(target=self._process_output, daemon=True)
208
+ self._thread.start()
209
+ return self
210
+
211
+ def _process_output(self):
212
+ try:
213
+ for msg_type, data in self._generator:
214
+
215
+ if msg_type == 'stdout':
216
+ self._write_stdout(data)
217
+ elif msg_type == 'stderr':
218
+ self._write_stderr(data)
219
+ elif msg_type == 'exit':
220
+ self._exit_code = data
221
+ break
222
+
223
+ except Exception as e:
224
+ print(f"ADBStreamShell execution error: {e}")
225
+ self._exit_code = -1
226
+
227
+ def _shell_v2(self, cmd, timeout) -> Generator[Tuple[str, bytes], None, None]:
228
+ with self.dev.open_transport(timeout=timeout) as c:
229
+ c.send_command(f"shell,v2:{cmd}")
230
+ c.check_okay()
231
+
232
+ while True:
233
+ header = c.read_exact(5)
234
+ msg_id = header[0]
235
+ length = int.from_bytes(header[1:5], byteorder="little")
236
+
237
+ if length == 0:
238
+ continue
239
+
240
+ data = c.read_exact(length)
241
+
242
+ if msg_id == 1:
243
+ yield ('stdout', data)
244
+ elif msg_id == 2:
245
+ yield ('stderr', data)
246
+ elif msg_id == 3:
247
+ yield ('exit', data[0])
248
+ break
249
+
250
+
251
+ def run_adb_command(cmd: List[str], timeout=10):
252
+ """
253
+ Runs an adb command and returns its output.
254
+
255
+ Parameters:
256
+ cmd (list): List of adb command arguments, e.g., ["devices"].
257
+ timeout (int): Timeout in seconds.
258
+
259
+ Returns:
260
+ str: The standard output from the command. If an error occurs, returns None.
261
+ """
262
+ full_cmd = ["adb"] + cmd
263
+ logger.debug(f"{' '.join(full_cmd)}")
264
+ try:
265
+ result = subprocess.run(full_cmd, capture_output=True, text=True, timeout=timeout)
266
+ if result.returncode != 0:
267
+ print(f"Command failed: {' '.join(full_cmd)}\nError: {result.stderr}", flush=True)
268
+ return "\n".join([
269
+ result.stdout.strip(),
270
+ result.stderr.strip()
271
+ ])
272
+ except subprocess.TimeoutExpired:
273
+ print(f"Command timed out: {' '.join(full_cmd)}", flush=True)
274
+ return None
275
+
276
+ def get_devices():
277
+ """
278
+ Retrieves the list of connected Android devices.
279
+
280
+ Returns:
281
+ list: A list of device serial numbers.
282
+ """
283
+ output = run_adb_command(["devices", "-l"])
284
+ devices = []
285
+ if output:
286
+ lines = output.splitlines()
287
+ # The first line is usually "List of devices attached". The following lines list individual devices.
288
+ for line in lines[1:]:
289
+ if line.strip():
290
+ parts = line.split()
291
+ if len(parts) >= 2 and parts[1] == "device":
292
+ devices.append(parts[0])
293
+ return devices
294
+
295
+ def ensure_device(func):
296
+ """
297
+ A decorator that resolves the device parameter automatically if it's not provided.
298
+
299
+ If 'device' is None or not present in the keyword arguments and only one device is connected,
300
+ that device will be automatically used. If no devices are connected or multiple devices are
301
+ connected, it raises a RuntimeError.
302
+ """
303
+ def wrapper(*args, **kwargs):
304
+ devices = get_devices()
305
+ if kwargs.get("device") is None and kwargs.get("transport_id") is None:
306
+ if not devices:
307
+ raise RuntimeError("No connected devices.")
308
+ if len(devices) > 1:
309
+ raise RuntimeError("Multiple connected devices detected. Please specify a device.")
310
+ kwargs["device"] = devices[0]
311
+ if kwargs.get("device"):
312
+ output = run_adb_command(["-s", kwargs["device"], "get-state"])
313
+ elif kwargs.get("transport_id"):
314
+ output = run_adb_command(["-t", kwargs["transport_id"], "get-state"])
315
+ if output.strip() != "device":
316
+ raise RuntimeError(f"[ERROR] {kwargs['device']} not connected. Please check.\n{output}")
317
+ return func(*args, **kwargs)
318
+ return wrapper
319
+
320
+ @ensure_device
321
+ def adb_shell(cmd: List[str], device:Optional[str]=None, transport_id:Optional[str]=None):
322
+ """
323
+ run adb shell commands
324
+
325
+ Parameters:
326
+ cmd (List[str])
327
+ device (str, optional): The device serial number. If None, it's resolved automatically when only one device is connected.
328
+ """
329
+ if device:
330
+ return run_adb_command(["-s", device, "shell"] + cmd)
331
+ if transport_id:
332
+ return run_adb_command(["-t", transport_id, "shell"] + cmd)
333
+
334
+
335
+
336
+ @ensure_device
337
+ def install_app(apk_path: str, device: Optional[str]=None, transport_id:Optional[str]=None):
338
+ """
339
+ Installs an APK application on the specified device.
340
+
341
+ Parameters:
342
+ apk_path (str): The local path to the APK file.
343
+ device (str, optional): The device serial number. If None, it's resolved automatically when only one device is connected.
344
+
345
+ Returns:
346
+ str: The output from the install command.
347
+ """
348
+ if device:
349
+ return run_adb_command(["-s", device, "install", apk_path])
350
+ if transport_id:
351
+ return run_adb_command(["-t", transport_id, "install", apk_path])
352
+
353
+
354
+ @ensure_device
355
+ def uninstall_app(package_name: str, device: Optional[str] = None, transport_id:Optional[str]=None):
356
+ """
357
+ Uninstalls an app from the specified device.
358
+
359
+ Parameters:
360
+ package_name (str): The package name of the app.
361
+ device (str, optional): The device serial number. If None, it's resolved automatically when only one device is connected.
362
+
363
+ Returns:
364
+ str: The output from the uninstall command.
365
+ """
366
+ if device:
367
+ return run_adb_command(["-s", device, "uninstall", package_name])
368
+ if transport_id:
369
+ return run_adb_command(["-t", transport_id, "uninstall", package_name])
370
+
371
+ @ensure_device
372
+ def push_file(local_path: str, remote_path: str, device: Optional[str] = None, transport_id:Optional[str]=None):
373
+ """
374
+ Pushes a file to the specified device.
375
+
376
+ Parameters:
377
+ local_path (str): The local file path.
378
+ remote_path (str): The destination path on the device.
379
+ device (str, optional): The device serial number. If None, it's resolved automatically when only one device is connected.
380
+
381
+ Returns:
382
+ str: The output from the push command.
383
+ """
384
+ local_path = str(local_path)
385
+ remote_path = str(remote_path)
386
+ if device:
387
+ return run_adb_command(["-s", device, "push", local_path, remote_path])
388
+ if transport_id:
389
+ return run_adb_command(["-t", transport_id, "push", local_path, remote_path])
390
+
391
+
392
+ @ensure_device
393
+ def pull_file(remote_path: str, local_path: str, device: Optional[str] = None, transport_id:Optional[str]=None):
394
+ """
395
+ Pulls a file from the device to a local path.
396
+
397
+ Parameters:
398
+ remote_path (str): The file path on the device.
399
+ local_path (str): The local destination path.
400
+ device (str, optional): The device serial number. If None, it's resolved automatically when only one device is connected.
401
+
402
+ Returns:
403
+ str: The output from the pull command.
404
+ """
405
+ if device:
406
+ return run_adb_command(["-s", device, "pull", remote_path, local_path])
407
+ if transport_id:
408
+ return run_adb_command(["-t", transport_id, "pull", remote_path, local_path])
409
+
410
+ # Forward-related functions
411
+
412
+
413
+ @ensure_device
414
+ def list_forwards(device: Optional[str] = None):
415
+ """
416
+ Lists current port forwarding rules on the specified device.
417
+
418
+ Parameters:
419
+ device (str, optional): The device serial number. If None, it is resolved automatically.
420
+
421
+ Returns:
422
+ list: A list of forwarding rules. Each rule is a dictionary with keys: device, local, remote.
423
+ """
424
+ output = run_adb_command(["-s", device, "forward", "--list"])
425
+ forwards = []
426
+ if output:
427
+ lines = output.splitlines()
428
+ for line in lines:
429
+ parts = line.split()
430
+ if len(parts) == 3:
431
+ # Each line is expected to be: <device> <local> <remote>
432
+ rule = {"device": parts[0], "local": parts[1], "remote": parts[2]}
433
+ if rule["device"] == device:
434
+ forwards.append(rule)
435
+ else:
436
+ forwards.append(line)
437
+ return forwards
438
+
439
+
440
+ @ensure_device
441
+ def create_forward(local_spec: str, remote_spec: str, device: Optional[str] = None):
442
+ """
443
+ Creates a port forwarding rule on the specified device.
444
+
445
+ Parameters:
446
+ local_spec (str): The local forward specification (e.g., "tcp:8000").
447
+ remote_spec (str): The remote target specification (e.g., "tcp:9000").
448
+ device (str, optional): The device serial number. If None, it is resolved automatically.
449
+
450
+ Returns:
451
+ str: The output from the forward creation command.
452
+ """
453
+ return run_adb_command(["-s", device, "forward", local_spec, remote_spec])
454
+
455
+
456
+ @ensure_device
457
+ def remove_forward(local_spec, device: Optional[str] = None):
458
+ """
459
+ Removes a specific port forwarding rule on the specified device.
460
+
461
+ Parameters:
462
+ local_spec (str): The local forward specification to remove (e.g., "tcp:8000").
463
+ device (str, optional): The device serial number. If None, it is resolved automatically.
464
+
465
+ Returns:
466
+ str: The output from the forward removal command.
467
+ """
468
+ return run_adb_command(["-s", device, "forward", "--remove", local_spec])
469
+
470
+
471
+ @ensure_device
472
+ def remove_all_forwards(device: Optional[str] = None):
473
+ """
474
+ Removes all port forwarding rules on the specified device.
475
+
476
+ Parameters:
477
+ device (str, optional): The device serial number. If None, it is resolved automatically.
478
+
479
+ Returns:
480
+ str: The output from the command to remove all forwards.
481
+ """
482
+ return run_adb_command(["-s", device, "forward", "--remove-all"])
483
+
484
+
485
+ @ensure_device
486
+ def get_packages(device: Optional[str]=None, transport_id: Optional[str]=None) -> Set[str]:
487
+ """
488
+ Retrieves packages that match the specified regular expression pattern.
489
+
490
+ Parameters:
491
+ pattern (str): Regular expression pattern to match package names.
492
+ device (str, optional): The device serial number. If None, it is resolved automatically.
493
+
494
+ Returns:
495
+ set: A set of package names that match the pattern.
496
+ """
497
+ import re
498
+
499
+ if device:
500
+ cmd = ["-s", device, "shell", "pm", "list", "packages"]
501
+ if transport_id:
502
+ cmd = ["-t", transport_id, "shell", "pm", "list", "packages"]
503
+ output = run_adb_command(cmd)
504
+
505
+ packages = set()
506
+ if output:
507
+ compiled_pattern = re.compile(r"package:(.+)\n")
508
+ matches = compiled_pattern.findall(output)
509
+ for match in matches:
510
+ if match:
511
+ packages.add(match)
512
+
513
+ return packages
514
+
515
+
516
+ if __name__ == '__main__':
517
+ # For testing: print the list of currently connected devices.
518
+ adb_shell(["ls", "vendor"], transport_id="2")
519
+ devices = get_devices()
520
+ if devices:
521
+ print("Connected devices:", flush=True)
522
+ for dev in devices:
523
+ print(f" - {dev}", flush=True)
524
+ else:
525
+ print("No devices connected.", flush=True)
526
+
527
+ # Example usage of forward-related functionalities:
528
+ try:
529
+ # List current forwards
530
+ forwards = list_forwards()
531
+ print("Current forward rules:", flush=True)
532
+ for rule in forwards:
533
+ print(rule, flush=True)
534
+
535
+ # Create a forward rule (example: forward local tcp 8000 to remote tcp 9000)
536
+ output = create_forward("tcp:8000", "tcp:9000")
537
+ print("Create forward output:", output, flush=True)
538
+
539
+ # List forwards again
540
+ forwards = list_forwards()
541
+ print("Forward rules after creation:", flush=True)
542
+ for rule in forwards:
543
+ print(rule, flush=True)
544
+
545
+ # Remove the forward rule
546
+ output = remove_forward("tcp:8000")
547
+ print("Remove forward output:", output, flush=True)
548
+
549
+ # Remove all forwards (if needed)
550
+ # output = remove_all_forwards()
551
+ # print("Remove all forwards output:", output)
552
+
553
+ except RuntimeError as e:
554
+ print("Error:", e, flush=True)
@@ -0,0 +1,16 @@
1
+ {
2
+ "compatibility infos": [
3
+ {
4
+ "name": "previous version",
5
+ "description": "The default initial version, <=0.3.6",
6
+ "from": "0.0.0",
7
+ "to": "0.3.6"
8
+ },
9
+ {
10
+ "name": "Hybrid test version 1.0.0",
11
+ "description": "Hybrid test was added in version 1.0.0. hybrid_test_config.py is required.",
12
+ "from": "1.0.0",
13
+ "to": ""
14
+ }
15
+ ]
16
+ }
Binary file
@@ -0,0 +1,2 @@
1
+ com.ss.android.xxx.DManageActivity
2
+ com.bytedance.xxxx.DebuggerPanelActivity
@@ -0,0 +1,3 @@
1
+ com.ss.android.xxx.NewActivity
2
+ com.ss.android.xxx.MusicActivity
3
+ com.ss.android.xxx.DetailListActivity
@@ -0,0 +1,7 @@
1
+ max.randomPickFromStringList = false
2
+ max.takeScreenshot = false
3
+ max.takeScreenshotForEveryStep = false
4
+ max.saveGUITreeToXmlEveryStep =false
5
+ max.execSchema = true
6
+ max.execSchemaEveryStartup = true
7
+ max.grantAllPermission = true