failtrace 0.0.8__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.
- failtrace-0.0.8/PKG-INFO +89 -0
- failtrace-0.0.8/README.md +62 -0
- failtrace-0.0.8/failtrace/__init__.py +0 -0
- failtrace-0.0.8/failtrace/__main__.py +4 -0
- failtrace-0.0.8/failtrace/analysis/__init__.py +0 -0
- failtrace-0.0.8/failtrace/analysis/critical_path_extractor.py +180 -0
- failtrace-0.0.8/failtrace/analysis/function_extractor.py +506 -0
- failtrace-0.0.8/failtrace/analysis/locator.py +124 -0
- failtrace-0.0.8/failtrace/analysis/mapper.py +118 -0
- failtrace-0.0.8/failtrace/analysis/summarizer.py +44 -0
- failtrace-0.0.8/failtrace/cli/__init__.py +0 -0
- failtrace-0.0.8/failtrace/cli/main.py +312 -0
- failtrace-0.0.8/failtrace/graph/__init__.py +0 -0
- failtrace-0.0.8/failtrace/graph/builder/__init__.py +10 -0
- failtrace-0.0.8/failtrace/graph/builder/base.py +9 -0
- failtrace-0.0.8/failtrace/graph/builder/csharp_graph.py +547 -0
- failtrace-0.0.8/failtrace/graph/builder/csharp_graph_builder.py +10 -0
- failtrace-0.0.8/failtrace/graph/builder/detector.py +18 -0
- failtrace-0.0.8/failtrace/graph/builder/java_graph.py +164 -0
- failtrace-0.0.8/failtrace/graph/builder/java_graph_builder.py +10 -0
- failtrace-0.0.8/failtrace/graph/builder/plugins.py +26 -0
- failtrace-0.0.8/failtrace/graph/builder/python_graph.py +305 -0
- failtrace-0.0.8/failtrace/graph/builder/python_graph_builder.py +10 -0
- failtrace-0.0.8/failtrace/graph/utils/__init__.py +0 -0
- failtrace-0.0.8/failtrace/graph/utils/locator_ranker.py +62 -0
- failtrace-0.0.8/failtrace/graph/utils/node_summarizer.py +76 -0
- failtrace-0.0.8/failtrace/graph/utils/path_enricher.py +100 -0
- failtrace-0.0.8/failtrace/llm/__init__.py +0 -0
- failtrace-0.0.8/failtrace/llm/avalai_client.py +105 -0
- failtrace-0.0.8/failtrace/llm/few_shots/__init__.py +0 -0
- failtrace-0.0.8/failtrace/llm/few_shots/csharp.json +10 -0
- failtrace-0.0.8/failtrace/llm/few_shots/java.json +10 -0
- failtrace-0.0.8/failtrace/llm/few_shots/python.json +10 -0
- failtrace-0.0.8/failtrace/llm/few_shots_loader.py +53 -0
- failtrace-0.0.8/failtrace/llm/prompt_generator.py +160 -0
- failtrace-0.0.8/failtrace/llm/structured_prompt_builder.py +141 -0
- failtrace-0.0.8/failtrace/output/analysis_report.txt +113 -0
- failtrace-0.0.8/failtrace/output/cache/graph.pkl +0 -0
- failtrace-0.0.8/failtrace/output/final_prompt.txt +399 -0
- failtrace-0.0.8/failtrace/output/function_summaries.json +8 -0
- failtrace-0.0.8/failtrace/output/graph.html +164 -0
- failtrace-0.0.8/failtrace/output/llm_prompt.json +247 -0
- failtrace-0.0.8/failtrace/output/summary.json +38 -0
- failtrace-0.0.8/failtrace/report/__init__.py +0 -0
- failtrace-0.0.8/failtrace/report/app.css +285 -0
- failtrace-0.0.8/failtrace/report/app.js +399 -0
- failtrace-0.0.8/failtrace/report/report_template.html +118 -0
- failtrace-0.0.8/failtrace/utils/__init__.py +0 -0
- failtrace-0.0.8/failtrace/utils/file_ops.py +5 -0
- failtrace-0.0.8/failtrace/utils/logs_parser.py +511 -0
- failtrace-0.0.8/failtrace/utils/normalize.py +98 -0
- failtrace-0.0.8/failtrace/utils/report_renderer.py +457 -0
- failtrace-0.0.8/failtrace/utils/visualizer.py +51 -0
- failtrace-0.0.8/pyproject.toml +64 -0
failtrace-0.0.8/PKG-INFO
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: failtrace
|
|
3
|
+
Version: 0.0.8
|
|
4
|
+
Summary: Leveraging Large Language Models for Automated Test Results Analysis
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: cli,testing,llm,analysis
|
|
7
|
+
Author: Mohadese Akhoondy
|
|
8
|
+
Author-email: m.akhoondy1381@gmail.com
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Dist: javalang (>=0.13.0,<0.14.0)
|
|
15
|
+
Requires-Dist: libcst (>=1.8.2,<2.0.0)
|
|
16
|
+
Requires-Dist: networkx (>=3.5,<4.0)
|
|
17
|
+
Requires-Dist: openai (>=1.99.9,<2.0.0)
|
|
18
|
+
Requires-Dist: platformdirs (>=4.2,<5.0)
|
|
19
|
+
Requires-Dist: pycg (>=0.0.8,<0.0.9)
|
|
20
|
+
Requires-Dist: pyvis (>=0.3.2,<0.4.0)
|
|
21
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
22
|
+
Requires-Dist: rich (>=14.1.0,<15.0.0)
|
|
23
|
+
Requires-Dist: tree-sitter (>=0.25.1,<0.26.0)
|
|
24
|
+
Requires-Dist: tree-sitter-c-sharp (==0.23.1)
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# FailTrace
|
|
28
|
+
|
|
29
|
+
**Leveraging Large Language Models for Automated Test Results Analysis**
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/failtrace/)
|
|
32
|
+
[](https://www.python.org/)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🌍 Overview
|
|
37
|
+
|
|
38
|
+
FailTrace helps developers and QA engineers **analyze automated test results** with **Large Language Models (LLMs)**.
|
|
39
|
+
It builds **dependency graphs**, maps them with test execution logs, and generates **interactive HTML reports** with insights into failures.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ✨ Features
|
|
44
|
+
|
|
45
|
+
- Supports **Python, Java, C#**
|
|
46
|
+
- Dependency graph visualization (PyVis + NetworkX)
|
|
47
|
+
- Test log parsing (Pytest, JSON, TRX, …)
|
|
48
|
+
- Interactive reports (`HTML + CSS + JS`)
|
|
49
|
+
- Dry-run mode (no API calls)
|
|
50
|
+
- CLI-first design for CI/CD
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📦 Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install failtrace
|
|
58
|
+
````
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## ⚡ Quickstart
|
|
63
|
+
|
|
64
|
+
Run the **full pipeline**:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m failtrace full -p ./your-source-code -l ./your-test-results.xml --open-report
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run in **quick mode** (reuse cached graph):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
failtrace quick -p ./src -l ./results.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 📜 License
|
|
79
|
+
|
|
80
|
+
Licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 👩💻 Author
|
|
85
|
+
|
|
86
|
+
[**Mohadese Akhoondy**](mailto:m.akhoondy1381@gmail.com)
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# FailTrace
|
|
2
|
+
|
|
3
|
+
**Leveraging Large Language Models for Automated Test Results Analysis**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/failtrace/)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🌍 Overview
|
|
11
|
+
|
|
12
|
+
FailTrace helps developers and QA engineers **analyze automated test results** with **Large Language Models (LLMs)**.
|
|
13
|
+
It builds **dependency graphs**, maps them with test execution logs, and generates **interactive HTML reports** with insights into failures.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ✨ Features
|
|
18
|
+
|
|
19
|
+
- Supports **Python, Java, C#**
|
|
20
|
+
- Dependency graph visualization (PyVis + NetworkX)
|
|
21
|
+
- Test log parsing (Pytest, JSON, TRX, …)
|
|
22
|
+
- Interactive reports (`HTML + CSS + JS`)
|
|
23
|
+
- Dry-run mode (no API calls)
|
|
24
|
+
- CLI-first design for CI/CD
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 📦 Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install failtrace
|
|
32
|
+
````
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚡ Quickstart
|
|
37
|
+
|
|
38
|
+
Run the **full pipeline**:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m failtrace full -p ./your-source-code -l ./your-test-results.xml --open-report
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run in **quick mode** (reuse cached graph):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
failtrace quick -p ./src -l ./results.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📜 License
|
|
53
|
+
|
|
54
|
+
Licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 👩💻 Author
|
|
59
|
+
|
|
60
|
+
[**Mohadese Akhoondy**](mailto:m.akhoondy1381@gmail.com)
|
|
61
|
+
|
|
62
|
+
```
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from typing import Dict, List, Any, Tuple, Iterable
|
|
3
|
+
from ..graph.utils.path_enricher import enrich_path_with_metadata
|
|
4
|
+
|
|
5
|
+
_EXCLUDED_SUBSTRS = (
|
|
6
|
+
"/.venv/",
|
|
7
|
+
"/venv/",
|
|
8
|
+
"/env/",
|
|
9
|
+
"/.tox/",
|
|
10
|
+
"/site-packages/",
|
|
11
|
+
"/dist-packages/",
|
|
12
|
+
"/.pytest_cache/",
|
|
13
|
+
"/__pycache__/",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
_DEFAULT_MAX_PATHS_PER_DIR = 5
|
|
17
|
+
_DEFAULT_CUTOFF = 6
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _keep_step(step: Dict[str, Any]) -> bool:
|
|
21
|
+
if not isinstance(step, dict):
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
f = (step.get("file") or "").replace("\\", "/").lower()
|
|
25
|
+
if not f:
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
path = f if f.startswith("/") else f"/{f}"
|
|
29
|
+
for bad in _EXCLUDED_SUBSTRS:
|
|
30
|
+
if bad in path:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _unique(seq: Iterable[str]) -> List[str]:
|
|
37
|
+
seen = set()
|
|
38
|
+
out: List[str] = []
|
|
39
|
+
for x in seq:
|
|
40
|
+
if x not in seen:
|
|
41
|
+
out.append(x)
|
|
42
|
+
seen.add(x)
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _attach_origin_for_external_step(graph: nx.DiGraph, step: Dict[str, Any]) -> None:
|
|
47
|
+
if step.get("type") != "external":
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
node_id = step.get("node")
|
|
51
|
+
if not node_id or node_id not in graph:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
callers_files: List[str] = []
|
|
55
|
+
for u in graph.predecessors(node_id):
|
|
56
|
+
f = (graph.nodes.get(u, {}) or {}).get("file")
|
|
57
|
+
if f:
|
|
58
|
+
callers_files.append(str(f).replace("\\", "/"))
|
|
59
|
+
|
|
60
|
+
if not callers_files:
|
|
61
|
+
for v in graph.successors(node_id):
|
|
62
|
+
f = (graph.nodes.get(v, {}) or {}).get("file")
|
|
63
|
+
if f:
|
|
64
|
+
callers_files.append(str(f).replace("\\", "/"))
|
|
65
|
+
|
|
66
|
+
callers_files = _unique(callers_files)
|
|
67
|
+
if callers_files:
|
|
68
|
+
step["origin"] = {"callers": callers_files[:5]}
|
|
69
|
+
else:
|
|
70
|
+
step["origin"] = {"callers": []}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _augment_externals_with_origin(
|
|
74
|
+
graph: nx.DiGraph, path: List[Dict[str, Any]]
|
|
75
|
+
) -> None:
|
|
76
|
+
for step in path:
|
|
77
|
+
if (
|
|
78
|
+
isinstance(step, dict)
|
|
79
|
+
and step.get("type") == "external"
|
|
80
|
+
and "origin" not in step
|
|
81
|
+
):
|
|
82
|
+
_attach_origin_for_external_step(graph, step)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _path_signature(path: List[Dict[str, Any]]) -> Tuple[str, ...]:
|
|
86
|
+
return tuple(
|
|
87
|
+
step["node"] for step in path if isinstance(step, dict) and "node" in step
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _informativeness_score(path: List[Dict[str, Any]]) -> Tuple[int, int]:
|
|
92
|
+
non_test = sum(1 for s in path if not s.get("is_test", False))
|
|
93
|
+
return (non_test, len(path))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _postprocess_paths(
|
|
97
|
+
paths: List[List[Dict[str, Any]]],
|
|
98
|
+
*,
|
|
99
|
+
drop_short_downstream: bool,
|
|
100
|
+
max_paths: int,
|
|
101
|
+
) -> List[List[Dict[str, Any]]]:
|
|
102
|
+
|
|
103
|
+
uniq: Dict[Tuple[str, ...], List[Dict[str, Any]]] = {}
|
|
104
|
+
|
|
105
|
+
for p in paths:
|
|
106
|
+
if drop_short_downstream and len(p) < 2:
|
|
107
|
+
continue
|
|
108
|
+
sig = _path_signature(p)
|
|
109
|
+
if not sig:
|
|
110
|
+
continue
|
|
111
|
+
old = uniq.get(sig)
|
|
112
|
+
if old is None or _informativeness_score(p) > _informativeness_score(old):
|
|
113
|
+
uniq[sig] = p
|
|
114
|
+
|
|
115
|
+
sorted_paths = sorted(uniq.values(), key=_informativeness_score, reverse=True)
|
|
116
|
+
return sorted_paths[:max_paths]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def find_critical_paths(
|
|
120
|
+
graph: nx.DiGraph,
|
|
121
|
+
*,
|
|
122
|
+
cutoff: int = _DEFAULT_CUTOFF,
|
|
123
|
+
max_paths_per_direction: int = _DEFAULT_MAX_PATHS_PER_DIR,
|
|
124
|
+
) -> Dict[str, Dict[str, List[List[Dict[str, Any]]]]]:
|
|
125
|
+
|
|
126
|
+
critical_paths: Dict[str, Dict[str, List[List[Dict[str, Any]]]]] = {}
|
|
127
|
+
|
|
128
|
+
failed_tests = [
|
|
129
|
+
node
|
|
130
|
+
for node, data in graph.nodes(data=True)
|
|
131
|
+
if data.get("is_test") and data.get("test_status") == "failed"
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if not failed_tests:
|
|
135
|
+
return {}
|
|
136
|
+
|
|
137
|
+
for failed_node in failed_tests:
|
|
138
|
+
upstream_raw: List[List[Dict[str, Any]]] = []
|
|
139
|
+
downstream_raw: List[List[Dict[str, Any]]] = []
|
|
140
|
+
|
|
141
|
+
for node in graph.nodes:
|
|
142
|
+
if node == failed_node:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
for path in nx.all_simple_paths(
|
|
147
|
+
graph, source=node, target=failed_node, cutoff=cutoff
|
|
148
|
+
):
|
|
149
|
+
enriched = enrich_path_with_metadata(graph, path)
|
|
150
|
+
_augment_externals_with_origin(graph, enriched)
|
|
151
|
+
filtered = [step for step in enriched if _keep_step(step)]
|
|
152
|
+
if filtered:
|
|
153
|
+
upstream_raw.append(filtered)
|
|
154
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
for path in nx.all_simple_paths(
|
|
159
|
+
graph, source=failed_node, target=node, cutoff=cutoff
|
|
160
|
+
):
|
|
161
|
+
enriched = enrich_path_with_metadata(graph, path)
|
|
162
|
+
_augment_externals_with_origin(graph, enriched)
|
|
163
|
+
filtered = [step for step in enriched if _keep_step(step)]
|
|
164
|
+
if filtered:
|
|
165
|
+
downstream_raw.append(filtered)
|
|
166
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
upstream = _postprocess_paths(
|
|
170
|
+
upstream_raw, drop_short_downstream=False, max_paths=max_paths_per_direction
|
|
171
|
+
)
|
|
172
|
+
downstream = _postprocess_paths(
|
|
173
|
+
downstream_raw,
|
|
174
|
+
drop_short_downstream=True,
|
|
175
|
+
max_paths=max_paths_per_direction,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
critical_paths[failed_node] = {"upstream": upstream, "downstream": downstream}
|
|
179
|
+
|
|
180
|
+
return critical_paths
|