Skip to main content

simular/edd/
report.rs

1//! Report generation for EDD experiments.
2//!
3//! This module provides functionality to generate various report formats
4//! from experiment results:
5//! - Markdown reports
6//! - JSON reports
7//! - HTML reports (planned)
8//!
9//! # Report Contents
10//!
11//! Generated reports include:
12//! - Experiment metadata
13//! - Verification test results
14//! - Falsification criteria results
15//! - Reproducibility verification
16//! - Execution metrics
17//!
18//! # Example
19//!
20//! ```ignore
21//! let result = runner.run("experiment.yaml")?;
22//! let report = ReportGenerator::markdown(&result)?;
23//! std::fs::write("report.md", report)?;
24//! ```
25
26use super::runner::{
27    EmcComplianceReport, ExecutionMetrics, ExperimentResult, FalsificationSummary,
28    ReproducibilitySummary, VerificationSummary,
29};
30use std::fmt::Write;
31
32/// Report format options.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ReportFormat {
35    /// Markdown format
36    Markdown,
37    /// JSON format
38    Json,
39    /// Plain text format
40    Text,
41}
42
43/// Report generator for experiment results.
44pub struct ReportGenerator;
45
46impl ReportGenerator {
47    /// Generate a markdown report from experiment results.
48    ///
49    /// # Errors
50    /// Returns error if formatting fails.
51    pub fn markdown(result: &ExperimentResult) -> Result<String, String> {
52        let mut report = String::new();
53
54        // Header
55        writeln!(report, "# Experiment Report: {}", result.name).map_err(|e| e.to_string())?;
56        writeln!(report).map_err(|e| e.to_string())?;
57
58        // Metadata
59        writeln!(report, "## Metadata").map_err(|e| e.to_string())?;
60        writeln!(report).map_err(|e| e.to_string())?;
61        writeln!(report, "| Field | Value |").map_err(|e| e.to_string())?;
62        writeln!(report, "|-------|-------|").map_err(|e| e.to_string())?;
63        writeln!(report, "| Experiment ID | {} |", result.experiment_id)
64            .map_err(|e| e.to_string())?;
65        writeln!(report, "| Seed | {} |", result.seed).map_err(|e| e.to_string())?;
66        writeln!(
67            report,
68            "| Status | {} |",
69            if result.passed { "PASSED" } else { "FAILED" }
70        )
71        .map_err(|e| e.to_string())?;
72        writeln!(report).map_err(|e| e.to_string())?;
73
74        // Verification Tests
75        writeln!(report, "## Verification Tests").map_err(|e| e.to_string())?;
76        writeln!(report).map_err(|e| e.to_string())?;
77        Self::write_verification_summary(&mut report, &result.verification)?;
78
79        // Falsification Criteria
80        writeln!(report, "## Falsification Criteria").map_err(|e| e.to_string())?;
81        writeln!(report).map_err(|e| e.to_string())?;
82        Self::write_falsification_summary(&mut report, &result.falsification)?;
83
84        // Reproducibility
85        if let Some(ref repro) = result.reproducibility {
86            writeln!(report, "## Reproducibility").map_err(|e| e.to_string())?;
87            writeln!(report).map_err(|e| e.to_string())?;
88            Self::write_reproducibility_summary(&mut report, repro)?;
89        }
90
91        // Execution Metrics
92        writeln!(report, "## Execution Metrics").map_err(|e| e.to_string())?;
93        writeln!(report).map_err(|e| e.to_string())?;
94        Self::write_execution_metrics(&mut report, &result.execution)?;
95
96        // Warnings
97        if !result.warnings.is_empty() {
98            writeln!(report, "## Warnings").map_err(|e| e.to_string())?;
99            writeln!(report).map_err(|e| e.to_string())?;
100            for warning in &result.warnings {
101                writeln!(report, "- {warning}").map_err(|e| e.to_string())?;
102            }
103            writeln!(report).map_err(|e| e.to_string())?;
104        }
105
106        // Footer
107        writeln!(report, "---").map_err(|e| e.to_string())?;
108        writeln!(
109            report,
110            "*Generated by simular v{}*",
111            env!("CARGO_PKG_VERSION")
112        )
113        .map_err(|e| e.to_string())?;
114
115        Ok(report)
116    }
117
118    fn write_verification_summary(
119        report: &mut String,
120        summary: &VerificationSummary,
121    ) -> Result<(), String> {
122        writeln!(
123            report,
124            "**Summary:** {} passed, {} failed out of {} total",
125            summary.passed, summary.failed, summary.total
126        )
127        .map_err(|e| e.to_string())?;
128        writeln!(report).map_err(|e| e.to_string())?;
129
130        if !summary.tests.is_empty() {
131            writeln!(
132                report,
133                "| ID | Name | Status | Expected | Actual | Tolerance |"
134            )
135            .map_err(|e| e.to_string())?;
136            writeln!(report, "|---|---|---|---|---|---|").map_err(|e| e.to_string())?;
137
138            for test in &summary.tests {
139                let status = if test.passed { "PASS" } else { "FAIL" };
140                let expected = test
141                    .expected
142                    .map_or_else(|| "-".to_string(), |v| format!("{v:.6}"));
143                let actual = test
144                    .actual
145                    .map_or_else(|| "-".to_string(), |v| format!("{v:.6}"));
146                let tolerance = test
147                    .tolerance
148                    .map_or_else(|| "-".to_string(), |v| format!("{v:.2e}"));
149
150                writeln!(
151                    report,
152                    "| {} | {} | {} | {} | {} | {} |",
153                    test.id, test.name, status, expected, actual, tolerance
154                )
155                .map_err(|e| e.to_string())?;
156            }
157            writeln!(report).map_err(|e| e.to_string())?;
158        }
159
160        Ok(())
161    }
162
163    fn write_falsification_summary(
164        report: &mut String,
165        summary: &FalsificationSummary,
166    ) -> Result<(), String> {
167        writeln!(
168            report,
169            "**Summary:** {} passed, {} triggered out of {} total",
170            summary.passed, summary.triggered, summary.total
171        )
172        .map_err(|e| e.to_string())?;
173
174        if summary.jidoka_triggered {
175            writeln!(report, "\n**Jidoka:** TRIGGERED (stop-on-error)")
176                .map_err(|e| e.to_string())?;
177        }
178        writeln!(report).map_err(|e| e.to_string())?;
179
180        if !summary.criteria.is_empty() {
181            writeln!(report, "| ID | Name | Triggered | Severity | Condition |")
182                .map_err(|e| e.to_string())?;
183            writeln!(report, "|---|---|---|---|---|").map_err(|e| e.to_string())?;
184
185            for crit in &summary.criteria {
186                let triggered = if crit.triggered { "YES" } else { "NO" };
187                writeln!(
188                    report,
189                    "| {} | {} | {} | {} | {} |",
190                    crit.id, crit.name, triggered, crit.severity, crit.condition
191                )
192                .map_err(|e| e.to_string())?;
193            }
194            writeln!(report).map_err(|e| e.to_string())?;
195        }
196
197        Ok(())
198    }
199
200    fn write_reproducibility_summary(
201        report: &mut String,
202        summary: &ReproducibilitySummary,
203    ) -> Result<(), String> {
204        writeln!(report, "| Property | Value |").map_err(|e| e.to_string())?;
205        writeln!(report, "|---|---|").map_err(|e| e.to_string())?;
206        writeln!(
207            report,
208            "| Passed | {} |",
209            if summary.passed { "YES" } else { "NO" }
210        )
211        .map_err(|e| e.to_string())?;
212        writeln!(report, "| Runs | {} |", summary.runs).map_err(|e| e.to_string())?;
213        writeln!(report, "| Identical | {} |", summary.identical).map_err(|e| e.to_string())?;
214        writeln!(report, "| Platform | {} |", summary.platform).map_err(|e| e.to_string())?;
215        writeln!(report, "| Reference Hash | `{}` |", summary.reference_hash)
216            .map_err(|e| e.to_string())?;
217        writeln!(report).map_err(|e| e.to_string())?;
218
219        Ok(())
220    }
221
222    fn write_execution_metrics(
223        report: &mut String,
224        metrics: &ExecutionMetrics,
225    ) -> Result<(), String> {
226        writeln!(report, "| Metric | Value |").map_err(|e| e.to_string())?;
227        writeln!(report, "|---|---|").map_err(|e| e.to_string())?;
228        writeln!(report, "| Duration | {} ms |", metrics.duration_ms).map_err(|e| e.to_string())?;
229        writeln!(report, "| Steps | {} |", metrics.steps).map_err(|e| e.to_string())?;
230        writeln!(report, "| Replications | {} |", metrics.replications)
231            .map_err(|e| e.to_string())?;
232
233        if let Some(mem) = metrics.peak_memory_bytes {
234            writeln!(report, "| Peak Memory | {mem} bytes |").map_err(|e| e.to_string())?;
235        }
236        writeln!(report).map_err(|e| e.to_string())?;
237
238        Ok(())
239    }
240
241    /// Generate a JSON report from experiment results.
242    ///
243    /// # Errors
244    /// Returns error if serialization fails.
245    pub fn json(result: &ExperimentResult) -> Result<String, String> {
246        serde_json::to_string_pretty(result)
247            .map_err(|e| format!("Failed to serialize to JSON: {e}"))
248    }
249
250    /// Generate a plain text report from experiment results.
251    ///
252    /// # Errors
253    /// Returns error if formatting fails.
254    pub fn text(result: &ExperimentResult) -> Result<String, String> {
255        let mut report = String::new();
256
257        let status = if result.passed { "PASSED" } else { "FAILED" };
258        writeln!(report, "EXPERIMENT REPORT: {}", result.name).map_err(|e| e.to_string())?;
259        writeln!(
260            report,
261            "================================================================================"
262        )
263        .map_err(|e| e.to_string())?;
264        writeln!(report).map_err(|e| e.to_string())?;
265        writeln!(report, "ID:     {}", result.experiment_id).map_err(|e| e.to_string())?;
266        writeln!(report, "Seed:   {}", result.seed).map_err(|e| e.to_string())?;
267        writeln!(report, "Status: {status}").map_err(|e| e.to_string())?;
268        writeln!(report).map_err(|e| e.to_string())?;
269
270        writeln!(report, "VERIFICATION TESTS").map_err(|e| e.to_string())?;
271        writeln!(
272            report,
273            "--------------------------------------------------------------------------------"
274        )
275        .map_err(|e| e.to_string())?;
276        writeln!(report, "Total:  {}", result.verification.total).map_err(|e| e.to_string())?;
277        writeln!(report, "Passed: {}", result.verification.passed).map_err(|e| e.to_string())?;
278        writeln!(report, "Failed: {}", result.verification.failed).map_err(|e| e.to_string())?;
279        writeln!(report).map_err(|e| e.to_string())?;
280
281        writeln!(report, "FALSIFICATION CRITERIA").map_err(|e| e.to_string())?;
282        writeln!(
283            report,
284            "--------------------------------------------------------------------------------"
285        )
286        .map_err(|e| e.to_string())?;
287        writeln!(report, "Total:     {}", result.falsification.total).map_err(|e| e.to_string())?;
288        writeln!(report, "Passed:    {}", result.falsification.passed)
289            .map_err(|e| e.to_string())?;
290        writeln!(report, "Triggered: {}", result.falsification.triggered)
291            .map_err(|e| e.to_string())?;
292        writeln!(
293            report,
294            "Jidoka:    {}",
295            if result.falsification.jidoka_triggered {
296                "TRIGGERED"
297            } else {
298                "OK"
299            }
300        )
301        .map_err(|e| e.to_string())?;
302        writeln!(report).map_err(|e| e.to_string())?;
303
304        writeln!(report, "EXECUTION").map_err(|e| e.to_string())?;
305        writeln!(
306            report,
307            "--------------------------------------------------------------------------------"
308        )
309        .map_err(|e| e.to_string())?;
310        writeln!(report, "Duration:     {} ms", result.execution.duration_ms)
311            .map_err(|e| e.to_string())?;
312        writeln!(report, "Replications: {}", result.execution.replications)
313            .map_err(|e| e.to_string())?;
314        writeln!(report).map_err(|e| e.to_string())?;
315
316        writeln!(
317            report,
318            "================================================================================"
319        )
320        .map_err(|e| e.to_string())?;
321        writeln!(report, "RESULT: {status}").map_err(|e| e.to_string())?;
322
323        Ok(report)
324    }
325
326    /// Generate a markdown report for EMC compliance.
327    ///
328    /// # Errors
329    /// Returns error if formatting fails.
330    pub fn emc_compliance_markdown(report: &EmcComplianceReport) -> Result<String, String> {
331        let mut output = String::new();
332
333        writeln!(
334            output,
335            "# EMC Compliance Report: {}",
336            report.experiment_name
337        )
338        .map_err(|e| e.to_string())?;
339        writeln!(output).map_err(|e| e.to_string())?;
340
341        let status = if report.passed {
342            "COMPLIANT"
343        } else {
344            "NON-COMPLIANT"
345        };
346        writeln!(output, "**Status:** {status}").map_err(|e| e.to_string())?;
347        writeln!(output).map_err(|e| e.to_string())?;
348
349        // EDD Compliance Checklist
350        writeln!(output, "## EDD Compliance Checklist").map_err(|e| e.to_string())?;
351        writeln!(output).map_err(|e| e.to_string())?;
352        writeln!(output, "| Requirement | Status |").map_err(|e| e.to_string())?;
353        writeln!(output, "|---|---|").map_err(|e| e.to_string())?;
354
355        let check = |b: bool| if b { "PASS" } else { "FAIL" };
356        writeln!(
357            output,
358            "| EDD-01: EMC Reference | {} |",
359            check(report.edd_compliance.edd_01_emc_reference)
360        )
361        .map_err(|e| e.to_string())?;
362        writeln!(
363            output,
364            "| EDD-02: Verification Tests | {} |",
365            check(report.edd_compliance.edd_02_verification_tests)
366        )
367        .map_err(|e| e.to_string())?;
368        writeln!(
369            output,
370            "| EDD-03: Seed Specified | {} |",
371            check(report.edd_compliance.edd_03_seed_specified)
372        )
373        .map_err(|e| e.to_string())?;
374        writeln!(
375            output,
376            "| EDD-04: Falsification Criteria | {} |",
377            check(report.edd_compliance.edd_04_falsification_criteria)
378        )
379        .map_err(|e| e.to_string())?;
380        writeln!(
381            output,
382            "| EDD-05: Hypothesis (Optional) | {} |",
383            check(report.edd_compliance.edd_05_hypothesis)
384        )
385        .map_err(|e| e.to_string())?;
386        writeln!(output).map_err(|e| e.to_string())?;
387
388        // Errors
389        if !report.schema_errors.is_empty() || !report.emc_errors.is_empty() {
390            writeln!(output, "## Errors").map_err(|e| e.to_string())?;
391            writeln!(output).map_err(|e| e.to_string())?;
392
393            for err in &report.schema_errors {
394                writeln!(output, "- Schema: {err}").map_err(|e| e.to_string())?;
395            }
396            for err in &report.emc_errors {
397                writeln!(output, "- EMC: {err}").map_err(|e| e.to_string())?;
398            }
399            writeln!(output).map_err(|e| e.to_string())?;
400        }
401
402        // Warnings
403        if !report.warnings.is_empty() {
404            writeln!(output, "## Warnings").map_err(|e| e.to_string())?;
405            writeln!(output).map_err(|e| e.to_string())?;
406            for warning in &report.warnings {
407                writeln!(output, "- {warning}").map_err(|e| e.to_string())?;
408            }
409            writeln!(output).map_err(|e| e.to_string())?;
410        }
411
412        // Footer
413        writeln!(output, "---").map_err(|e| e.to_string())?;
414        writeln!(
415            output,
416            "*Generated by simular v{}*",
417            env!("CARGO_PKG_VERSION")
418        )
419        .map_err(|e| e.to_string())?;
420
421        Ok(output)
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use crate::edd::runner::{
429        EddComplianceChecklist, FalsificationCriterionResult, VerificationTestSummary,
430    };
431
432    fn sample_result() -> ExperimentResult {
433        ExperimentResult {
434            name: "Test Experiment".to_string(),
435            experiment_id: "EXP-001".to_string(),
436            seed: 42,
437            passed: true,
438            verification: VerificationSummary {
439                total: 2,
440                passed: 2,
441                failed: 0,
442                tests: vec![VerificationTestSummary {
443                    id: "VT-001".to_string(),
444                    name: "Basic test".to_string(),
445                    passed: true,
446                    expected: Some(10.0),
447                    actual: Some(10.0),
448                    tolerance: Some(0.001),
449                    error: None,
450                }],
451            },
452            falsification: FalsificationSummary {
453                total: 1,
454                passed: 1,
455                triggered: 0,
456                jidoka_triggered: false,
457                criteria: vec![FalsificationCriterionResult {
458                    id: "FC-001".to_string(),
459                    name: "Error bound".to_string(),
460                    triggered: false,
461                    condition: "error < 0.01".to_string(),
462                    severity: "critical".to_string(),
463                    value: None,
464                    threshold: Some(0.01),
465                }],
466            },
467            reproducibility: Some(ReproducibilitySummary {
468                passed: true,
469                runs: 3,
470                identical: true,
471                reference_hash: "abc123".to_string(),
472                run_hashes: vec!["abc123".to_string(); 3],
473                platform: "x86_64".to_string(),
474            }),
475            execution: ExecutionMetrics {
476                duration_ms: 100,
477                steps: 1000,
478                replications: 30,
479                peak_memory_bytes: Some(1024 * 1024),
480            },
481            artifacts: Vec::new(),
482            warnings: vec!["Test warning".to_string()],
483        }
484    }
485
486    #[test]
487    fn test_markdown_report() {
488        let result = sample_result();
489        let report = ReportGenerator::markdown(&result);
490        assert!(report.is_ok());
491        let report = report.expect("markdown generation should succeed");
492        assert!(report.contains("# Experiment Report: Test Experiment"));
493        assert!(report.contains("PASSED"));
494        assert!(report.contains("42"));
495        assert!(report.contains("VT-001"));
496    }
497
498    #[test]
499    fn test_json_report() {
500        let result = sample_result();
501        let report = ReportGenerator::json(&result);
502        assert!(report.is_ok());
503        let report = report.expect("json generation should succeed");
504        assert!(report.contains("\"name\": \"Test Experiment\""));
505        assert!(report.contains("\"seed\": 42"));
506    }
507
508    #[test]
509    fn test_text_report() {
510        let result = sample_result();
511        let report = ReportGenerator::text(&result);
512        assert!(report.is_ok());
513        let report = report.expect("text generation should succeed");
514        assert!(report.contains("EXPERIMENT REPORT: Test Experiment"));
515        assert!(report.contains("RESULT: PASSED"));
516    }
517
518    #[test]
519    fn test_emc_compliance_markdown() {
520        let report = EmcComplianceReport {
521            experiment_name: "Test".to_string(),
522            passed: true,
523            schema_errors: Vec::new(),
524            emc_errors: Vec::new(),
525            warnings: Vec::new(),
526            edd_compliance: EddComplianceChecklist {
527                edd_01_emc_reference: true,
528                edd_02_verification_tests: true,
529                edd_03_seed_specified: true,
530                edd_04_falsification_criteria: true,
531                edd_05_hypothesis: true,
532            },
533        };
534
535        let output = ReportGenerator::emc_compliance_markdown(&report);
536        assert!(output.is_ok());
537        let output = output.expect("compliance markdown should succeed");
538        assert!(output.contains("COMPLIANT"));
539        assert!(output.contains("EDD-01"));
540    }
541
542    #[test]
543    fn test_failed_report() {
544        let mut result = sample_result();
545        result.passed = false;
546        result.verification.failed = 1;
547        result.verification.passed = 1;
548
549        let report = ReportGenerator::markdown(&result);
550        assert!(report.is_ok());
551        let report = report.expect("markdown for failed result should succeed");
552        assert!(report.contains("FAILED"));
553    }
554
555    #[test]
556    fn test_report_format_enum() {
557        let markdown = ReportFormat::Markdown;
558        let json = ReportFormat::Json;
559        let text = ReportFormat::Text;
560
561        assert_eq!(markdown, ReportFormat::Markdown);
562        assert_ne!(json, ReportFormat::Markdown);
563        assert_ne!(text, ReportFormat::Json);
564    }
565
566    #[test]
567    fn test_markdown_report_no_reproducibility() {
568        let mut result = sample_result();
569        result.reproducibility = None;
570        result.warnings.clear();
571
572        let report = ReportGenerator::markdown(&result);
573        assert!(report.is_ok());
574        let report = report.ok().unwrap();
575        // Should not contain Reproducibility section
576        assert!(!report.contains("## Reproducibility"));
577        // Should not contain Warnings section
578        assert!(!report.contains("## Warnings"));
579    }
580
581    #[test]
582    fn test_markdown_report_with_memory() {
583        let result = sample_result();
584        let report = ReportGenerator::markdown(&result);
585        assert!(report.is_ok());
586        let report = report.ok().unwrap();
587        // Should contain memory info
588        assert!(report.contains("1048576")); // 1024*1024
589    }
590
591    #[test]
592    fn test_text_report_with_jidoka() {
593        let mut result = sample_result();
594        result.falsification.jidoka_triggered = true;
595
596        let report = ReportGenerator::text(&result);
597        assert!(report.is_ok());
598        let report = report.ok().unwrap();
599        assert!(report.contains("TRIGGERED"));
600    }
601
602    #[test]
603    fn test_markdown_verification_with_no_expected() {
604        let mut result = sample_result();
605        result.verification.tests = vec![VerificationTestSummary {
606            id: "VT-002".to_string(),
607            name: "No expected".to_string(),
608            passed: true,
609            expected: None,
610            actual: None,
611            tolerance: None,
612            error: None,
613        }];
614
615        let report = ReportGenerator::markdown(&result);
616        assert!(report.is_ok());
617    }
618
619    #[test]
620    fn test_markdown_falsification_with_triggered() {
621        let mut result = sample_result();
622        result.falsification.criteria[0].triggered = true;
623        result.falsification.triggered = 1;
624        result.falsification.passed = 0;
625
626        let report = ReportGenerator::markdown(&result);
627        assert!(report.is_ok());
628        let report = report.ok().unwrap();
629        assert!(report.contains("YES"));
630    }
631
632    #[test]
633    fn test_markdown_jidoka_triggered() {
634        let mut result = sample_result();
635        result.falsification.jidoka_triggered = true;
636
637        let report = ReportGenerator::markdown(&result);
638        assert!(report.is_ok());
639        let report = report.ok().unwrap();
640        assert!(report.contains("Jidoka"));
641        assert!(report.contains("TRIGGERED"));
642    }
643
644    #[test]
645    fn test_emc_compliance_with_errors() {
646        let report = EmcComplianceReport {
647            experiment_name: "Error Test".to_string(),
648            passed: false,
649            schema_errors: vec!["Schema error 1".to_string()],
650            emc_errors: vec!["EMC error 1".to_string()],
651            warnings: vec!["Warning 1".to_string()],
652            edd_compliance: EddComplianceChecklist {
653                edd_01_emc_reference: false,
654                edd_02_verification_tests: false,
655                edd_03_seed_specified: true,
656                edd_04_falsification_criteria: false,
657                edd_05_hypothesis: false,
658            },
659        };
660
661        let output = ReportGenerator::emc_compliance_markdown(&report);
662        assert!(output.is_ok());
663        let output = output.ok().unwrap();
664        assert!(output.contains("NON-COMPLIANT"));
665        assert!(output.contains("Schema: Schema error 1"));
666        assert!(output.contains("EMC: EMC error 1"));
667        assert!(output.contains("Warning 1"));
668        assert!(output.contains("FAIL"));
669    }
670
671    #[test]
672    fn test_markdown_execution_no_memory() {
673        let mut result = sample_result();
674        result.execution.peak_memory_bytes = None;
675
676        let report = ReportGenerator::markdown(&result);
677        assert!(report.is_ok());
678        let report = report.ok().unwrap();
679        // Should not contain Peak Memory line since it's None
680        assert!(!report.contains("Peak Memory"));
681    }
682
683    #[test]
684    fn test_verification_empty_tests() {
685        let mut result = sample_result();
686        result.verification.tests.clear();
687        result.verification.total = 0;
688        result.verification.passed = 0;
689        result.verification.failed = 0;
690
691        let report = ReportGenerator::markdown(&result);
692        assert!(report.is_ok());
693        let report = report.ok().unwrap();
694        assert!(report.contains("0 passed, 0 failed out of 0 total"));
695    }
696
697    #[test]
698    fn test_falsification_empty_criteria() {
699        let mut result = sample_result();
700        result.falsification.criteria.clear();
701        result.falsification.total = 0;
702        result.falsification.passed = 0;
703        result.falsification.triggered = 0;
704
705        let report = ReportGenerator::markdown(&result);
706        assert!(report.is_ok());
707        let report = report.ok().unwrap();
708        assert!(report.contains("0 passed, 0 triggered out of 0 total"));
709    }
710
711    #[test]
712    fn test_compliance_no_errors_no_warnings() {
713        let report = EmcComplianceReport {
714            experiment_name: "Clean".to_string(),
715            passed: true,
716            schema_errors: Vec::new(),
717            emc_errors: Vec::new(),
718            warnings: Vec::new(),
719            edd_compliance: EddComplianceChecklist {
720                edd_01_emc_reference: true,
721                edd_02_verification_tests: true,
722                edd_03_seed_specified: true,
723                edd_04_falsification_criteria: true,
724                edd_05_hypothesis: true,
725            },
726        };
727
728        let output = ReportGenerator::emc_compliance_markdown(&report);
729        assert!(output.is_ok());
730        let output = output.ok().unwrap();
731        assert!(output.contains("COMPLIANT"));
732        // Should not contain Errors or Warnings sections
733        assert!(!output.contains("## Errors"));
734        assert!(!output.contains("## Warnings"));
735    }
736
737    #[test]
738    fn test_report_format_debug() {
739        let format = ReportFormat::Markdown;
740        let debug_str = format!("{format:?}");
741        assert!(debug_str.contains("Markdown"));
742    }
743
744    #[test]
745    fn test_report_format_clone() {
746        let format = ReportFormat::Json;
747        let cloned = format;
748        assert_eq!(cloned, ReportFormat::Json);
749    }
750
751    #[test]
752    fn test_report_format_copy() {
753        let format = ReportFormat::Text;
754        let copied = format;
755        assert_eq!(format, copied);
756    }
757
758    #[test]
759    fn test_text_report_without_jidoka() {
760        let result = sample_result();
761        let report = ReportGenerator::text(&result);
762        assert!(report.is_ok());
763        let report = report.ok().unwrap();
764        assert!(report.contains("Jidoka:    OK"));
765    }
766
767    #[test]
768    fn test_markdown_report_content_details() {
769        let result = sample_result();
770        let report = ReportGenerator::markdown(&result);
771        assert!(report.is_ok());
772        let report = report.ok().unwrap();
773
774        // Check metadata table
775        assert!(report.contains("| Field | Value |"));
776        assert!(report.contains("| Experiment ID |"));
777        assert!(report.contains("| Seed |"));
778        assert!(report.contains("| Status |"));
779
780        // Check verification section
781        assert!(report.contains("## Verification Tests"));
782        assert!(report.contains("**Summary:**"));
783
784        // Check falsification section
785        assert!(report.contains("## Falsification Criteria"));
786
787        // Check reproducibility section (sample_result has it)
788        assert!(report.contains("## Reproducibility"));
789
790        // Check execution section
791        assert!(report.contains("## Execution Metrics"));
792        assert!(report.contains("| Duration |"));
793        assert!(report.contains("| Steps |"));
794        assert!(report.contains("| Replications |"));
795    }
796
797    #[test]
798    fn test_json_report_deserialization() {
799        let result = sample_result();
800        let json = ReportGenerator::json(&result);
801        assert!(json.is_ok());
802        let json = json.ok().unwrap();
803
804        // Should be valid JSON
805        let parsed: Result<serde_json::Value, _> = serde_json::from_str(&json);
806        assert!(parsed.is_ok());
807    }
808
809    #[test]
810    fn test_text_report_full_content() {
811        let result = sample_result();
812        let report = ReportGenerator::text(&result);
813        assert!(report.is_ok());
814        let report = report.ok().unwrap();
815
816        assert!(report.contains("EXPERIMENT REPORT:"));
817        assert!(report.contains("ID:"));
818        assert!(report.contains("Seed:"));
819        assert!(report.contains("VERIFICATION TESTS"));
820        assert!(report.contains("FALSIFICATION CRITERIA"));
821        assert!(report.contains("EXECUTION"));
822        assert!(report.contains("Duration:"));
823        assert!(report.contains("Replications:"));
824    }
825
826    #[test]
827    fn test_emc_compliance_only_schema_errors() {
828        let report = EmcComplianceReport {
829            experiment_name: "Schema Error".to_string(),
830            passed: false,
831            schema_errors: vec!["Schema error only".to_string()],
832            emc_errors: Vec::new(),
833            warnings: Vec::new(),
834            edd_compliance: EddComplianceChecklist {
835                edd_01_emc_reference: true,
836                edd_02_verification_tests: true,
837                edd_03_seed_specified: true,
838                edd_04_falsification_criteria: true,
839                edd_05_hypothesis: true,
840            },
841        };
842
843        let output = ReportGenerator::emc_compliance_markdown(&report);
844        assert!(output.is_ok());
845        let output = output.ok().unwrap();
846        assert!(output.contains("Schema: Schema error only"));
847    }
848
849    #[test]
850    fn test_emc_compliance_only_emc_errors() {
851        let report = EmcComplianceReport {
852            experiment_name: "EMC Error".to_string(),
853            passed: false,
854            schema_errors: Vec::new(),
855            emc_errors: vec!["EMC error only".to_string()],
856            warnings: Vec::new(),
857            edd_compliance: EddComplianceChecklist {
858                edd_01_emc_reference: true,
859                edd_02_verification_tests: true,
860                edd_03_seed_specified: true,
861                edd_04_falsification_criteria: true,
862                edd_05_hypothesis: true,
863            },
864        };
865
866        let output = ReportGenerator::emc_compliance_markdown(&report);
867        assert!(output.is_ok());
868        let output = output.ok().unwrap();
869        assert!(output.contains("EMC: EMC error only"));
870    }
871
872    #[test]
873    fn test_verification_with_error_field() {
874        let mut result = sample_result();
875        result.verification.tests = vec![VerificationTestSummary {
876            id: "VT-ERR".to_string(),
877            name: "Test with error".to_string(),
878            passed: false,
879            expected: Some(10.0),
880            actual: Some(15.0),
881            tolerance: Some(0.001),
882            error: Some("Out of tolerance".to_string()),
883        }];
884        result.verification.passed = 0;
885        result.verification.failed = 1;
886
887        let report = ReportGenerator::markdown(&result);
888        assert!(report.is_ok());
889        let report = report.ok().unwrap();
890        assert!(report.contains("VT-ERR"));
891        assert!(report.contains("FAIL"));
892    }
893
894    #[test]
895    fn test_reproducibility_not_passed() {
896        let mut result = sample_result();
897        result.reproducibility = Some(ReproducibilitySummary {
898            passed: false,
899            runs: 3,
900            identical: false,
901            reference_hash: "abc123".to_string(),
902            run_hashes: vec![
903                "abc123".to_string(),
904                "def456".to_string(),
905                "ghi789".to_string(),
906            ],
907            platform: "x86_64".to_string(),
908        });
909
910        let report = ReportGenerator::markdown(&result);
911        assert!(report.is_ok());
912        let report = report.ok().unwrap();
913        assert!(report.contains("| Passed | NO |"));
914        assert!(report.contains("| Identical | false |"));
915    }
916
917    #[test]
918    fn test_multiple_verification_tests() {
919        let mut result = sample_result();
920        result.verification.tests = vec![
921            VerificationTestSummary {
922                id: "VT-001".to_string(),
923                name: "Test 1".to_string(),
924                passed: true,
925                expected: Some(1.0),
926                actual: Some(1.0),
927                tolerance: Some(0.01),
928                error: None,
929            },
930            VerificationTestSummary {
931                id: "VT-002".to_string(),
932                name: "Test 2".to_string(),
933                passed: true,
934                expected: Some(2.0),
935                actual: Some(2.0),
936                tolerance: Some(0.01),
937                error: None,
938            },
939            VerificationTestSummary {
940                id: "VT-003".to_string(),
941                name: "Test 3".to_string(),
942                passed: false,
943                expected: Some(3.0),
944                actual: Some(3.5),
945                tolerance: Some(0.01),
946                error: Some("Test 3 failed".to_string()),
947            },
948        ];
949
950        let report = ReportGenerator::markdown(&result);
951        assert!(report.is_ok());
952        let report = report.ok().unwrap();
953        assert!(report.contains("VT-001"));
954        assert!(report.contains("VT-002"));
955        assert!(report.contains("VT-003"));
956    }
957
958    #[test]
959    fn test_multiple_falsification_criteria() {
960        let mut result = sample_result();
961        result.falsification.criteria = vec![
962            FalsificationCriterionResult {
963                id: "FC-001".to_string(),
964                name: "Criterion 1".to_string(),
965                triggered: false,
966                condition: "x < 1".to_string(),
967                severity: "warning".to_string(),
968                value: Some(0.5),
969                threshold: Some(1.0),
970            },
971            FalsificationCriterionResult {
972                id: "FC-002".to_string(),
973                name: "Criterion 2".to_string(),
974                triggered: true,
975                condition: "y > 10".to_string(),
976                severity: "critical".to_string(),
977                value: Some(15.0),
978                threshold: Some(10.0),
979            },
980        ];
981
982        let report = ReportGenerator::markdown(&result);
983        assert!(report.is_ok());
984        let report = report.ok().unwrap();
985        assert!(report.contains("FC-001"));
986        assert!(report.contains("FC-002"));
987    }
988
989    #[test]
990    fn test_all_edd_compliance_fail() {
991        let report = EmcComplianceReport {
992            experiment_name: "All Fail".to_string(),
993            passed: false,
994            schema_errors: Vec::new(),
995            emc_errors: Vec::new(),
996            warnings: Vec::new(),
997            edd_compliance: EddComplianceChecklist {
998                edd_01_emc_reference: false,
999                edd_02_verification_tests: false,
1000                edd_03_seed_specified: false,
1001                edd_04_falsification_criteria: false,
1002                edd_05_hypothesis: false,
1003            },
1004        };
1005
1006        let output = ReportGenerator::emc_compliance_markdown(&report);
1007        assert!(output.is_ok());
1008        let output = output.ok().unwrap();
1009
1010        // Should contain FAIL for all checklist items
1011        let fail_count = output.matches("| FAIL |").count();
1012        assert_eq!(fail_count, 5, "All 5 EDD checks should fail");
1013    }
1014
1015    #[test]
1016    fn test_emc_compliance_footer() {
1017        let report = EmcComplianceReport {
1018            experiment_name: "Footer Test".to_string(),
1019            passed: true,
1020            schema_errors: Vec::new(),
1021            emc_errors: Vec::new(),
1022            warnings: Vec::new(),
1023            edd_compliance: EddComplianceChecklist {
1024                edd_01_emc_reference: true,
1025                edd_02_verification_tests: true,
1026                edd_03_seed_specified: true,
1027                edd_04_falsification_criteria: true,
1028                edd_05_hypothesis: true,
1029            },
1030        };
1031
1032        let output = ReportGenerator::emc_compliance_markdown(&report);
1033        assert!(output.is_ok());
1034        let output = output.ok().unwrap();
1035        assert!(output.contains("---"));
1036        assert!(output.contains("*Generated by simular v"));
1037    }
1038}