Kea2-python 0.2.4__tar.gz → 0.3.0__tar.gz
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_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/PKG-INFO +10 -3
- {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/SOURCES.txt +2 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/PKG-INFO +10 -3
- {kea2_python-0.2.4 → kea2_python-0.3.0}/README.md +9 -2
- kea2_python-0.3.0/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/bug_report_generator.py +267 -7
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/cli.py +71 -2
- kea2_python-0.3.0/kea2/report_merger.py +651 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/templates/bug_report_template.html +1583 -68
- kea2_python-0.3.0/kea2/templates/merged_bug_report_template.html +2547 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/pyproject.toml +1 -1
- kea2_python-0.2.4/kea2/assets/monkeyq.jar +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/dependency_links.txt +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/entry_points.txt +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/requires.txt +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/top_level.txt +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/LICENSE +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/__init__.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/absDriver.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/adbUtils.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot-thirdpart.jar +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/abl.strings +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/awl.strings +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.config +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.strings +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/widget.block.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/framework.jar +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/kea2-thirdpart.jar +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/quicktest.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/fastbotManager.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/keaUtils.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/kea_launcher.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/logWatcher.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/resultSyncer.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/u2Driver.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/utils.py +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/setup.cfg +0 -0
- {kea2_python-0.2.4 → kea2_python-0.3.0}/tests/test_u2Selector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A python library for supporting and customizing automated UI testing for mobile apps
|
|
5
5
|
Author-email: Xixian Liang <xixian@stu.ecnu.edu.cn>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -22,6 +22,8 @@ Dynamic: license-file
|
|
|
22
22
|
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
+
The group has reached its capacity. Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat group.
|
|
26
|
+
|
|
25
27
|
### Github repo link
|
|
26
28
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
27
29
|
|
|
@@ -58,7 +60,11 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
58
60
|
| **Finding crashes in deep states** | | :+1: | :+1: |
|
|
59
61
|
| **Finding non-crashing functional (logic) bugs** | | | :+1: |
|
|
60
62
|
|
|
63
|
+
## Kea2's Known Users
|
|
64
|
+
|
|
65
|
+
[OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app
|
|
61
66
|
|
|
67
|
+
We are glad to hear if you are using Kea2 for your app.
|
|
62
68
|
|
|
63
69
|
## Design & Roadmap
|
|
64
70
|
Kea2 currently works with:
|
|
@@ -224,6 +230,7 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
224
230
|
- How to run Kea2 and Kea2's command line options
|
|
225
231
|
- How to find and understand Kea2's testing results
|
|
226
232
|
- How to [whitelist or blacklist](docs/blacklisting.md) specific activities, UI widgets and UI regions during fuzzing
|
|
233
|
+
- [Common Q&A for PBT and Kea2](https://sy8pzmhmun.feishu.cn/wiki/SLGwwqgzIiEuC3kwmV8cSZY0nTg?from=from_copylink)
|
|
227
234
|
|
|
228
235
|
## Open-source projects used by Kea2
|
|
229
236
|
|
|
@@ -246,8 +253,8 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
246
253
|
Kea2 has been actively developed and maintained by the people in [ecnusse](https://github.com/ecnusse):
|
|
247
254
|
|
|
248
255
|
- [Xixian Liang](https://xixianliang.github.io/resume/) ([@XixianLiang][])
|
|
249
|
-
- Bo Ma ([@majuzi123][])
|
|
250
|
-
-
|
|
256
|
+
- [Bo Ma](https://github.com/majuzi123) ([@majuzi123][])
|
|
257
|
+
- [Cheng Peng](https://github.com/Drifterpc) ([@Drifterpc][])
|
|
251
258
|
- [Ting Su](https://tingsu.github.io/) ([@tingsu][])
|
|
252
259
|
|
|
253
260
|
[@XixianLiang]: https://github.com/XixianLiang
|
|
@@ -16,6 +16,7 @@ kea2/fastbotManager.py
|
|
|
16
16
|
kea2/keaUtils.py
|
|
17
17
|
kea2/kea_launcher.py
|
|
18
18
|
kea2/logWatcher.py
|
|
19
|
+
kea2/report_merger.py
|
|
19
20
|
kea2/resultSyncer.py
|
|
20
21
|
kea2/u2Driver.py
|
|
21
22
|
kea2/utils.py
|
|
@@ -37,4 +38,5 @@ kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so
|
|
|
37
38
|
kea2/assets/fastbot_libs/x86/libfastbot_native.so
|
|
38
39
|
kea2/assets/fastbot_libs/x86_64/libfastbot_native.so
|
|
39
40
|
kea2/templates/bug_report_template.html
|
|
41
|
+
kea2/templates/merged_bug_report_template.html
|
|
40
42
|
tests/test_u2Selector.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Kea2-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A python library for supporting and customizing automated UI testing for mobile apps
|
|
5
5
|
Author-email: Xixian Liang <xixian@stu.ecnu.edu.cn>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -22,6 +22,8 @@ Dynamic: license-file
|
|
|
22
22
|
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
+
The group has reached its capacity. Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat group.
|
|
26
|
+
|
|
25
27
|
### Github repo link
|
|
26
28
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
27
29
|
|
|
@@ -58,7 +60,11 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
58
60
|
| **Finding crashes in deep states** | | :+1: | :+1: |
|
|
59
61
|
| **Finding non-crashing functional (logic) bugs** | | | :+1: |
|
|
60
62
|
|
|
63
|
+
## Kea2's Known Users
|
|
64
|
+
|
|
65
|
+
[OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app
|
|
61
66
|
|
|
67
|
+
We are glad to hear if you are using Kea2 for your app.
|
|
62
68
|
|
|
63
69
|
## Design & Roadmap
|
|
64
70
|
Kea2 currently works with:
|
|
@@ -224,6 +230,7 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
224
230
|
- How to run Kea2 and Kea2's command line options
|
|
225
231
|
- How to find and understand Kea2's testing results
|
|
226
232
|
- How to [whitelist or blacklist](docs/blacklisting.md) specific activities, UI widgets and UI regions during fuzzing
|
|
233
|
+
- [Common Q&A for PBT and Kea2](https://sy8pzmhmun.feishu.cn/wiki/SLGwwqgzIiEuC3kwmV8cSZY0nTg?from=from_copylink)
|
|
227
234
|
|
|
228
235
|
## Open-source projects used by Kea2
|
|
229
236
|
|
|
@@ -246,8 +253,8 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
246
253
|
Kea2 has been actively developed and maintained by the people in [ecnusse](https://github.com/ecnusse):
|
|
247
254
|
|
|
248
255
|
- [Xixian Liang](https://xixianliang.github.io/resume/) ([@XixianLiang][])
|
|
249
|
-
- Bo Ma ([@majuzi123][])
|
|
250
|
-
-
|
|
256
|
+
- [Bo Ma](https://github.com/majuzi123) ([@majuzi123][])
|
|
257
|
+
- [Cheng Peng](https://github.com/Drifterpc) ([@Drifterpc][])
|
|
251
258
|
- [Ting Su](https://tingsu.github.io/) ([@tingsu][])
|
|
252
259
|
|
|
253
260
|
[@XixianLiang]: https://github.com/XixianLiang
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<img src="https://github.com/user-attachments/assets/84e47b87-2dd2-4d7e-91d1-e8c1d1db0cf4" style="border-radius: 14px; width: 20%; height: 20%;"/>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
+
The group has reached its capacity. Please contact Xixian Liang at [xixian@stu.ecnu.edu.cn](xixian@stu.ecnu.edu.cn) with your Wechat ID / QR code to be invited to the WeChat group.
|
|
12
|
+
|
|
11
13
|
### Github repo link
|
|
12
14
|
[https://github.com/ecnusse/Kea2](https://github.com/ecnusse/Kea2)
|
|
13
15
|
|
|
@@ -44,7 +46,11 @@ Kea2 currently targets [Android](https://en.wikipedia.org/wiki/Android_(operatin
|
|
|
44
46
|
| **Finding crashes in deep states** | | :+1: | :+1: |
|
|
45
47
|
| **Finding non-crashing functional (logic) bugs** | | | :+1: |
|
|
46
48
|
|
|
49
|
+
## Kea2's Known Users
|
|
50
|
+
|
|
51
|
+
[OPay Business](https://play.google.com/store/apps/details?id=team.opay.pay.merchant.service) --- a financial & payment app
|
|
47
52
|
|
|
53
|
+
We are glad to hear if you are using Kea2 for your app.
|
|
48
54
|
|
|
49
55
|
## Design & Roadmap
|
|
50
56
|
Kea2 currently works with:
|
|
@@ -210,6 +216,7 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
210
216
|
- How to run Kea2 and Kea2's command line options
|
|
211
217
|
- How to find and understand Kea2's testing results
|
|
212
218
|
- How to [whitelist or blacklist](docs/blacklisting.md) specific activities, UI widgets and UI regions during fuzzing
|
|
219
|
+
- [Common Q&A for PBT and Kea2](https://sy8pzmhmun.feishu.cn/wiki/SLGwwqgzIiEuC3kwmV8cSZY0nTg?from=from_copylink)
|
|
213
220
|
|
|
214
221
|
## Open-source projects used by Kea2
|
|
215
222
|
|
|
@@ -232,8 +239,8 @@ You can find the [user manual](docs/manual_en.md), which includes:
|
|
|
232
239
|
Kea2 has been actively developed and maintained by the people in [ecnusse](https://github.com/ecnusse):
|
|
233
240
|
|
|
234
241
|
- [Xixian Liang](https://xixianliang.github.io/resume/) ([@XixianLiang][])
|
|
235
|
-
- Bo Ma ([@majuzi123][])
|
|
236
|
-
-
|
|
242
|
+
- [Bo Ma](https://github.com/majuzi123) ([@majuzi123][])
|
|
243
|
+
- [Cheng Peng](https://github.com/Drifterpc) ([@Drifterpc][])
|
|
237
244
|
- [Ting Su](https://tingsu.github.io/) ([@tingsu][])
|
|
238
245
|
|
|
239
246
|
[@XixianLiang]: https://github.com/XixianLiang
|
|
Binary file
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, TypedDict, List, Deque, NewType, Union, Optional
|
|
6
|
+
from typing import Dict, Tuple, TypedDict, List, Deque, NewType, Union, Optional
|
|
6
7
|
from collections import deque
|
|
7
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
9
|
|
|
@@ -56,6 +57,8 @@ class ReportData(TypedDict):
|
|
|
56
57
|
coverage_trend: List
|
|
57
58
|
property_execution_trend: List # Track executed properties count over steps
|
|
58
59
|
activity_count_history: Dict[str, int] # Activity traversal count from final coverage data
|
|
60
|
+
crash_events: List[Dict] # Crash events from crash-dump.log
|
|
61
|
+
anr_events: List[Dict] # ANR events from crash-dump.log
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
class PropertyExecResult(TypedDict):
|
|
@@ -119,6 +122,7 @@ class DataPath:
|
|
|
119
122
|
coverage_log: Path
|
|
120
123
|
screenshots_dir: Path
|
|
121
124
|
property_exec_info: Path
|
|
125
|
+
crash_dump_log: Path
|
|
122
126
|
|
|
123
127
|
|
|
124
128
|
class BugReportGenerator:
|
|
@@ -223,7 +227,8 @@ class BugReportGenerator:
|
|
|
223
227
|
result_json=self.result_dir / f"result_{self.log_timestamp}.json",
|
|
224
228
|
coverage_log=self.result_dir / f"output_{self.log_timestamp}" / "coverage.log",
|
|
225
229
|
screenshots_dir=self.result_dir / f"output_{self.log_timestamp}" / "screenshots",
|
|
226
|
-
property_exec_info=self.result_dir / f"property_exec_info_{self.log_timestamp}.json"
|
|
230
|
+
property_exec_info=self.result_dir / f"property_exec_info_{self.log_timestamp}.json",
|
|
231
|
+
crash_dump_log=self.result_dir / f"output_{self.log_timestamp}" / "crash-dump.log"
|
|
227
232
|
)
|
|
228
233
|
|
|
229
234
|
self.screenshots = deque()
|
|
@@ -287,7 +292,9 @@ class BugReportGenerator:
|
|
|
287
292
|
"screenshot_info": {},
|
|
288
293
|
"coverage_trend": [],
|
|
289
294
|
"property_execution_trend": [],
|
|
290
|
-
"activity_count_history": {}
|
|
295
|
+
"activity_count_history": {},
|
|
296
|
+
"crash_events": [],
|
|
297
|
+
"anr_events": []
|
|
291
298
|
}
|
|
292
299
|
|
|
293
300
|
# Parse steps.log file to get test step numbers and screenshot mappings
|
|
@@ -402,6 +409,11 @@ class BugReportGenerator:
|
|
|
402
409
|
# Load error details for properties with fail/error state
|
|
403
410
|
data["property_error_details"] = self._load_property_error_details()
|
|
404
411
|
|
|
412
|
+
# Load crash and ANR events from crash-dump.log
|
|
413
|
+
crash_events, anr_events = self._load_crash_dump_data()
|
|
414
|
+
data["crash_events"] = crash_events
|
|
415
|
+
data["anr_events"] = anr_events
|
|
416
|
+
|
|
405
417
|
return data
|
|
406
418
|
|
|
407
419
|
def _parse_step_data(self, raw_step_info: str) -> StepData:
|
|
@@ -432,7 +444,7 @@ class BugReportGenerator:
|
|
|
432
444
|
logger.error(f"Error when marking screenshots: {e}")
|
|
433
445
|
|
|
434
446
|
|
|
435
|
-
def _mark_screenshot_interaction(self, step_type: str, screenshot_name: str, action_type: str, position: Union[List,
|
|
447
|
+
def _mark_screenshot_interaction(self, step_type: str, screenshot_name: str, action_type: str, position: Union[List, Tuple]) -> bool:
|
|
436
448
|
"""
|
|
437
449
|
Mark interaction on screenshot with colored rectangle
|
|
438
450
|
|
|
@@ -556,7 +568,9 @@ class BugReportGenerator:
|
|
|
556
568
|
'take_screenshots': self.take_screenshots, # Pass screenshot setting to template
|
|
557
569
|
'property_execution_trend': data["property_execution_trend"],
|
|
558
570
|
'property_execution_data': json.dumps(data["property_execution_trend"]),
|
|
559
|
-
'activity_count_history': data["activity_count_history"]
|
|
571
|
+
'activity_count_history': data["activity_count_history"],
|
|
572
|
+
'crash_events': data["crash_events"],
|
|
573
|
+
'anr_events': data["anr_events"]
|
|
560
574
|
}
|
|
561
575
|
|
|
562
576
|
# Check if template exists, if not create it
|
|
@@ -616,7 +630,7 @@ class BugReportGenerator:
|
|
|
616
630
|
})
|
|
617
631
|
|
|
618
632
|
def _process_script_info(self, property_name: str, state: str, step_index: int, screenshot: str,
|
|
619
|
-
current_property: str, current_test: Dict, property_violations: Dict) ->
|
|
633
|
+
current_property: str, current_test: Dict, property_violations: Dict) -> Tuple:
|
|
620
634
|
"""
|
|
621
635
|
Process ScriptInfo step for property violations tracking
|
|
622
636
|
|
|
@@ -817,11 +831,257 @@ class BugReportGenerator:
|
|
|
817
831
|
|
|
818
832
|
return property_execution_trend
|
|
819
833
|
|
|
834
|
+
def _load_crash_dump_data(self) -> Tuple[List[Dict], List[Dict]]:
|
|
835
|
+
"""
|
|
836
|
+
Load crash and ANR events from crash-dump.log file
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
tuple: (crash_events, anr_events) - Lists of crash and ANR event dictionaries
|
|
840
|
+
"""
|
|
841
|
+
crash_events = []
|
|
842
|
+
anr_events = []
|
|
843
|
+
|
|
844
|
+
if not self.data_path.crash_dump_log.exists():
|
|
845
|
+
logger.info(f"No crash was found in this run.")
|
|
846
|
+
return crash_events, anr_events
|
|
847
|
+
|
|
848
|
+
try:
|
|
849
|
+
with open(self.data_path.crash_dump_log, "r", encoding="utf-8") as f:
|
|
850
|
+
content = f.read()
|
|
851
|
+
|
|
852
|
+
# Parse crash events
|
|
853
|
+
crash_events = self._parse_crash_events(content)
|
|
854
|
+
|
|
855
|
+
# Parse ANR events
|
|
856
|
+
anr_events = self._parse_anr_events(content)
|
|
857
|
+
|
|
858
|
+
logger.debug(f"Found {len(crash_events)} crash events and {len(anr_events)} ANR events")
|
|
859
|
+
|
|
860
|
+
return crash_events, anr_events
|
|
861
|
+
|
|
862
|
+
except Exception as e:
|
|
863
|
+
logger.error(f"Error reading crash dump file: {e}")
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def _parse_crash_events(self, content: str) -> List[Dict]:
|
|
867
|
+
"""
|
|
868
|
+
Parse crash events from crash-dump.log content
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
content: Content of crash-dump.log file
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
List[Dict]: List of crash event dictionaries
|
|
875
|
+
"""
|
|
876
|
+
crash_events = []
|
|
877
|
+
|
|
878
|
+
# Pattern to match crash blocks
|
|
879
|
+
crash_pattern = r'(\d{14})\ncrash:\n(.*?)\n// crash end'
|
|
880
|
+
|
|
881
|
+
for match in re.finditer(crash_pattern, content, re.DOTALL):
|
|
882
|
+
timestamp_str = match.group(1)
|
|
883
|
+
crash_content = match.group(2)
|
|
884
|
+
|
|
885
|
+
# Parse timestamp (format: YYYYMMDDHHMMSS)
|
|
886
|
+
try:
|
|
887
|
+
timestamp = datetime.strptime(timestamp_str, "%Y%m%d%H%M%S")
|
|
888
|
+
formatted_time = timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
|
889
|
+
except ValueError:
|
|
890
|
+
formatted_time = timestamp_str
|
|
891
|
+
|
|
892
|
+
# Extract crash information
|
|
893
|
+
crash_info = self._extract_crash_info(crash_content)
|
|
894
|
+
|
|
895
|
+
crash_event = {
|
|
896
|
+
"time": formatted_time,
|
|
897
|
+
"exception_type": crash_info.get("exception_type", "Unknown"),
|
|
898
|
+
"process": crash_info.get("process", "Unknown"),
|
|
899
|
+
"stack_trace": crash_info.get("stack_trace", "")
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
crash_events.append(crash_event)
|
|
903
|
+
|
|
904
|
+
return crash_events
|
|
905
|
+
|
|
906
|
+
def _parse_anr_events(self, content: str) -> List[Dict]:
|
|
907
|
+
"""
|
|
908
|
+
Parse ANR events from crash-dump.log content
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
content: Content of crash-dump.log file
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
List[Dict]: List of ANR event dictionaries
|
|
915
|
+
"""
|
|
916
|
+
anr_events = []
|
|
917
|
+
|
|
918
|
+
# Pattern to match ANR blocks
|
|
919
|
+
anr_pattern = r'(\d{14})\nanr:\n(.*?)\nanr end'
|
|
920
|
+
|
|
921
|
+
for match in re.finditer(anr_pattern, content, re.DOTALL):
|
|
922
|
+
timestamp_str = match.group(1)
|
|
923
|
+
anr_content = match.group(2)
|
|
924
|
+
|
|
925
|
+
# Parse timestamp (format: YYYYMMDDHHMMSS)
|
|
926
|
+
try:
|
|
927
|
+
timestamp = datetime.strptime(timestamp_str, "%Y%m%d%H%M%S")
|
|
928
|
+
formatted_time = timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
|
929
|
+
except ValueError:
|
|
930
|
+
formatted_time = timestamp_str
|
|
931
|
+
|
|
932
|
+
# Extract ANR information
|
|
933
|
+
anr_info = self._extract_anr_info(anr_content)
|
|
934
|
+
|
|
935
|
+
anr_event = {
|
|
936
|
+
"time": formatted_time,
|
|
937
|
+
"reason": anr_info.get("reason", "Unknown"),
|
|
938
|
+
"process": anr_info.get("process", "Unknown"),
|
|
939
|
+
"trace": anr_info.get("trace", "")
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
anr_events.append(anr_event)
|
|
943
|
+
|
|
944
|
+
return anr_events
|
|
945
|
+
|
|
946
|
+
def _extract_crash_info(self, crash_content: str) -> Dict:
|
|
947
|
+
"""
|
|
948
|
+
Extract crash information from crash content
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
crash_content: Content of a single crash block
|
|
952
|
+
|
|
953
|
+
Returns:
|
|
954
|
+
Dict: Extracted crash information
|
|
955
|
+
"""
|
|
956
|
+
crash_info = {
|
|
957
|
+
"exception_type": "Unknown",
|
|
958
|
+
"process": "Unknown",
|
|
959
|
+
"stack_trace": ""
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
lines = crash_content.strip().split('\n')
|
|
963
|
+
|
|
964
|
+
for line in lines:
|
|
965
|
+
line = line.strip()
|
|
966
|
+
|
|
967
|
+
# Extract PID from CRASH line
|
|
968
|
+
if line.startswith("// CRASH:"):
|
|
969
|
+
# Pattern: // CRASH: process_name (pid xxxx) (dump time: ...)
|
|
970
|
+
pid_match = re.search(r'\(pid\s+(\d+)\)', line)
|
|
971
|
+
if pid_match:
|
|
972
|
+
crash_info["process"] = pid_match.group(1)
|
|
973
|
+
|
|
974
|
+
# Extract exception type from Long Msg line
|
|
975
|
+
elif line.startswith("// Long Msg:"):
|
|
976
|
+
# Pattern: // Long Msg: ExceptionType: message
|
|
977
|
+
exception_match = re.search(r'// Long Msg:\s+([^:]+)', line)
|
|
978
|
+
if exception_match:
|
|
979
|
+
crash_info["exception_type"] = exception_match.group(1).strip()
|
|
980
|
+
|
|
981
|
+
# Extract full stack trace (all lines starting with //)
|
|
982
|
+
stack_lines = []
|
|
983
|
+
for line in lines:
|
|
984
|
+
if line.startswith("//"):
|
|
985
|
+
# Remove the "// " prefix for cleaner display
|
|
986
|
+
clean_line = line[3:] if line.startswith("// ") else line[2:]
|
|
987
|
+
stack_lines.append(clean_line)
|
|
988
|
+
|
|
989
|
+
crash_info["stack_trace"] = '\n'.join(stack_lines)
|
|
990
|
+
|
|
991
|
+
return crash_info
|
|
992
|
+
|
|
993
|
+
def _extract_anr_info(self, anr_content: str) -> Dict:
|
|
994
|
+
"""
|
|
995
|
+
Extract ANR information from ANR content
|
|
996
|
+
|
|
997
|
+
Args:
|
|
998
|
+
anr_content: Content of a single ANR block
|
|
999
|
+
|
|
1000
|
+
Returns:
|
|
1001
|
+
Dict: Extracted ANR information
|
|
1002
|
+
"""
|
|
1003
|
+
anr_info = {
|
|
1004
|
+
"reason": "Unknown",
|
|
1005
|
+
"process": "Unknown",
|
|
1006
|
+
"trace": ""
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
lines = anr_content.strip().split('\n')
|
|
1010
|
+
|
|
1011
|
+
for line in lines:
|
|
1012
|
+
line = line.strip()
|
|
1013
|
+
|
|
1014
|
+
# Extract PID from ANR line
|
|
1015
|
+
if line.startswith("// ANR:"):
|
|
1016
|
+
# Pattern: // ANR: process_name (pid xxxx) (dump time: ...)
|
|
1017
|
+
pid_match = re.search(r'\(pid\s+(\d+)\)', line)
|
|
1018
|
+
if pid_match:
|
|
1019
|
+
anr_info["process"] = pid_match.group(1)
|
|
1020
|
+
|
|
1021
|
+
# Extract reason from Reason line
|
|
1022
|
+
elif line.startswith("Reason:"):
|
|
1023
|
+
# Pattern: Reason: Input dispatching timed out (...)
|
|
1024
|
+
reason_match = re.search(r'Reason:\s+(.+)', line)
|
|
1025
|
+
if reason_match:
|
|
1026
|
+
full_reason = reason_match.group(1).strip()
|
|
1027
|
+
# Simplify the reason by extracting the main part before parentheses
|
|
1028
|
+
simplified_reason = self._simplify_anr_reason(full_reason)
|
|
1029
|
+
anr_info["reason"] = simplified_reason
|
|
1030
|
+
|
|
1031
|
+
# Store the full ANR trace content
|
|
1032
|
+
anr_info["trace"] = anr_content
|
|
1033
|
+
|
|
1034
|
+
return anr_info
|
|
1035
|
+
|
|
1036
|
+
def _simplify_anr_reason(self, full_reason: str) -> str:
|
|
1037
|
+
"""
|
|
1038
|
+
Simplify ANR reason by extracting the main part
|
|
1039
|
+
|
|
1040
|
+
Args:
|
|
1041
|
+
full_reason: Full ANR reason string
|
|
1042
|
+
|
|
1043
|
+
Returns:
|
|
1044
|
+
str: Simplified ANR reason
|
|
1045
|
+
"""
|
|
1046
|
+
# Common ANR reason patterns to simplify
|
|
1047
|
+
simplification_patterns = [
|
|
1048
|
+
# Input dispatching timed out (details...) -> Input dispatching timed out
|
|
1049
|
+
(r'^(Input dispatching timed out)\s*\(.*\).*$', r'\1'),
|
|
1050
|
+
# Broadcast of Intent (details...) -> Broadcast timeout
|
|
1051
|
+
(r'^Broadcast of Intent.*$', 'Broadcast timeout'),
|
|
1052
|
+
# Service timeout -> Service timeout
|
|
1053
|
+
(r'^Service.*timeout.*$', 'Service timeout'),
|
|
1054
|
+
# ContentProvider timeout -> ContentProvider timeout
|
|
1055
|
+
(r'^ContentProvider.*timeout.*$', 'ContentProvider timeout'),
|
|
1056
|
+
]
|
|
1057
|
+
|
|
1058
|
+
# Apply simplification patterns
|
|
1059
|
+
for pattern, replacement in simplification_patterns:
|
|
1060
|
+
match = re.match(pattern, full_reason, re.IGNORECASE)
|
|
1061
|
+
if match:
|
|
1062
|
+
if callable(replacement):
|
|
1063
|
+
return replacement(match)
|
|
1064
|
+
elif '\\1' in replacement:
|
|
1065
|
+
return re.sub(pattern, replacement, full_reason, flags=re.IGNORECASE)
|
|
1066
|
+
else:
|
|
1067
|
+
return replacement
|
|
1068
|
+
|
|
1069
|
+
# If no pattern matches, try to extract the part before the first parenthesis
|
|
1070
|
+
paren_match = re.match(r'^([^(]+)', full_reason)
|
|
1071
|
+
if paren_match:
|
|
1072
|
+
simplified = paren_match.group(1).strip()
|
|
1073
|
+
# Remove trailing punctuation
|
|
1074
|
+
simplified = re.sub(r'[.,;:]+$', '', simplified)
|
|
1075
|
+
return simplified
|
|
1076
|
+
|
|
1077
|
+
# If all else fails, return the original but truncated
|
|
1078
|
+
return full_reason[:50] + "..." if len(full_reason) > 50 else full_reason
|
|
1079
|
+
|
|
820
1080
|
|
|
821
1081
|
if __name__ == "__main__":
|
|
822
1082
|
print("Generating bug report")
|
|
823
1083
|
# OUTPUT_PATH = "<Your output path>"
|
|
824
|
-
OUTPUT_PATH = "P:/Python/Kea2/output/
|
|
1084
|
+
OUTPUT_PATH = "P:/Python/Kea2/output/res_2025072011_5048015228"
|
|
825
1085
|
|
|
826
1086
|
report_generator = BugReportGenerator()
|
|
827
1087
|
report_path = report_generator.generate_report(OUTPUT_PATH)
|
|
@@ -54,9 +54,15 @@ def cmd_report(args):
|
|
|
54
54
|
logger.error("Report directory path is required. Use -p to specify the path.")
|
|
55
55
|
return
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
if Path(report_dir).is_absolute():
|
|
58
|
+
report_path = Path(report_dir)
|
|
59
|
+
else:
|
|
60
|
+
report_path = Path.cwd() / report_dir
|
|
61
|
+
|
|
62
|
+
report_path = report_path.resolve()
|
|
63
|
+
|
|
58
64
|
if not report_path.exists():
|
|
59
|
-
logger.error(f"Report directory does not exist: {
|
|
65
|
+
logger.error(f"Report directory does not exist: {report_path}")
|
|
60
66
|
return
|
|
61
67
|
|
|
62
68
|
logger.debug(f"Generating test report from directory: {report_dir}")
|
|
@@ -74,6 +80,47 @@ def cmd_report(args):
|
|
|
74
80
|
logger.error(f"Error generating test report: {e}")
|
|
75
81
|
|
|
76
82
|
|
|
83
|
+
def cmd_merge(args):
|
|
84
|
+
"""Merge multiple test report directories and generate a combined report"""
|
|
85
|
+
from .report_merger import TestReportMerger
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Validate input paths
|
|
89
|
+
if not args.paths or len(args.paths) < 2:
|
|
90
|
+
logger.error("At least 2 test report paths are required for merging. Use -p to specify paths.")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
# Validate that all paths exist
|
|
94
|
+
for path in args.paths:
|
|
95
|
+
path_obj = Path(path)
|
|
96
|
+
if not path_obj.exists():
|
|
97
|
+
logger.error(f"Test report path does not exist: {path}")
|
|
98
|
+
return
|
|
99
|
+
if not path_obj.is_dir():
|
|
100
|
+
logger.error(f"Path is not a directory: {path}")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
logger.debug(f"Merging {len(args.paths)} test report directories...")
|
|
104
|
+
|
|
105
|
+
# Initialize merger
|
|
106
|
+
merger = TestReportMerger()
|
|
107
|
+
|
|
108
|
+
# Merge test reports
|
|
109
|
+
merged_dir = merger.merge_reports(args.paths, args.output)
|
|
110
|
+
|
|
111
|
+
# Print results
|
|
112
|
+
print(f"✅ Test reports merged successfully!", flush=True)
|
|
113
|
+
print(f"📁 Merged report directory: {merged_dir}", flush=True)
|
|
114
|
+
print(f"📊 Merged report: {merged_dir}/merged_report.html", flush=True)
|
|
115
|
+
|
|
116
|
+
# Get merge summary
|
|
117
|
+
merge_summary = merger.get_merge_summary()
|
|
118
|
+
print(f"📈 Merged {merge_summary.get('merged_directories', 0)} directories", flush=True)
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Error during merge operation: {e}")
|
|
122
|
+
|
|
123
|
+
|
|
77
124
|
def cmd_run(args):
|
|
78
125
|
base_dir = getProjectRoot()
|
|
79
126
|
if base_dir is None:
|
|
@@ -102,6 +149,28 @@ _commands = [
|
|
|
102
149
|
help="Path to the directory containing test results"
|
|
103
150
|
)
|
|
104
151
|
]
|
|
152
|
+
),
|
|
153
|
+
dict(
|
|
154
|
+
action=cmd_merge,
|
|
155
|
+
command="merge",
|
|
156
|
+
help="merge multiple test report directories and generate a combined report",
|
|
157
|
+
flags=[
|
|
158
|
+
dict(
|
|
159
|
+
name=["paths"],
|
|
160
|
+
args=["-p", "--paths"],
|
|
161
|
+
type=str,
|
|
162
|
+
nargs='+',
|
|
163
|
+
required=True,
|
|
164
|
+
help="Paths to test report directories (res_* directories) to merge"
|
|
165
|
+
),
|
|
166
|
+
dict(
|
|
167
|
+
name=["output"],
|
|
168
|
+
args=["-o", "--output"],
|
|
169
|
+
type=str,
|
|
170
|
+
required=False,
|
|
171
|
+
help="Output directory for merged report (optional)"
|
|
172
|
+
)
|
|
173
|
+
]
|
|
105
174
|
)
|
|
106
175
|
]
|
|
107
176
|
|