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.

Files changed (45) hide show
  1. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/PKG-INFO +10 -3
  2. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/SOURCES.txt +2 -0
  3. {kea2_python-0.2.4 → kea2_python-0.3.0}/PKG-INFO +10 -3
  4. {kea2_python-0.2.4 → kea2_python-0.3.0}/README.md +9 -2
  5. kea2_python-0.3.0/kea2/assets/monkeyq.jar +0 -0
  6. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/bug_report_generator.py +267 -7
  7. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/cli.py +71 -2
  8. kea2_python-0.3.0/kea2/report_merger.py +651 -0
  9. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/templates/bug_report_template.html +1583 -68
  10. kea2_python-0.3.0/kea2/templates/merged_bug_report_template.html +2547 -0
  11. {kea2_python-0.2.4 → kea2_python-0.3.0}/pyproject.toml +1 -1
  12. kea2_python-0.2.4/kea2/assets/monkeyq.jar +0 -0
  13. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/dependency_links.txt +0 -0
  14. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/entry_points.txt +0 -0
  15. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/requires.txt +0 -0
  16. {kea2_python-0.2.4 → kea2_python-0.3.0}/Kea2_python.egg-info/top_level.txt +0 -0
  17. {kea2_python-0.2.4 → kea2_python-0.3.0}/LICENSE +0 -0
  18. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/__init__.py +0 -0
  19. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/absDriver.py +0 -0
  20. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/adbUtils.py +0 -0
  21. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot-thirdpart.jar +0 -0
  22. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/abl.strings +0 -0
  23. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/awl.strings +0 -0
  24. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.config +0 -0
  25. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.fuzzing.strings +0 -0
  26. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.schema.strings +0 -0
  27. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.strings +0 -0
  28. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/max.tree.pruning +0 -0
  29. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_configs/widget.block.py +0 -0
  30. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/arm64-v8a/libfastbot_native.so +0 -0
  31. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/armeabi-v7a/libfastbot_native.so +0 -0
  32. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/x86/libfastbot_native.so +0 -0
  33. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/fastbot_libs/x86_64/libfastbot_native.so +0 -0
  34. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/framework.jar +0 -0
  35. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/kea2-thirdpart.jar +0 -0
  36. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/assets/quicktest.py +0 -0
  37. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/fastbotManager.py +0 -0
  38. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/keaUtils.py +0 -0
  39. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/kea_launcher.py +0 -0
  40. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/logWatcher.py +0 -0
  41. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/resultSyncer.py +0 -0
  42. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/u2Driver.py +0 -0
  43. {kea2_python-0.2.4 → kea2_python-0.3.0}/kea2/utils.py +0 -0
  44. {kea2_python-0.2.4 → kea2_python-0.3.0}/setup.cfg +0 -0
  45. {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.2.4
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
- - Chen Peng ([@Drifterpc][])
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.2.4
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
- - Chen Peng ([@Drifterpc][])
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
- - Chen Peng ([@Drifterpc][])
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
@@ -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, tuple]) -> bool:
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) -> tuple:
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/res_2025070814_4842540549"
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
- report_path = Path(report_dir)
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: {report_dir}")
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