Kea2-python 1.0.6b0__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.

Files changed (52) hide show
  1. kea2/__init__.py +3 -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 +320 -0
  24. kea2/fastbotManager.py +267 -0
  25. kea2/fastbotx/ActivityTimes.py +52 -0
  26. kea2/fastbotx/ReuseEntry.py +74 -0
  27. kea2/fastbotx/ReuseModel.py +63 -0
  28. kea2/fastbotx/__init__.py +7 -0
  29. kea2/fbm_parser.py +871 -0
  30. kea2/fs_lock.py +131 -0
  31. kea2/kea2_api.py +166 -0
  32. kea2/keaUtils.py +1112 -0
  33. kea2/kea_launcher.py +319 -0
  34. kea2/logWatcher.py +92 -0
  35. kea2/mixin.py +22 -0
  36. kea2/report/__init__.py +0 -0
  37. kea2/report/bug_report_generator.py +793 -0
  38. kea2/report/mixin.py +482 -0
  39. kea2/report/report_merger.py +797 -0
  40. kea2/report/templates/bug_report_template.html +3876 -0
  41. kea2/report/templates/merged_bug_report_template.html +3333 -0
  42. kea2/report/utils.py +10 -0
  43. kea2/resultSyncer.py +65 -0
  44. kea2/u2Driver.py +610 -0
  45. kea2/utils.py +184 -0
  46. kea2/version_manager.py +102 -0
  47. kea2_python-1.0.6b0.dist-info/METADATA +447 -0
  48. kea2_python-1.0.6b0.dist-info/RECORD +52 -0
  49. kea2_python-1.0.6b0.dist-info/WHEEL +5 -0
  50. kea2_python-1.0.6b0.dist-info/entry_points.txt +2 -0
  51. kea2_python-1.0.6b0.dist-info/licenses/LICENSE +16 -0
  52. kea2_python-1.0.6b0.dist-info/top_level.txt +1 -0
