quantrs2_tytan/testing_framework/
reports.rs

1//! Report generation for test results.
2//!
3//! This module provides functions for generating test reports in various formats
4//! including Text, JSON, HTML, Markdown, and CSV.
5
6use std::fmt::Write;
7use std::fs::File;
8use std::io::Write as IoWrite;
9
10use super::config::ReportFormat;
11use super::results::TestResults;
12use super::types::TestSuite;
13
14/// Generate report based on format
15pub fn generate_report(
16    format: &ReportFormat,
17    results: &TestResults,
18    suite: &TestSuite,
19) -> Result<String, String> {
20    match format {
21        ReportFormat::Text => generate_text_report(results),
22        ReportFormat::Json => generate_json_report(results),
23        ReportFormat::Html => generate_html_report(results),
24        ReportFormat::Markdown => generate_markdown_report(results),
25        ReportFormat::Csv => generate_csv_report(results),
26    }
27}
28
29/// Generate text report
30pub fn generate_text_report(results: &TestResults) -> Result<String, String> {
31    let mut report = String::new();
32
33    report.push_str("=== Quantum Optimization Test Report ===\n\n");
34
35    report.push_str(&format!("Total Tests: {}\n", results.summary.total_tests));
36    report.push_str(&format!("Passed: {}\n", results.summary.passed));
37    report.push_str(&format!("Failed: {}\n", results.summary.failed));
38    report.push_str(&format!(
39        "Success Rate: {:.2}%\n",
40        results.summary.success_rate * 100.0
41    ));
42    report.push_str(&format!(
43        "Average Runtime: {:?}\n\n",
44        results.summary.avg_runtime
45    ));
46
47    report.push_str("Quality Metrics:\n");
48    report.push_str(&format!(
49        "  Average Quality: {:.4}\n",
50        results.summary.quality_metrics.avg_quality
51    ));
52    report.push_str(&format!(
53        "  Best Quality: {:.4}\n",
54        results.summary.quality_metrics.best_quality
55    ));
56    report.push_str(&format!(
57        "  Worst Quality: {:.4}\n",
58        results.summary.quality_metrics.worst_quality
59    ));
60    report.push_str(&format!(
61        "  Std Dev: {:.4}\n",
62        results.summary.quality_metrics.std_dev
63    ));
64    report.push_str(&format!(
65        "  Constraint Satisfaction: {:.2}%\n\n",
66        results.summary.quality_metrics.constraint_satisfaction_rate * 100.0
67    ));
68
69    if !results.failures.is_empty() {
70        report.push_str("Failures:\n");
71        for failure in &results.failures {
72            report.push_str(&format!(
73                "  - {} ({:?}): {}\n",
74                failure.test_id, failure.failure_type, failure.message
75            ));
76        }
77    }
78
79    Ok(report)
80}
81
82/// Generate JSON report
83pub fn generate_json_report(results: &TestResults) -> Result<String, String> {
84    // Helper to convert fmt::Error to String
85    fn write_err(e: std::fmt::Error) -> String {
86        format!("JSON write error: {e}")
87    }
88
89    let mut json = String::new();
90
91    // Build JSON manually (avoiding serde dependency issues)
92    json.push_str("{\n");
93
94    // Summary section
95    json.push_str("  \"summary\": {\n");
96    writeln!(
97        &mut json,
98        "    \"total_tests\": {},",
99        results.summary.total_tests
100    )
101    .map_err(write_err)?;
102    writeln!(&mut json, "    \"passed\": {},", results.summary.passed).map_err(write_err)?;
103    writeln!(&mut json, "    \"failed\": {},", results.summary.failed).map_err(write_err)?;
104    writeln!(&mut json, "    \"skipped\": {},", results.summary.skipped).map_err(write_err)?;
105    writeln!(
106        &mut json,
107        "    \"success_rate\": {},",
108        results.summary.success_rate
109    )
110    .map_err(write_err)?;
111    writeln!(
112        &mut json,
113        "    \"avg_runtime_ms\": {}",
114        results.summary.avg_runtime.as_millis()
115    )
116    .map_err(write_err)?;
117    json.push_str("  },\n");
118
119    // Quality metrics
120    json.push_str("  \"quality_metrics\": {\n");
121    writeln!(
122        &mut json,
123        "    \"avg_quality\": {},",
124        results.summary.quality_metrics.avg_quality
125    )
126    .map_err(write_err)?;
127    writeln!(
128        &mut json,
129        "    \"best_quality\": {},",
130        results.summary.quality_metrics.best_quality
131    )
132    .map_err(write_err)?;
133    writeln!(
134        &mut json,
135        "    \"worst_quality\": {},",
136        results.summary.quality_metrics.worst_quality
137    )
138    .map_err(write_err)?;
139    writeln!(
140        &mut json,
141        "    \"std_dev\": {},",
142        results.summary.quality_metrics.std_dev
143    )
144    .map_err(write_err)?;
145    writeln!(
146        &mut json,
147        "    \"constraint_satisfaction_rate\": {}",
148        results.summary.quality_metrics.constraint_satisfaction_rate
149    )
150    .map_err(write_err)?;
151    json.push_str("  },\n");
152
153    // Performance data
154    json.push_str("  \"performance\": {\n");
155    writeln!(
156        &mut json,
157        "    \"total_time_ms\": {},",
158        results.performance.runtime_stats.total_time.as_millis()
159    )
160    .map_err(write_err)?;
161    writeln!(
162        &mut json,
163        "    \"solving_time_ms\": {},",
164        results.performance.runtime_stats.solving_time.as_millis()
165    )
166    .map_err(write_err)?;
167    writeln!(
168        &mut json,
169        "    \"validation_time_ms\": {}",
170        results
171            .performance
172            .runtime_stats
173            .validation_time
174            .as_millis()
175    )
176    .map_err(write_err)?;
177    json.push_str("  },\n");
178
179    // Test results
180    json.push_str("  \"test_results\": [\n");
181    for (i, result) in results.test_results.iter().enumerate() {
182        json.push_str("    {\n");
183        writeln!(&mut json, "      \"test_id\": \"{}\",", result.test_id).map_err(write_err)?;
184        writeln!(&mut json, "      \"sampler\": \"{}\",", result.sampler).map_err(write_err)?;
185        writeln!(
186            &mut json,
187            "      \"objective_value\": {},",
188            result.objective_value
189        )
190        .map_err(write_err)?;
191        writeln!(
192            &mut json,
193            "      \"constraints_satisfied\": {},",
194            result.constraints_satisfied
195        )
196        .map_err(write_err)?;
197        writeln!(
198            &mut json,
199            "      \"runtime_ms\": {},",
200            result.runtime.as_millis()
201        )
202        .map_err(write_err)?;
203        writeln!(
204            &mut json,
205            "      \"is_valid\": {}",
206            result.validation.is_valid
207        )
208        .map_err(write_err)?;
209        json.push_str("    }");
210        if i < results.test_results.len() - 1 {
211            json.push(',');
212        }
213        json.push('\n');
214    }
215    json.push_str("  ],\n");
216
217    // Failures
218    json.push_str("  \"failures\": [\n");
219    for (i, failure) in results.failures.iter().enumerate() {
220        json.push_str("    {\n");
221        writeln!(&mut json, "      \"test_id\": \"{}\",", failure.test_id).map_err(write_err)?;
222        writeln!(
223            &mut json,
224            "      \"failure_type\": \"{:?}\",",
225            failure.failure_type
226        )
227        .map_err(write_err)?;
228        writeln!(
229            &mut json,
230            "      \"message\": \"{}\"",
231            failure.message.replace('"', "\\\"")
232        )
233        .map_err(write_err)?;
234        json.push_str("    }");
235        if i < results.failures.len() - 1 {
236            json.push(',');
237        }
238        json.push('\n');
239    }
240    json.push_str("  ]\n");
241
242    json.push_str("}\n");
243
244    Ok(json)
245}
246
247/// Generate HTML report
248pub fn generate_html_report(results: &TestResults) -> Result<String, String> {
249    let mut html = String::new();
250
251    html.push_str("<!DOCTYPE html>\n<html>\n<head>\n");
252    html.push_str("<title>Quantum Optimization Test Report</title>\n");
253    html.push_str(
254        "<style>
255        body { font-family: Arial, sans-serif; margin: 20px; }
256        table { border-collapse: collapse; width: 100%; }
257        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
258        th { background-color: #f2f2f2; }
259        .passed { color: green; }
260        .failed { color: red; }
261    </style>\n",
262    );
263    html.push_str("</head>\n<body>\n");
264
265    html.push_str("<h1>Quantum Optimization Test Report</h1>\n");
266
267    // Summary
268    html.push_str("<h2>Summary</h2>\n");
269    html.push_str("<table>\n");
270    html.push_str(&format!(
271        "<tr><td>Total Tests</td><td>{}</td></tr>\n",
272        results.summary.total_tests
273    ));
274    html.push_str(&format!(
275        "<tr><td>Passed</td><td class='passed'>{}</td></tr>\n",
276        results.summary.passed
277    ));
278    html.push_str(&format!(
279        "<tr><td>Failed</td><td class='failed'>{}</td></tr>\n",
280        results.summary.failed
281    ));
282    html.push_str(&format!(
283        "<tr><td>Success Rate</td><td>{:.2}%</td></tr>\n",
284        results.summary.success_rate * 100.0
285    ));
286    html.push_str("</table>\n");
287
288    html.push_str("</body>\n</html>");
289
290    Ok(html)
291}
292
293/// Generate Markdown report
294pub fn generate_markdown_report(results: &TestResults) -> Result<String, String> {
295    let mut md = String::new();
296
297    md.push_str("# Quantum Optimization Test Report\n\n");
298
299    md.push_str("## Summary\n\n");
300    md.push_str("| Metric | Value |\n");
301    md.push_str("|--------|-------|\n");
302    md.push_str(&format!(
303        "| Total Tests | {} |\n",
304        results.summary.total_tests
305    ));
306    md.push_str(&format!("| Passed | {} |\n", results.summary.passed));
307    md.push_str(&format!("| Failed | {} |\n", results.summary.failed));
308    md.push_str(&format!(
309        "| Success Rate | {:.2}% |\n",
310        results.summary.success_rate * 100.0
311    ));
312    md.push_str(&format!(
313        "| Average Runtime | {:?} |\n\n",
314        results.summary.avg_runtime
315    ));
316
317    md.push_str("## Quality Metrics\n\n");
318    md.push_str(&format!(
319        "- **Average Quality**: {:.4}\n",
320        results.summary.quality_metrics.avg_quality
321    ));
322    md.push_str(&format!(
323        "- **Best Quality**: {:.4}\n",
324        results.summary.quality_metrics.best_quality
325    ));
326    md.push_str(&format!(
327        "- **Worst Quality**: {:.4}\n",
328        results.summary.quality_metrics.worst_quality
329    ));
330    md.push_str(&format!(
331        "- **Standard Deviation**: {:.4}\n",
332        results.summary.quality_metrics.std_dev
333    ));
334    md.push_str(&format!(
335        "- **Constraint Satisfaction Rate**: {:.2}%\n\n",
336        results.summary.quality_metrics.constraint_satisfaction_rate * 100.0
337    ));
338
339    Ok(md)
340}
341
342/// Generate CSV report
343pub fn generate_csv_report(results: &TestResults) -> Result<String, String> {
344    let mut csv = String::new();
345
346    csv.push_str("test_id,sampler,objective_value,constraints_satisfied,runtime_ms,valid\n");
347
348    for result in &results.test_results {
349        csv.push_str(&format!(
350            "{},{},{},{},{},{}\n",
351            result.test_id,
352            result.sampler,
353            result.objective_value,
354            result.constraints_satisfied,
355            result.runtime.as_millis(),
356            result.validation.is_valid
357        ));
358    }
359
360    Ok(csv)
361}
362
363/// Export results as CSV with problem type and size
364pub fn export_csv(results: &TestResults, suite: &TestSuite) -> Result<String, String> {
365    let mut csv = String::new();
366    csv.push_str("test_id,problem_type,size,sampler,objective_value,runtime_ms,constraints_satisfied,valid\n");
367
368    for result in &results.test_results {
369        // Find corresponding test case for additional info
370        if let Some(test_case) = suite.test_cases.iter().find(|tc| tc.id == result.test_id) {
371            csv.push_str(&format!(
372                "{},{:?},{},{},{},{},{},{}\n",
373                result.test_id,
374                test_case.problem_type,
375                test_case.size,
376                result.sampler,
377                result.objective_value,
378                result.runtime.as_millis(),
379                result.constraints_satisfied,
380                result.validation.is_valid
381            ));
382        }
383    }
384
385    Ok(csv)
386}
387
388/// Export results as XML
389pub fn export_xml(results: &TestResults) -> Result<String, String> {
390    let mut xml = String::new();
391    xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
392    xml.push_str("<test_results>\n");
393    xml.push_str(&format!(
394        "  <summary total=\"{}\" passed=\"{}\" failed=\"{}\" success_rate=\"{:.2}\"/>\n",
395        results.summary.total_tests,
396        results.summary.passed,
397        results.summary.failed,
398        results.summary.success_rate
399    ));
400
401    xml.push_str("  <tests>\n");
402    for result in &results.test_results {
403        xml.push_str(&format!(
404            "    <test id=\"{}\" sampler=\"{}\" objective=\"{}\" runtime_ms=\"{}\" valid=\"{}\"/>\n",
405            result.test_id,
406            result.sampler,
407            result.objective_value,
408            result.runtime.as_millis(),
409            result.validation.is_valid
410        ));
411    }
412    xml.push_str("  </tests>\n");
413    xml.push_str("</test_results>\n");
414
415    Ok(xml)
416}
417
418/// Save report to file
419pub fn save_report(report: &str, filename: &str) -> Result<(), String> {
420    let mut file = File::create(filename).map_err(|e| format!("Failed to create file: {e}"))?;
421    file.write_all(report.as_bytes())
422        .map_err(|e| format!("Failed to write file: {e}"))?;
423    Ok(())
424}