Skip to content

Commit b0ab5fa

Browse files
committed
feat: Improve mutation coverage report with detailed information
1 parent 0c03051 commit b0ab5fa

File tree

2 files changed

+429
-100
lines changed

2 files changed

+429
-100
lines changed

β€Žsrc/mutahunter/core/report.py

Lines changed: 143 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -22,133 +22,189 @@ class MutantReport:
2222

2323
def __init__(self, config: MutahunterConfig) -> None:
2424
self.config = config
25+
self.log_file = "logs/_latest/coverage.txt"
2526

2627
def generate_report(
27-
self,
28-
mutants: List[Mutant],
29-
total_cost: float,
30-
line_rate: float,
28+
self, mutants: List[Mutant], total_cost: float, line_rate: float
3129
) -> None:
3230
"""
3331
Generates a comprehensive mutation testing report.
3432
3533
Args:
3634
mutants (List[Mutant]): List of mutants generated during mutation testing.
35+
total_cost (float): The total cost of mutation testing.
36+
line_rate (float): The line coverage rate.
3737
"""
38-
mutants = [asdict(mutant) for mutant in mutants]
39-
self.save_report("logs/_latest/mutants.json", mutants)
38+
mutant_dicts = [asdict(mutant) for mutant in mutants]
39+
self.save_report("logs/_latest/mutants.json", mutant_dicts)
4040
print(MUTAHUNTER_ASCII)
41-
self.generate_mutant_report(mutants, total_cost, line_rate)
42-
self.generate_mutant_report_detail(mutants)
43-
44-
def generate_mutant_report(
45-
self,
46-
mutants: List[Mutant],
47-
total_cost: float,
48-
line_rate: float,
41+
self._generate_summary_report(mutant_dicts, total_cost, line_rate)
42+
self._generate_detailed_report(mutant_dicts)
43+
44+
def _generate_summary_report(
45+
self, mutants: List[dict], total_cost: float, line_rate: float
4946
) -> None:
50-
killed_mutants = [mutant for mutant in mutants if mutant["status"] == "KILLED"]
51-
survived_mutants = [
52-
mutant for mutant in mutants if mutant["status"] == "SURVIVED"
53-
]
54-
timeout_mutants = [
55-
mutant for mutant in mutants if mutant["status"] == "TIMEOUT"
56-
]
57-
compile_error_mutants = [
58-
mutant for mutant in mutants if mutant["status"] == "COMPILE_ERROR"
59-
]
60-
valid_mutants = [
61-
m for m in mutants if m["status"] not in ["COMPILE_ERROR", "TIMEOUT"]
62-
]
47+
"""
48+
Generates a summary mutation testing report.
6349
64-
total_mutation_coverage = (
65-
f"{len(killed_mutants) / len(valid_mutants) * 100:.2f}%"
66-
if valid_mutants
50+
Args:
51+
mutants (List[dict]): List of mutant dictionaries.
52+
total_cost (float): The total cost of mutation testing.
53+
line_rate (float): The line coverage rate.
54+
"""
55+
report_data = self._compute_summary_data(mutants)
56+
summary_text = self._format_summary(report_data, total_cost, line_rate)
57+
self._log_and_write(summary_text)
58+
59+
def _compute_summary_data(self, mutants: List[dict]) -> dict:
60+
"""
61+
Computes summary data from the list of mutants.
62+
63+
Args:
64+
mutants (List[dict]): List of mutant dictionaries.
65+
66+
Returns:
67+
dict: Summary data including counts of different mutant statuses.
68+
"""
69+
data = {
70+
"killed_mutants": len([m for m in mutants if m["status"] == "KILLED"]),
71+
"survived_mutants": len([m for m in mutants if m["status"] == "SURVIVED"]),
72+
"timeout_mutants": len([m for m in mutants if m["status"] == "TIMEOUT"]),
73+
"compile_error_mutants": len(
74+
[m for m in mutants if m["status"] == "COMPILE_ERROR"]
75+
),
76+
}
77+
data["total_mutants"] = len(mutants)
78+
data["valid_mutants"] = (
79+
data["total_mutants"]
80+
- data["compile_error_mutants"]
81+
- data["timeout_mutants"]
82+
)
83+
data["mutation_coverage"] = (
84+
f"{data['killed_mutants'] / data['valid_mutants'] * 100:.2f}%"
85+
if data["valid_mutants"]
6786
else "0.00%"
6887
)
69-
line_coverage = f"{line_rate * 100:.2f}%"
88+
return data
89+
90+
def _format_summary(self, data: dict, total_cost: float, line_rate: float) -> str:
91+
"""
92+
Formats the summary data into a string.
7093
71-
logger.info("πŸ“Š Line Coverage: %s πŸ“Š", line_coverage)
72-
logger.info("🎯 Mutation Coverage: %s 🎯", total_mutation_coverage)
73-
logger.info("🦠 Total Mutants: %d 🦠", len(mutants))
74-
logger.info("πŸ›‘οΈ Survived Mutants: %d πŸ›‘οΈ", len(survived_mutants))
75-
logger.info("πŸ—‘οΈ Killed Mutants: %d πŸ—‘οΈ", len(killed_mutants))
76-
logger.info("πŸ•’ Timeout Mutants: %d πŸ•’", len(timeout_mutants))
77-
logger.info("πŸ”₯ Compile Error Mutants: %d πŸ”₯", len(compile_error_mutants))
94+
Args:
95+
data (dict): Summary data including counts of different mutant statuses.
96+
total_cost (float): The total cost of mutation testing.
97+
line_rate (float): The line coverage rate.
98+
99+
Returns:
100+
str: Formatted summary report.
101+
"""
102+
line_coverage = f"{line_rate * 100:.2f}%"
103+
summary = [
104+
"Mutation Coverage:",
105+
f"πŸ“Š Line Coverage: {line_coverage} πŸ“Š",
106+
f"🎯 Mutation Coverage: {data['mutation_coverage']} 🎯",
107+
f"🦠 Total Mutants: {data['total_mutants']} 🦠",
108+
f"πŸ›‘οΈ Survived Mutants: {data['survived_mutants']} πŸ›‘οΈ",
109+
f"πŸ—‘οΈ Killed Mutants: {data['killed_mutants']} πŸ—‘οΈ",
110+
f"πŸ•’ Timeout Mutants: {data['timeout_mutants']} πŸ•’",
111+
f"πŸ”₯ Compile Error Mutants: {data['compile_error_mutants']} πŸ”₯",
112+
]
78113
if self.config.extreme:
79-
logger.info("πŸ’° No Cost for extreme mutation testing πŸ’°")
114+
summary.append("πŸ’° No Cost for extreme mutation testing πŸ’°")
80115
else:
81-
logger.info("πŸ’° Expected Cost: $%.5f USD πŸ’°", total_cost)
82-
83-
with open("logs/_latest/coverage.txt", "a") as file:
84-
file.write("Mutation Coverage:\n")
85-
file.write(f"πŸ“Š Line Coverage: {line_coverage} πŸ“Š\n")
86-
file.write(f"🎯 Mutation Coverage: {total_mutation_coverage} 🎯\n")
87-
file.write(f"🦠 Total Mutants: {len(mutants)} 🦠\n")
88-
file.write(f"πŸ›‘οΈ Survived Mutants: {len(survived_mutants)} πŸ›‘οΈ\n")
89-
file.write(f"πŸ—‘οΈ Killed Mutants: {len(killed_mutants)} πŸ—‘οΈ\n")
90-
file.write(f"πŸ•’ Timeout Mutants: {len(timeout_mutants)} πŸ•’\n")
91-
file.write(f"πŸ”₯ Compile Error Mutants: {len(compile_error_mutants)} πŸ”₯\n")
92-
if self.config.extreme:
93-
file.write("πŸ’° No Cost for extreme mutation testing πŸ’°\n")
94-
else:
95-
file.write("πŸ’° Expected Cost: $%.5f USD πŸ’°\n", total_cost)
96-
97-
def generate_mutant_report_detail(self, mutants: List[Mutant]) -> None:
116+
summary.append(f"πŸ’° Expected Cost: ${total_cost:.5f} USD πŸ’°")
117+
return "\n".join(summary)
118+
119+
def _generate_detailed_report(self, mutants: List[dict]) -> None:
98120
"""
99121
Generates a detailed mutation testing report per source file.
100122
101123
Args:
102-
mutants (List[Mutant]): List of mutants generated during mutation testing.
124+
mutants (List[dict]): List of mutant dictionaries.
125+
"""
126+
report_detail = self._compute_detailed_data(mutants)
127+
detailed_text = self._format_detailed_report(report_detail)
128+
self._log_and_write("\nDetailed Mutation Coverage:\n" + detailed_text)
129+
130+
def _compute_detailed_data(self, mutants: List[dict]) -> dict:
131+
"""
132+
Computes detailed data for each source file from the list of mutants.
133+
134+
Args:
135+
mutants (List[dict]): List of mutant dictionaries.
136+
137+
Returns:
138+
dict: Detailed data including counts of different mutant statuses per source file.
103139
"""
104-
report_detail = {}
140+
detail = {}
105141
for mutant in mutants:
106142
source_path = mutant["source_path"]
107-
if source_path not in report_detail:
108-
report_detail[source_path] = {
143+
if source_path not in detail:
144+
detail[source_path] = {
109145
"total_mutants": 0,
110146
"killed_mutants": 0,
111147
"survived_mutants": 0,
112148
"timeout_mutants": 0,
113149
"compile_error_mutants": 0,
114150
}
115-
report_detail[source_path]["total_mutants"] += 1
151+
detail[source_path]["total_mutants"] += 1
116152
if mutant["status"] == "KILLED":
117-
report_detail[source_path]["killed_mutants"] += 1
153+
detail[source_path]["killed_mutants"] += 1
118154
elif mutant["status"] == "SURVIVED":
119-
report_detail[source_path]["survived_mutants"] += 1
155+
detail[source_path]["survived_mutants"] += 1
120156
elif mutant["status"] == "TIMEOUT":
121-
report_detail[source_path]["timeout_mutants"] += 1
122-
157+
detail[source_path]["timeout_mutants"] += 1
123158
elif mutant["status"] == "COMPILE_ERROR":
124-
report_detail[source_path]["compile_error_mutants"] += 1
159+
detail[source_path]["compile_error_mutants"] += 1
125160

126-
for source_path, detail in report_detail.items():
161+
for source_path, data in detail.items():
127162
valid_mutants = (
128-
detail["total_mutants"]
129-
- detail["compile_error_mutants"]
130-
- detail["timeout_mutants"]
163+
data["total_mutants"]
164+
- data["compile_error_mutants"]
165+
- data["timeout_mutants"]
131166
)
132-
mutation_coverage = (
133-
f"{detail['killed_mutants'] / valid_mutants * 100:.2f}%"
167+
data["mutation_coverage"] = (
168+
f"{data['killed_mutants'] / valid_mutants * 100:.2f}%"
134169
if valid_mutants
135170
else "0.00%"
136171
)
137-
detail["mutation_coverage"] = mutation_coverage
138-
139-
with open("logs/_latest/coverage.txt", "a") as file:
140-
file.write("\nDetailed Mutation Coverage:\n")
141-
for source_path, detail in report_detail.items():
142-
file.write(f"πŸ“‚ Source File: {source_path} πŸ“‚\n")
143-
file.write(f"🎯 Mutation Coverage: {detail['mutation_coverage']}🎯\n")
144-
file.write(f"🦠 Total Mutants: {detail['total_mutants']} 🦠\n")
145-
file.write(f"πŸ›‘οΈ Survived Mutants: {detail['survived_mutants']} πŸ›‘οΈ\n")
146-
file.write(f"πŸ—‘οΈ Killed Mutants: {detail['killed_mutants']} πŸ—‘οΈ\n")
147-
file.write(f"πŸ•’ Timeout Mutants: {detail['timeout_mutants']} πŸ•’\n")
148-
file.write(
149-
f"πŸ”₯ Compile Error Mutants: {detail['compile_error_mutants']}πŸ”₯\n"
150-
)
151-
file.write("\n")
172+
return detail
173+
174+
def _format_detailed_report(self, report_detail: dict) -> str:
175+
"""
176+
Formats the detailed report data into a string.
177+
178+
Args:
179+
report_detail (dict): Detailed data including counts of different mutant statuses per source file.
180+
181+
Returns:
182+
str: Formatted detailed report.
183+
"""
184+
details = []
185+
for source_path, detail in report_detail.items():
186+
details.append(f"πŸ“‚ Source File: {source_path} πŸ“‚")
187+
details.append(f"🎯 Mutation Coverage: {detail['mutation_coverage']} 🎯")
188+
details.append(f"🦠 Total Mutants: {detail['total_mutants']} 🦠")
189+
details.append(f"πŸ›‘οΈ Survived Mutants: {detail['survived_mutants']} πŸ›‘οΈ")
190+
details.append(f"πŸ—‘οΈ Killed Mutants: {detail['killed_mutants']} πŸ—‘οΈ")
191+
details.append(f"πŸ•’ Timeout Mutants: {detail['timeout_mutants']} πŸ•’")
192+
details.append(
193+
f"πŸ”₯ Compile Error Mutants: {detail['compile_error_mutants']} πŸ”₯"
194+
)
195+
details.append("\n")
196+
return "\n".join(details)
197+
198+
def _log_and_write(self, text: str) -> None:
199+
"""
200+
Logs and writes the given text to a file.
201+
202+
Args:
203+
text (str): The text to log and write.
204+
"""
205+
logger.info(text)
206+
with open(self.log_file, "a") as file:
207+
file.write(text + "\n")
152208

153209
def save_report(self, filepath: str, data: Any) -> None:
154210
"""

0 commit comments

Comments
Β (0)