Skip to content

Commit 9e36220

Browse files
authored
Merge pull request #23 from anurag03/ansinha/ai_agent_debug
Ansinha/ai agent debug
2 parents 9cf673a + f3ddb36 commit 9e36220

File tree

6 files changed

+113
-16
lines changed

6 files changed

+113
-16
lines changed

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
12
pytest
3+
requests
4+
pyyaml

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="testcato",
8-
version="1.2.0",
8+
version="1.2.1",
99
packages=find_packages(),
1010
description="A package for categorizing test results.",
1111
long_description=long_description,

testcato/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# Package metadata for wheel
33
__title__ = "testcato"
4-
__version__ = "1.2.0"
4+
__version__ = "1.2.1"
55

66
# Automatically create config file if not present
77
import os

testcato/ai_agent.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import glob
3+
import xml.etree.ElementTree as ET
4+
import datetime
5+
import requests
6+
import yaml
7+
8+
def get_latest_xml(result_dir):
9+
files = glob.glob(os.path.join(result_dir, 'test_run_*.xml'))
10+
if not files:
11+
return None
12+
return max(files, key=os.path.getctime)
13+
14+
def load_agent_config(config_path):
15+
with open(config_path, 'r') as f:
16+
config = yaml.safe_load(f)
17+
if not isinstance(config, dict):
18+
return None
19+
default_agent = config.get('default', 'agent1')
20+
agent = config.get(default_agent, {})
21+
# Check if agent config is empty or missing required fields
22+
if not agent or not agent.get('api_key') or not agent.get('api_url'):
23+
return None
24+
return agent
25+
26+
def send_to_ai_agent(agent, test_name, traceback):
27+
api_url = agent.get('api_url')
28+
if not api_url:
29+
return "No api_url provided in agent config."
30+
headers = {
31+
'Authorization': f"Bearer {agent.get('api_key', '')}",
32+
'Content-Type': 'application/json'
33+
}
34+
# The payload structure should be defined by the user in their config and usage
35+
payload = {
36+
"test_name": test_name,
37+
"traceback": traceback
38+
}
39+
response = requests.post(api_url, json=payload, headers=headers)
40+
if response.ok:
41+
return response.text
42+
return "AI agent failed to respond."
43+
44+
def debug_latest_xml():
45+
result_dir = os.path.join(os.getcwd(), 'testcato_result')
46+
config_path = os.path.join(os.getcwd(), 'testcato_config.yaml')
47+
latest_xml = get_latest_xml(result_dir)
48+
if not latest_xml:
49+
print("No test_run XML file found.")
50+
return
51+
agent = load_agent_config(config_path)
52+
if not agent:
53+
# Print warning in yellow in pytest output or console
54+
YELLOW = '\033[33m'
55+
RESET = '\033[0m'
56+
warning_msg = f"{YELLOW}WARNING: TESTCATO: No valid AI agent config found. Debugging is disabled.{RESET}"
57+
import sys
58+
tr = getattr(sys, '_pytest_terminalreporter', None)
59+
if tr:
60+
tr.write_line(warning_msg)
61+
else:
62+
print(warning_msg)
63+
return
64+
import json
65+
tree = ET.parse(latest_xml)
66+
root = tree.getroot()
67+
lines = []
68+
for test_elem in root.findall('Test'):
69+
name = test_elem.get('name')
70+
tb_elem = test_elem.find('Traceback')
71+
debug_result = None
72+
if tb_elem is not None:
73+
debug_result = send_to_ai_agent(agent, name, tb_elem.text)
74+
line = {
75+
"name": name,
76+
"traceback": tb_elem.text if tb_elem is not None else None,
77+
"debug": debug_result
78+
}
79+
lines.append(line)
80+
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
81+
debug_jsonl_path = os.path.join(result_dir, f'test_debugg_{timestamp}.jsonl')
82+
with open(debug_jsonl_path, 'w', encoding='utf-8') as f:
83+
for line in lines:
84+
f.write(json.dumps(line, ensure_ascii=False) + '\n')
85+
print(f"Debug results saved to {debug_jsonl_path}")

testcato/pytest_testcato.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import pytest
23
import os
34
import datetime
@@ -20,6 +21,9 @@ def pytest_configure(config):
2021
config.option.verbose = 3
2122

2223
def pytest_terminal_summary(terminalreporter, exitstatus, config):
24+
def remove_ansi_codes(text):
25+
ansi_escape = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]')
26+
return ansi_escape.sub('', text)
2327
if config.getoption("--testcato"):
2428
results = []
2529
tracebacks = []
@@ -28,25 +32,22 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
2832
for report in terminalreporter.getreports("failed"):
2933
results.append({'name': report.nodeid, 'status': 'failed'})
3034
if hasattr(report, 'longrepr') and report.longrepr:
31-
tb = str(report.longrepr)
35+
tb = remove_ansi_codes(str(report.longrepr))
3236
tracebacks.append({'name': report.nodeid, 'traceback': tb})
3337
for report in terminalreporter.getreports("skipped"):
3438
results.append({'name': report.nodeid, 'status': 'skipped'})
3539

36-
# Save tracebacks to XML
40+
# Save tracebacks to JSON Lines
3741
if tracebacks:
42+
import json
3843
result_dir = os.path.join(os.getcwd(), 'testcato_result')
3944
os.makedirs(result_dir, exist_ok=True)
40-
root = ET.Element('TestTracebacks')
41-
for tb in tracebacks:
42-
test_elem = ET.SubElement(root, 'Test', name=tb['name'])
43-
tb_elem = ET.SubElement(test_elem, 'Traceback')
44-
tb_elem.text = tb['traceback']
45-
tree = ET.ElementTree(root)
4645
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
47-
xml_path = os.path.join(result_dir, f'test_run_{timestamp}.xml')
48-
tree.write(xml_path, encoding='utf-8', xml_declaration=True)
49-
terminalreporter.write_line(f"Tracebacks saved to {xml_path}")
46+
jsonl_path = os.path.join(result_dir, f'test_run_{timestamp}.jsonl')
47+
with open(jsonl_path, 'w', encoding='utf-8') as f:
48+
for tb in tracebacks:
49+
f.write(json.dumps(tb, ensure_ascii=False) + '\n')
50+
terminalreporter.write_line(f"Tracebacks saved to {jsonl_path}")
5051

5152
categorizer = TestCategorizer()
5253
categories = categorizer.categorize(results)
@@ -55,3 +56,10 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
5556
terminalreporter.write_line(f"{category}:")
5657
for test in tests:
5758
terminalreporter.write_line(f" {test}")
59+
60+
# Automatically send latest XML to AI agent and save debug file
61+
try:
62+
from .ai_agent import debug_latest_xml
63+
debug_latest_xml()
64+
except Exception as e:
65+
terminalreporter.write_line(f"Error sending XML to AI agent: {e}")

testcato_config.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
#
88
# agent1:
99
# type: openai
10-
# model: gpt-4
11-
# api_key: YOUR_OPENAI_API_KEY
10+
# model: gpt-4.1-mini
11+
# api_key: <YOUR_OPENAI_API_KEY>
12+
# api_url: https://api.openai.com/v1/chat/completions
1213
#
1314
# Add more agents below as agent2, agent3, etc.
1415
# agent2:
1516
# type: azure
1617
# model: gpt-4
1718
# api_key: YOUR_AZURE_API_KEY
18-
19+
# api_url: https://your.azure.endpoint/api
1920

0 commit comments

Comments
 (0)