failtrace 0.2.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.
- failtrace-0.2.0/PKG-INFO +30 -0
- failtrace-0.2.0/README.md +0 -0
- failtrace-0.2.0/failtrace/__init__.py +0 -0
- failtrace-0.2.0/failtrace/__main__.py +4 -0
- failtrace-0.2.0/failtrace/analysis/__init__.py +0 -0
- failtrace-0.2.0/failtrace/analysis/critical_path.py +53 -0
- failtrace-0.2.0/failtrace/analysis/critical_path_extractor.py +205 -0
- failtrace-0.2.0/failtrace/analysis/function_extractor.py +530 -0
- failtrace-0.2.0/failtrace/analysis/locator.py +157 -0
- failtrace-0.2.0/failtrace/analysis/mapper.py +130 -0
- failtrace-0.2.0/failtrace/analysis/summarizer.py +47 -0
- failtrace-0.2.0/failtrace/cli/__init__.py +0 -0
- failtrace-0.2.0/failtrace/cli/main.py +189 -0
- failtrace-0.2.0/failtrace/graph/__init__.py +0 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/__init__.py +11 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/base.py +12 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/csharp_graph.py +497 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/csharp_graph_builder.py +13 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/detector.py +18 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/java_graph.py +164 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/java_graph_builder.py +13 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/plugins.py +28 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/python_graph.py +311 -0
- failtrace-0.2.0/failtrace/graph/graph_builder/python_graph_builder.py +15 -0
- failtrace-0.2.0/failtrace/graph/graph_utils/__init__.py +0 -0
- failtrace-0.2.0/failtrace/graph/graph_utils/locator_ranker.py +77 -0
- failtrace-0.2.0/failtrace/graph/graph_utils/node_summarizer.py +84 -0
- failtrace-0.2.0/failtrace/graph/graph_utils/path_enricher.py +127 -0
- failtrace-0.2.0/failtrace/lib/bindings/utils.js +189 -0
- failtrace-0.2.0/failtrace/lib/tom-select/tom-select.complete.min.js +356 -0
- failtrace-0.2.0/failtrace/lib/tom-select/tom-select.css +334 -0
- failtrace-0.2.0/failtrace/lib/vis-9.1.2/vis-network.css +1 -0
- failtrace-0.2.0/failtrace/lib/vis-9.1.2/vis-network.min.js +27 -0
- failtrace-0.2.0/failtrace/llm/__init__.py +0 -0
- failtrace-0.2.0/failtrace/llm/avalai_client.py +105 -0
- failtrace-0.2.0/failtrace/llm/few_shots/csharp_async_race.json +10 -0
- failtrace-0.2.0/failtrace/llm/few_shots/java_nullpointer.json +10 -0
- failtrace-0.2.0/failtrace/llm/few_shots/python_unit_fail.json +10 -0
- failtrace-0.2.0/failtrace/llm/few_shots_loader.py +62 -0
- failtrace-0.2.0/failtrace/llm/function_extractor.py +100 -0
- failtrace-0.2.0/failtrace/llm/prompt_generator.py +170 -0
- failtrace-0.2.0/failtrace/llm/structured_prompt_builder.py +141 -0
- failtrace-0.2.0/failtrace/tests/__init__.py +0 -0
- failtrace-0.2.0/failtrace/tests/test_main.py +0 -0
- failtrace-0.2.0/failtrace/utils/__init__.py +0 -0
- failtrace-0.2.0/failtrace/utils/file_ops.py +5 -0
- failtrace-0.2.0/failtrace/utils/logs_parser.py +578 -0
- failtrace-0.2.0/failtrace/utils/normalize.py +128 -0
- failtrace-0.2.0/failtrace/utils/report_renderer.py +487 -0
- failtrace-0.2.0/failtrace/utils/visualizer.py +36 -0
- failtrace-0.2.0/pyproject.toml +43 -0
failtrace-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: failtrace
|
|
3
|
+
Version: 0.2.0
|
|
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: mypy (>=1.17.1,<2.0.0)
|
|
17
|
+
Requires-Dist: networkx (>=3.5,<4.0)
|
|
18
|
+
Requires-Dist: openai (>=1.99.9,<2.0.0)
|
|
19
|
+
Requires-Dist: pycg (>=0.0.8,<0.0.9)
|
|
20
|
+
Requires-Dist: pylint (>=3.3.8,<4.0.0)
|
|
21
|
+
Requires-Dist: pytest (>=8.4.1,<9.0.0)
|
|
22
|
+
Requires-Dist: pytest-json-report (>=1.5.0,<2.0.0)
|
|
23
|
+
Requires-Dist: pyvis (>=0.3.2,<0.4.0)
|
|
24
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
25
|
+
Requires-Dist: rich (>=14.1.0,<15.0.0)
|
|
26
|
+
Requires-Dist: tree-sitter (>=0.25.1,<0.26.0)
|
|
27
|
+
Requires-Dist: tree-sitter-c-sharp (==0.23.1)
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from typing import Dict, List, Any
|
|
3
|
+
from ..graph.graph_utils.path_enricher import enrich_path_with_metadata
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def find_critical_paths(graph: nx.DiGraph) -> Dict[str, Dict[str, List[List[Dict[str, Any]]]]]:
|
|
7
|
+
"""
|
|
8
|
+
مسیرهای بحرانی برای تستهای شکستخورده را استخراج میکند (upstream/downstream)
|
|
9
|
+
و خروجی enriched برمیگرداند.
|
|
10
|
+
"""
|
|
11
|
+
critical_paths: Dict[str, Dict[str, List[List[Dict[str, Any]]]]] = {}
|
|
12
|
+
|
|
13
|
+
failed_tests = [
|
|
14
|
+
node for node, data in graph.nodes(data=True)
|
|
15
|
+
if data.get("is_test") and data.get("test_status") == "failed"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
if not failed_tests:
|
|
19
|
+
# Debugging output
|
|
20
|
+
""" print("[critical] No failed tests found.") """
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
for failed_node in failed_tests:
|
|
24
|
+
upstream = []
|
|
25
|
+
downstream = []
|
|
26
|
+
|
|
27
|
+
for node in graph.nodes:
|
|
28
|
+
if node == failed_node:
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
paths = nx.all_simple_paths(graph, source=node, target=failed_node, cutoff=6)
|
|
33
|
+
for path in paths:
|
|
34
|
+
enriched_path = enrich_path_with_metadata(graph, path)
|
|
35
|
+
upstream.append(enriched_path)
|
|
36
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
paths = nx.all_simple_paths(graph, source=failed_node, target=node, cutoff=6)
|
|
41
|
+
for path in paths:
|
|
42
|
+
enriched_path = enrich_path_with_metadata(graph, path)
|
|
43
|
+
downstream.append(enriched_path)
|
|
44
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
critical_paths[failed_node] = {
|
|
48
|
+
"upstream": upstream,
|
|
49
|
+
"downstream": downstream
|
|
50
|
+
}
|
|
51
|
+
# Debugging output
|
|
52
|
+
""" print(f"[critical] Found {len(critical_paths)} critical test nodes.") """
|
|
53
|
+
return critical_paths
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import networkx as nx
|
|
2
|
+
from typing import Dict, List, Any, Tuple, Iterable
|
|
3
|
+
from ..graph.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
|
+
"""
|
|
22
|
+
آیا این گام نگه داشته شود؟
|
|
23
|
+
- نودهای external حذف نمیشوند (برای تحلیل LLM لازماند).
|
|
24
|
+
- هر گرهی که file آن داخل مسیرهای محیط/غیرسورس باشد حذف میشود.
|
|
25
|
+
"""
|
|
26
|
+
if not isinstance(step, dict):
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
f = (step.get("file") or "").replace("\\", "/").lower()
|
|
30
|
+
if not f:
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
path = f if f.startswith("/") else f"/{f}"
|
|
34
|
+
for bad in _EXCLUDED_SUBSTRS:
|
|
35
|
+
if bad in path:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _unique(seq: Iterable[str]) -> List[str]:
|
|
42
|
+
seen = set()
|
|
43
|
+
out: List[str] = []
|
|
44
|
+
for x in seq:
|
|
45
|
+
if x not in seen:
|
|
46
|
+
out.append(x)
|
|
47
|
+
seen.add(x)
|
|
48
|
+
return out
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _attach_origin_for_external_step(graph: nx.DiGraph, step: Dict[str, Any]) -> None:
|
|
52
|
+
"""
|
|
53
|
+
برای نود external یک «origin» اضافه میکند:
|
|
54
|
+
- callers: فهرست فایلهایی که این نماد از آنها فراخوانی شده (سرنخ import/alias).
|
|
55
|
+
- اگر caller فایلی نداشته باشد، حذف میشود.
|
|
56
|
+
"""
|
|
57
|
+
if step.get("type") != "external":
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
node_id = step.get("node")
|
|
61
|
+
if not node_id or node_id not in graph:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
callers_files: List[str] = []
|
|
65
|
+
for u in graph.predecessors(node_id):
|
|
66
|
+
f = (graph.nodes.get(u, {}) or {}).get("file")
|
|
67
|
+
if f:
|
|
68
|
+
callers_files.append(str(f).replace("\\", "/"))
|
|
69
|
+
|
|
70
|
+
if not callers_files:
|
|
71
|
+
for v in graph.successors(node_id):
|
|
72
|
+
f = (graph.nodes.get(v, {}) or {}).get("file")
|
|
73
|
+
if f:
|
|
74
|
+
callers_files.append(str(f).replace("\\", "/"))
|
|
75
|
+
|
|
76
|
+
callers_files = _unique(callers_files)
|
|
77
|
+
if callers_files:
|
|
78
|
+
step["origin"] = {"callers": callers_files[:5]}
|
|
79
|
+
else:
|
|
80
|
+
step["origin"] = {"callers": []}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _augment_externals_with_origin(
|
|
84
|
+
graph: nx.DiGraph, path: List[Dict[str, Any]]
|
|
85
|
+
) -> None:
|
|
86
|
+
for step in path:
|
|
87
|
+
if (
|
|
88
|
+
isinstance(step, dict)
|
|
89
|
+
and step.get("type") == "external"
|
|
90
|
+
and "origin" not in step
|
|
91
|
+
):
|
|
92
|
+
_attach_origin_for_external_step(graph, step)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _path_signature(path: List[Dict[str, Any]]) -> Tuple[str, ...]:
|
|
96
|
+
"""امضای یکتا برای مسیر بر مبنای توالی node-id ها."""
|
|
97
|
+
return tuple(
|
|
98
|
+
step["node"] for step in path if isinstance(step, dict) and "node" in step
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _informativeness_score(path: List[Dict[str, Any]]) -> Tuple[int, int]:
|
|
103
|
+
"""
|
|
104
|
+
نمرهی مفید بودن مسیر:
|
|
105
|
+
- تعداد گرههای غیرتستی (بیشتر بهتر)
|
|
106
|
+
- طول مسیر (بیشتر بهتر)
|
|
107
|
+
"""
|
|
108
|
+
non_test = sum(1 for s in path if not s.get("is_test", False))
|
|
109
|
+
return (non_test, len(path))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _postprocess_paths(
|
|
113
|
+
paths: List[List[Dict[str, Any]]],
|
|
114
|
+
*,
|
|
115
|
+
drop_short_downstream: bool,
|
|
116
|
+
max_paths: int,
|
|
117
|
+
) -> List[List[Dict[str, Any]]]:
|
|
118
|
+
"""
|
|
119
|
+
Dedup + Filter + Sort + Cap
|
|
120
|
+
drop_short_downstream: اگر True باشد، مسیرهای با طول < 2 حذف میشوند.
|
|
121
|
+
"""
|
|
122
|
+
uniq: Dict[Tuple[str, ...], List[Dict[str, Any]]] = {}
|
|
123
|
+
|
|
124
|
+
for p in paths:
|
|
125
|
+
if drop_short_downstream and len(p) < 2:
|
|
126
|
+
continue
|
|
127
|
+
sig = _path_signature(p)
|
|
128
|
+
if not sig:
|
|
129
|
+
continue
|
|
130
|
+
old = uniq.get(sig)
|
|
131
|
+
if old is None or _informativeness_score(p) > _informativeness_score(old):
|
|
132
|
+
uniq[sig] = p
|
|
133
|
+
|
|
134
|
+
sorted_paths = sorted(uniq.values(), key=_informativeness_score, reverse=True)
|
|
135
|
+
return sorted_paths[:max_paths]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def find_critical_paths(
|
|
139
|
+
graph: nx.DiGraph,
|
|
140
|
+
*,
|
|
141
|
+
cutoff: int = _DEFAULT_CUTOFF,
|
|
142
|
+
max_paths_per_direction: int = _DEFAULT_MAX_PATHS_PER_DIR,
|
|
143
|
+
) -> Dict[str, Dict[str, List[List[Dict[str, Any]]]]]:
|
|
144
|
+
"""
|
|
145
|
+
مسیرهای بحرانی برای تستهای شکستخورده را استخراج میکند (upstream/downstream)
|
|
146
|
+
و خروجی enriched برمیگرداند. این نسخه:
|
|
147
|
+
- external ها را نگه میدارد و «origin» برایشان اضافه میکند.
|
|
148
|
+
- مسیرهای تکراری را حذف و بهترینها را نگه میدارد.
|
|
149
|
+
- cutoff و سقف مسیرها قابلپیکربندیاند.
|
|
150
|
+
"""
|
|
151
|
+
critical_paths: Dict[str, Dict[str, List[List[Dict[str, Any]]]]] = {}
|
|
152
|
+
|
|
153
|
+
failed_tests = [
|
|
154
|
+
node
|
|
155
|
+
for node, data in graph.nodes(data=True)
|
|
156
|
+
if data.get("is_test") and data.get("test_status") == "failed"
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
if not failed_tests:
|
|
160
|
+
return {}
|
|
161
|
+
|
|
162
|
+
for failed_node in failed_tests:
|
|
163
|
+
upstream_raw: List[List[Dict[str, Any]]] = []
|
|
164
|
+
downstream_raw: List[List[Dict[str, Any]]] = []
|
|
165
|
+
|
|
166
|
+
for node in graph.nodes:
|
|
167
|
+
if node == failed_node:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
for path in nx.all_simple_paths(
|
|
172
|
+
graph, source=node, target=failed_node, cutoff=cutoff
|
|
173
|
+
):
|
|
174
|
+
enriched = enrich_path_with_metadata(graph, path)
|
|
175
|
+
_augment_externals_with_origin(graph, enriched)
|
|
176
|
+
filtered = [step for step in enriched if _keep_step(step)]
|
|
177
|
+
if filtered:
|
|
178
|
+
upstream_raw.append(filtered)
|
|
179
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
for path in nx.all_simple_paths(
|
|
184
|
+
graph, source=failed_node, target=node, cutoff=cutoff
|
|
185
|
+
):
|
|
186
|
+
enriched = enrich_path_with_metadata(graph, path)
|
|
187
|
+
_augment_externals_with_origin(graph, enriched)
|
|
188
|
+
filtered = [step for step in enriched if _keep_step(step)]
|
|
189
|
+
if filtered:
|
|
190
|
+
downstream_raw.append(filtered)
|
|
191
|
+
except (nx.NetworkXNoPath, nx.NodeNotFound):
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
upstream = _postprocess_paths(
|
|
195
|
+
upstream_raw, drop_short_downstream=False, max_paths=max_paths_per_direction
|
|
196
|
+
)
|
|
197
|
+
downstream = _postprocess_paths(
|
|
198
|
+
downstream_raw,
|
|
199
|
+
drop_short_downstream=True,
|
|
200
|
+
max_paths=max_paths_per_direction,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
critical_paths[failed_node] = {"upstream": upstream, "downstream": downstream}
|
|
204
|
+
|
|
205
|
+
return critical_paths
|