kea2/fs_lock.py ADDED
@@ -0,0 +1,131 @@
1
+ import os
2
+ import time
3
+ import errno
4
+ import random
5
+ import threading
6
+
7
+ class LockTimeoutError(Exception):
8
+ pass
9
+
10
+ class FileLock:
11
+ """Simple cross-platform exclusive lock with per-thread reentrancy.
12
+
13
+ Usage:
14
+ with FileLock(path, timeout=10.0, poll_interval=0.1):
15
+ # exclusive section
16
+
17
+ Implementation:
18
+ - If `portalocker` is available it will use that for advisory file locking.
19
+ - Otherwise falls back to an atomic mkdir-based lock (create a directory '<path>.lockdir').
20
+ - Reentrancy: a thread that already holds the lock can re-enter without blocking.
21
+ """
22
+ # thread-local storage to track held locks: mapping lock_id -> {'count': int, 'lockfile': file obj or None}
23
+ _local = threading.local()
24
+
25
+ def __init__(self, path, timeout=10.0, poll_interval=0.1):
26
+ self.target = os.fspath(path)
27
+ self.timeout = float(timeout)
28
+ self.poll_interval = float(poll_interval)
29
+ self._use_portalocker = False
30
+ self._portalocker = None
31
+ self._lockfile = None
32
+ self._lockdir = None
33
+ try:
34
+ import portalocker # type: ignore
35
+ self._portalocker = portalocker
36
+ self._use_portalocker = True
37
+ except Exception:
38
+ self._portalocker = None
39
+ self._use_portalocker = False
40
+ # Normalize lock id
41
+ self._lock_id = os.path.abspath(self.target)
42
+
43
+ def __enter__(self):
44
+ if not hasattr(FileLock._local, 'held'):
45
+ FileLock._local.held = {}
46
+ held = FileLock._local.held
47
+ # Reentrant: if we already hold this lock in this thread, increment counter and return
48
+ if self._lock_id in held:
49
+ held[self._lock_id]['count'] += 1
50
+ return self
51
+
52
+ deadline = time.time() + self.timeout
53
+ if self._use_portalocker:
54
+ lock_path = self.target if os.path.isdir(self.target) else (self.target + '.lockfile')
55
+ parent = os.path.dirname(lock_path) or '.'
56
+ try:
57
+ os.makedirs(parent, exist_ok=True)
58
+ except Exception:
59
+ pass
60
+ # open lock file
61
+ lf = open(lock_path, 'a+b')
62
+ while True:
63
+ try:
64
+ self._portalocker.lock(lf, self._portalocker.LockFlags.EXCLUSIVE | self._portalocker.LockFlags.NON_BLOCKING)
65
+ # record in thread-local
66
+ held[self._lock_id] = {'count': 1, 'lockfile': lf, 'lockdir': None}
67
+ return self
68
+ except Exception:
69
+ if time.time() > deadline:
70
+ try:
71
+ lf.close()
72
+ except Exception:
73
+ pass
74
+ raise LockTimeoutError(f"Timeout acquiring portalocker on {lock_path}")
75
+ time.sleep(self.poll_interval + random.random() * 0.01)
76
+ else:
77
+ lockdir = (self.target + '.lockdir') if not str(self.target).endswith('.lockdir') else self.target
78
+ while True:
79
+ try:
80
+ os.mkdir(lockdir)
81
+ try:
82
+ with open(os.path.join(lockdir, 'owner'), 'w') as f:
83
+ f.write(f"{os.getpid()}\n{time.time()}\n")
84
+ except Exception:
85
+ pass
86
+ held[self._lock_id] = {'count': 1, 'lockfile': None, 'lockdir': lockdir}
87
+ return self
88
+ except OSError as e:
89
+ if e.errno != errno.EEXIST:
90
+ raise
91
+ if time.time() > deadline:
92
+ raise LockTimeoutError(f"Timeout acquiring mkdir lock on {lockdir}")
93
+ time.sleep(self.poll_interval + random.random() * 0.01)
94
+
95
+ def __exit__(self, exc_type, exc, tb):
96
+ held = getattr(FileLock._local, 'held', None)
97
+ if not held or self._lock_id not in held:
98
+ # nothing to release
99
+ return
100
+ entry = held[self._lock_id]
101
+ entry['count'] -= 1
102
+ if entry['count'] > 0:
103
+ return
104
+ # count reached zero: release underlying OS lock
105
+ if entry.get('lockfile'):
106
+ try:
107
+ self._portalocker.unlock(entry['lockfile'])
108
+ except Exception:
109
+ pass
110
+ try:
111
+ entry['lockfile'].close()
112
+ except Exception:
113
+ pass
114
+ else:
115
+ lockdir = entry.get('lockdir')
116
+ if lockdir:
117
+ try:
118
+ owner = os.path.join(lockdir, 'owner')
119
+ if os.path.exists(owner):
120
+ try:
121
+ os.remove(owner)
122
+ except Exception:
123
+ pass
124
+ os.rmdir(lockdir)
125
+ except Exception:
126
+ # best-effort cleanup only
127
+ pass
128
+ try:
129
+ del held[self._lock_id]
130
+ except Exception:
131
+ pass
kea2/kea2_api.py ADDED
@@ -0,0 +1,166 @@
1
+ from typing import Optional, List, Callable, Any, Union, Dict
2
+ from pathlib import Path
3
+ import unittest
4
+ import os
5
+ import inspect
6
+ from .keaUtils import KeaTestRunner, Options
7
+ from .u2Driver import U2Driver
8
+ from .utils import getLogger, TimeStamp, setCustomProjectRoot
9
+ from .adbUtils import ADBDevice
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ class Kea2Tester:
15
+ """
16
+ Kea2 property tester
17
+
18
+ This class allows users to directly launch Kea2 property tests in existing test scripts.
19
+
20
+ Environment Variables:
21
+ KEA2_HYBRID_MODE: Controls whether to enable Kea2 testing
22
+ - "kea2": Enable Kea2 testing, trigger a breakpoint after testing is completed
23
+ - Other values or not set: Skip Kea2 testing, continue executing the original script
24
+
25
+ """
26
+
27
+ def __init__(self):
28
+ self.options: Optional[Options] = None
29
+ self.properties: List[unittest.TestCase] = []
30
+ self._caller_info: Optional[Dict[str, str]] = None
31
+
32
+ def run_kea2_testing(self, option: Options, configs_path: Optional[Union[str, Path]] = None) -> Dict[str, Any]:
33
+ """
34
+ Launch kea2 property test
35
+
36
+ Args:
37
+ option: Kea2 and Fastbot configuration options
38
+ configs_path: Your configs directory (absolute or relative path)
39
+
40
+ Returns:
41
+ dict: Test result dictionary containing the following keys:
42
+ - executed (bool): Whether Kea2 testing was executed
43
+ - skipped (bool): Whether Kea2 testing was skipped (KEA2_HYBRID_MODE != kea2)
44
+ - caller_info (Dict|None): Caller information (file, class, method name)
45
+ - output_dir (Path|None): Test output directory
46
+ - bug_report (Path|None): Bug report HTML file path
47
+ - result_json (Path|None): Test result JSON file path
48
+ - log_file (Path|None): Fastbot log file path
49
+
50
+ """
51
+
52
+ self._caller_info = self._get_caller_info()
53
+
54
+ logger.info("Starting Kea2 property testing...")
55
+ logger.info(f"Kea2 test launch location:")
56
+ if self._caller_info:
57
+ logger.info(f" File: {self._caller_info['file']}")
58
+ logger.info(f" Class: {self._caller_info['class']}")
59
+ logger.info(f" Method: {self._caller_info['method']}")
60
+
61
+ self.options = option
62
+ if self.options is None:
63
+ raise ValueError("Please set up the option config first.")
64
+
65
+ from kea2.utils import getProjectRoot
66
+ previous_root = getProjectRoot()
67
+ if configs_path is not None:
68
+ configs_dir = Path(configs_path).expanduser()
69
+ if not configs_dir.exists() or not configs_dir.is_dir():
70
+ raise FileNotFoundError(f"Configs directory not found in the specified path: {configs_dir}")
71
+ else:
72
+ setCustomProjectRoot(configs_path)
73
+
74
+ KeaTestRunner.setOptions(self.options)
75
+ argv = ["python3 -m unittest"] + self.options.propertytest_args
76
+
77
+ logger.info("Starting Kea2 property test...")
78
+ runner = KeaTestRunner()
79
+ unittest.main(module=None, argv=argv, testRunner=runner, exit=False)
80
+ logger.info("Kea2 property test completed.")
81
+
82
+ if configs_path is not None:
83
+ setCustomProjectRoot(previous_root)
84
+
85
+ result = self._build_test_result()
86
+
87
+ return result
88
+
89
+ def _build_test_result(self) -> Dict[str, Any]:
90
+ """
91
+ build test result dict
92
+
93
+ Returns:
94
+ dict: Dictionary containing output directory and paths to various report files
95
+ """
96
+ if self.options is None:
97
+ return {
98
+ 'executed': False,
99
+ 'skipped': False,
100
+ 'caller_info': self._caller_info,
101
+ 'output_dir': None,
102
+ 'bug_report': None,
103
+ 'result_json': None,
104
+ 'log_file': None
105
+ }
106
+
107
+ output_dir = self.options.output_dir
108
+
109
+ from .keaUtils import STAMP, LOGFILE, RESFILE
110
+
111
+ bug_report_path = output_dir / "bug_report.html"
112
+ result_json_path = output_dir / RESFILE.name if hasattr(RESFILE, 'name') else output_dir / f"result_{STAMP}.json"
113
+ log_file_path = output_dir / LOGFILE.name if hasattr(LOGFILE, 'name') else output_dir / f"fastbot_{STAMP}.log"
114
+
115
+ return {
116
+ 'executed': True,
117
+ 'skipped': False,
118
+ 'caller_info': self._caller_info,
119
+ 'output_dir': output_dir,
120
+ 'bug_report': bug_report_path if bug_report_path.exists() else None,
121
+ 'result_json': result_json_path if result_json_path.exists() else None,
122
+ 'log_file': log_file_path if log_file_path.exists() else None
123
+ }
124
+
125
+ def _get_caller_info(self) -> Dict[str, str]:
126
+ """
127
+ Get caller information (file, class, method name)
128
+
129
+ Returns:
130
+ dict: Dictionary containing file, class, method
131
+ """
132
+ try:
133
+ frame = inspect.currentframe()
134
+ caller_frame = frame.f_back.f_back
135
+
136
+ while caller_frame:
137
+ frame_info = inspect.getframeinfo(caller_frame)
138
+ if 'kea2_api.py' not in frame_info.filename:
139
+ # find caller
140
+ file_path = frame_info.filename
141
+ method_name = frame_info.function
142
+
143
+ # get class name
144
+ class_name = None
145
+ if 'self' in caller_frame.f_locals:
146
+ class_name = caller_frame.f_locals['self'].__class__.__name__
147
+
148
+ return {
149
+ 'file': file_path,
150
+ 'class': class_name or 'N/A',
151
+ 'method': method_name
152
+ }
153
+ caller_frame = caller_frame.f_back
154
+
155
+ return {
156
+ 'file': 'Unknown',
157
+ 'class': 'N/A',
158
+ 'method': 'Unknown'
159
+ }
160
+ except Exception as e:
161
+ logger.warning(f"Failed to get caller info: {e}")
162
+ return {
163
+ 'file': 'Unknown',
164
+ 'class': 'N/A',
165
+ 'method': 'Unknown'
166
+ }