use super::runner::{
EmcComplianceReport, ExecutionMetrics, ExperimentResult, FalsificationSummary,
ReproducibilitySummary, VerificationSummary,
};
use std::fmt::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReportFormat {
Markdown,
Json,
Text,
}
pub struct ReportGenerator;
impl ReportGenerator {
pub fn markdown(result: &ExperimentResult) -> Result<String, String> {
let mut report = String::new();
writeln!(report, "# Experiment Report: {}", result.name).map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "## Metadata").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "| Field | Value |").map_err(|e| e.to_string())?;
writeln!(report, "|-------|-------|").map_err(|e| e.to_string())?;
writeln!(report, "| Experiment ID | {} |", result.experiment_id)
.map_err(|e| e.to_string())?;
writeln!(report, "| Seed | {} |", result.seed).map_err(|e| e.to_string())?;
writeln!(
report,
"| Status | {} |",
if result.passed { "PASSED" } else { "FAILED" }
)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "## Verification Tests").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
Self::write_verification_summary(&mut report, &result.verification)?;
writeln!(report, "## Falsification Criteria").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
Self::write_falsification_summary(&mut report, &result.falsification)?;
if let Some(ref repro) = result.reproducibility {
writeln!(report, "## Reproducibility").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
Self::write_reproducibility_summary(&mut report, repro)?;
}
writeln!(report, "## Execution Metrics").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
Self::write_execution_metrics(&mut report, &result.execution)?;
if !result.warnings.is_empty() {
writeln!(report, "## Warnings").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
for warning in &result.warnings {
writeln!(report, "- {warning}").map_err(|e| e.to_string())?;
}
writeln!(report).map_err(|e| e.to_string())?;
}
writeln!(report, "---").map_err(|e| e.to_string())?;
writeln!(
report,
"*Generated by simular v{}*",
env!("CARGO_PKG_VERSION")
)
.map_err(|e| e.to_string())?;
Ok(report)
}
fn write_verification_summary(
report: &mut String,
summary: &VerificationSummary,
) -> Result<(), String> {
writeln!(
report,
"**Summary:** {} passed, {} failed out of {} total",
summary.passed, summary.failed, summary.total
)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
if !summary.tests.is_empty() {
writeln!(
report,
"| ID | Name | Status | Expected | Actual | Tolerance |"
)
.map_err(|e| e.to_string())?;
writeln!(report, "|---|---|---|---|---|---|").map_err(|e| e.to_string())?;
for test in &summary.tests {
let status = if test.passed { "PASS" } else { "FAIL" };
let expected = test
.expected
.map_or_else(|| "-".to_string(), |v| format!("{v:.6}"));
let actual = test
.actual
.map_or_else(|| "-".to_string(), |v| format!("{v:.6}"));
let tolerance = test
.tolerance
.map_or_else(|| "-".to_string(), |v| format!("{v:.2e}"));
writeln!(
report,
"| {} | {} | {} | {} | {} | {} |",
test.id, test.name, status, expected, actual, tolerance
)
.map_err(|e| e.to_string())?;
}
writeln!(report).map_err(|e| e.to_string())?;
}
Ok(())
}
fn write_falsification_summary(
report: &mut String,
summary: &FalsificationSummary,
) -> Result<(), String> {
writeln!(
report,
"**Summary:** {} passed, {} triggered out of {} total",
summary.passed, summary.triggered, summary.total
)
.map_err(|e| e.to_string())?;
if summary.jidoka_triggered {
writeln!(report, "\n**Jidoka:** TRIGGERED (stop-on-error)")
.map_err(|e| e.to_string())?;
}
writeln!(report).map_err(|e| e.to_string())?;
if !summary.criteria.is_empty() {
writeln!(report, "| ID | Name | Triggered | Severity | Condition |")
.map_err(|e| e.to_string())?;
writeln!(report, "|---|---|---|---|---|").map_err(|e| e.to_string())?;
for crit in &summary.criteria {
let triggered = if crit.triggered { "YES" } else { "NO" };
writeln!(
report,
"| {} | {} | {} | {} | {} |",
crit.id, crit.name, triggered, crit.severity, crit.condition
)
.map_err(|e| e.to_string())?;
}
writeln!(report).map_err(|e| e.to_string())?;
}
Ok(())
}
fn write_reproducibility_summary(
report: &mut String,
summary: &ReproducibilitySummary,
) -> Result<(), String> {
writeln!(report, "| Property | Value |").map_err(|e| e.to_string())?;
writeln!(report, "|---|---|").map_err(|e| e.to_string())?;
writeln!(
report,
"| Passed | {} |",
if summary.passed { "YES" } else { "NO" }
)
.map_err(|e| e.to_string())?;
writeln!(report, "| Runs | {} |", summary.runs).map_err(|e| e.to_string())?;
writeln!(report, "| Identical | {} |", summary.identical).map_err(|e| e.to_string())?;
writeln!(report, "| Platform | {} |", summary.platform).map_err(|e| e.to_string())?;
writeln!(report, "| Reference Hash | `{}` |", summary.reference_hash)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
Ok(())
}
fn write_execution_metrics(
report: &mut String,
metrics: &ExecutionMetrics,
) -> Result<(), String> {
writeln!(report, "| Metric | Value |").map_err(|e| e.to_string())?;
writeln!(report, "|---|---|").map_err(|e| e.to_string())?;
writeln!(report, "| Duration | {} ms |", metrics.duration_ms).map_err(|e| e.to_string())?;
writeln!(report, "| Steps | {} |", metrics.steps).map_err(|e| e.to_string())?;
writeln!(report, "| Replications | {} |", metrics.replications)
.map_err(|e| e.to_string())?;
if let Some(mem) = metrics.peak_memory_bytes {
writeln!(report, "| Peak Memory | {mem} bytes |").map_err(|e| e.to_string())?;
}
writeln!(report).map_err(|e| e.to_string())?;
Ok(())
}
pub fn json(result: &ExperimentResult) -> Result<String, String> {
serde_json::to_string_pretty(result)
.map_err(|e| format!("Failed to serialize to JSON: {e}"))
}
pub fn text(result: &ExperimentResult) -> Result<String, String> {
let mut report = String::new();
let status = if result.passed { "PASSED" } else { "FAILED" };
writeln!(report, "EXPERIMENT REPORT: {}", result.name).map_err(|e| e.to_string())?;
writeln!(
report,
"================================================================================"
)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "ID: {}", result.experiment_id).map_err(|e| e.to_string())?;
writeln!(report, "Seed: {}", result.seed).map_err(|e| e.to_string())?;
writeln!(report, "Status: {status}").map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "VERIFICATION TESTS").map_err(|e| e.to_string())?;
writeln!(
report,
"--------------------------------------------------------------------------------"
)
.map_err(|e| e.to_string())?;
writeln!(report, "Total: {}", result.verification.total).map_err(|e| e.to_string())?;
writeln!(report, "Passed: {}", result.verification.passed).map_err(|e| e.to_string())?;
writeln!(report, "Failed: {}", result.verification.failed).map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "FALSIFICATION CRITERIA").map_err(|e| e.to_string())?;
writeln!(
report,
"--------------------------------------------------------------------------------"
)
.map_err(|e| e.to_string())?;
writeln!(report, "Total: {}", result.falsification.total).map_err(|e| e.to_string())?;
writeln!(report, "Passed: {}", result.falsification.passed)
.map_err(|e| e.to_string())?;
writeln!(report, "Triggered: {}", result.falsification.triggered)
.map_err(|e| e.to_string())?;
writeln!(
report,
"Jidoka: {}",
if result.falsification.jidoka_triggered {
"TRIGGERED"
} else {
"OK"
}
)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(report, "EXECUTION").map_err(|e| e.to_string())?;
writeln!(
report,
"--------------------------------------------------------------------------------"
)
.map_err(|e| e.to_string())?;
writeln!(report, "Duration: {} ms", result.execution.duration_ms)
.map_err(|e| e.to_string())?;
writeln!(report, "Replications: {}", result.execution.replications)
.map_err(|e| e.to_string())?;
writeln!(report).map_err(|e| e.to_string())?;
writeln!(
report,
"================================================================================"
)
.map_err(|e| e.to_string())?;
writeln!(report, "RESULT: {status}").map_err(|e| e.to_string())?;
Ok(report)
}
pub fn emc_compliance_markdown(report: &EmcComplianceReport) -> Result<String, String> {
let mut output = String::new();
writeln!(
output,
"# EMC Compliance Report: {}",
report.experiment_name
)
.map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
let status = if report.passed {
"COMPLIANT"
} else {
"NON-COMPLIANT"
};
writeln!(output, "**Status:** {status}").map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
writeln!(output, "## EDD Compliance Checklist").map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
writeln!(output, "| Requirement | Status |").map_err(|e| e.to_string())?;
writeln!(output, "|---|---|").map_err(|e| e.to_string())?;
let check = |b: bool| if b { "PASS" } else { "FAIL" };
writeln!(
output,
"| EDD-01: EMC Reference | {} |",
check(report.edd_compliance.edd_01_emc_reference)
)
.map_err(|e| e.to_string())?;
writeln!(
output,
"| EDD-02: Verification Tests | {} |",
check(report.edd_compliance.edd_02_verification_tests)
)
.map_err(|e| e.to_string())?;
writeln!(
output,
"| EDD-03: Seed Specified | {} |",
check(report.edd_compliance.edd_03_seed_specified)
)
.map_err(|e| e.to_string())?;
writeln!(
output,
"| EDD-04: Falsification Criteria | {} |",
check(report.edd_compliance.edd_04_falsification_criteria)
)
.map_err(|e| e.to_string())?;
writeln!(
output,
"| EDD-05: Hypothesis (Optional) | {} |",
check(report.edd_compliance.edd_05_hypothesis)
)
.map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
if !report.schema_errors.is_empty() || !report.emc_errors.is_empty() {
writeln!(output, "## Errors").map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
for err in &report.schema_errors {
writeln!(output, "- Schema: {err}").map_err(|e| e.to_string())?;
}
for err in &report.emc_errors {
writeln!(output, "- EMC: {err}").map_err(|e| e.to_string())?;
}
writeln!(output).map_err(|e| e.to_string())?;
}
if !report.warnings.is_empty() {
writeln!(output, "## Warnings").map_err(|e| e.to_string())?;
writeln!(output).map_err(|e| e.to_string())?;
for warning in &report.warnings {
writeln!(output, "- {warning}").map_err(|e| e.to_string())?;
}
writeln!(output).map_err(|e| e.to_string())?;
}
writeln!(output, "---").map_err(|e| e.to_string())?;
writeln!(
output,
"*Generated by simular v{}*",
env!("CARGO_PKG_VERSION")
)
.map_err(|e| e.to_string())?;
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edd::runner::{
EddComplianceChecklist, FalsificationCriterionResult, VerificationTestSummary,
};
fn sample_result() -> ExperimentResult {
ExperimentResult {
name: "Test Experiment".to_string(),
experiment_id: "EXP-001".to_string(),
seed: 42,
passed: true,
verification: VerificationSummary {
total: 2,
passed: 2,
failed: 0,
tests: vec![VerificationTestSummary {
id: "VT-001".to_string(),
name: "Basic test".to_string(),
passed: true,
expected: Some(10.0),
actual: Some(10.0),
tolerance: Some(0.001),
error: None,
}],
},
falsification: FalsificationSummary {
total: 1,
passed: 1,
triggered: 0,
jidoka_triggered: false,
criteria: vec![FalsificationCriterionResult {
id: "FC-001".to_string(),
name: "Error bound".to_string(),
triggered: false,
condition: "error < 0.01".to_string(),
severity: "critical".to_string(),
value: None,
threshold: Some(0.01),
}],
},
reproducibility: Some(ReproducibilitySummary {
passed: true,
runs: 3,
identical: true,
reference_hash: "abc123".to_string(),
run_hashes: vec!["abc123".to_string(); 3],
platform: "x86_64".to_string(),
}),
execution: ExecutionMetrics {
duration_ms: 100,
steps: 1000,
replications: 30,
peak_memory_bytes: Some(1024 * 1024),
},
artifacts: Vec::new(),
warnings: vec!["Test warning".to_string()],
}
}
#[test]
fn test_markdown_report() {
let result = sample_result();
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.expect("markdown generation should succeed");
assert!(report.contains("# Experiment Report: Test Experiment"));
assert!(report.contains("PASSED"));
assert!(report.contains("42"));
assert!(report.contains("VT-001"));
}
#[test]
fn test_json_report() {
let result = sample_result();
let report = ReportGenerator::json(&result);
assert!(report.is_ok());
let report = report.expect("json generation should succeed");
assert!(report.contains("\"name\": \"Test Experiment\""));
assert!(report.contains("\"seed\": 42"));
}
#[test]
fn test_text_report() {
let result = sample_result();
let report = ReportGenerator::text(&result);
assert!(report.is_ok());
let report = report.expect("text generation should succeed");
assert!(report.contains("EXPERIMENT REPORT: Test Experiment"));
assert!(report.contains("RESULT: PASSED"));
}
#[test]
fn test_emc_compliance_markdown() {
let report = EmcComplianceReport {
experiment_name: "Test".to_string(),
passed: true,
schema_errors: Vec::new(),
emc_errors: Vec::new(),
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: true,
edd_02_verification_tests: true,
edd_03_seed_specified: true,
edd_04_falsification_criteria: true,
edd_05_hypothesis: true,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.expect("compliance markdown should succeed");
assert!(output.contains("COMPLIANT"));
assert!(output.contains("EDD-01"));
}
#[test]
fn test_failed_report() {
let mut result = sample_result();
result.passed = false;
result.verification.failed = 1;
result.verification.passed = 1;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.expect("markdown for failed result should succeed");
assert!(report.contains("FAILED"));
}
#[test]
fn test_report_format_enum() {
let markdown = ReportFormat::Markdown;
let json = ReportFormat::Json;
let text = ReportFormat::Text;
assert_eq!(markdown, ReportFormat::Markdown);
assert_ne!(json, ReportFormat::Markdown);
assert_ne!(text, ReportFormat::Json);
}
#[test]
fn test_markdown_report_no_reproducibility() {
let mut result = sample_result();
result.reproducibility = None;
result.warnings.clear();
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(!report.contains("## Reproducibility"));
assert!(!report.contains("## Warnings"));
}
#[test]
fn test_markdown_report_with_memory() {
let result = sample_result();
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("1048576")); }
#[test]
fn test_text_report_with_jidoka() {
let mut result = sample_result();
result.falsification.jidoka_triggered = true;
let report = ReportGenerator::text(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("TRIGGERED"));
}
#[test]
fn test_markdown_verification_with_no_expected() {
let mut result = sample_result();
result.verification.tests = vec![VerificationTestSummary {
id: "VT-002".to_string(),
name: "No expected".to_string(),
passed: true,
expected: None,
actual: None,
tolerance: None,
error: None,
}];
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
}
#[test]
fn test_markdown_falsification_with_triggered() {
let mut result = sample_result();
result.falsification.criteria[0].triggered = true;
result.falsification.triggered = 1;
result.falsification.passed = 0;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("YES"));
}
#[test]
fn test_markdown_jidoka_triggered() {
let mut result = sample_result();
result.falsification.jidoka_triggered = true;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("Jidoka"));
assert!(report.contains("TRIGGERED"));
}
#[test]
fn test_emc_compliance_with_errors() {
let report = EmcComplianceReport {
experiment_name: "Error Test".to_string(),
passed: false,
schema_errors: vec!["Schema error 1".to_string()],
emc_errors: vec!["EMC error 1".to_string()],
warnings: vec!["Warning 1".to_string()],
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: false,
edd_02_verification_tests: false,
edd_03_seed_specified: true,
edd_04_falsification_criteria: false,
edd_05_hypothesis: false,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
assert!(output.contains("NON-COMPLIANT"));
assert!(output.contains("Schema: Schema error 1"));
assert!(output.contains("EMC: EMC error 1"));
assert!(output.contains("Warning 1"));
assert!(output.contains("FAIL"));
}
#[test]
fn test_markdown_execution_no_memory() {
let mut result = sample_result();
result.execution.peak_memory_bytes = None;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(!report.contains("Peak Memory"));
}
#[test]
fn test_verification_empty_tests() {
let mut result = sample_result();
result.verification.tests.clear();
result.verification.total = 0;
result.verification.passed = 0;
result.verification.failed = 0;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("0 passed, 0 failed out of 0 total"));
}
#[test]
fn test_falsification_empty_criteria() {
let mut result = sample_result();
result.falsification.criteria.clear();
result.falsification.total = 0;
result.falsification.passed = 0;
result.falsification.triggered = 0;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("0 passed, 0 triggered out of 0 total"));
}
#[test]
fn test_compliance_no_errors_no_warnings() {
let report = EmcComplianceReport {
experiment_name: "Clean".to_string(),
passed: true,
schema_errors: Vec::new(),
emc_errors: Vec::new(),
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: true,
edd_02_verification_tests: true,
edd_03_seed_specified: true,
edd_04_falsification_criteria: true,
edd_05_hypothesis: true,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
assert!(output.contains("COMPLIANT"));
assert!(!output.contains("## Errors"));
assert!(!output.contains("## Warnings"));
}
#[test]
fn test_report_format_debug() {
let format = ReportFormat::Markdown;
let debug_str = format!("{format:?}");
assert!(debug_str.contains("Markdown"));
}
#[test]
fn test_report_format_clone() {
let format = ReportFormat::Json;
let cloned = format;
assert_eq!(cloned, ReportFormat::Json);
}
#[test]
fn test_report_format_copy() {
let format = ReportFormat::Text;
let copied = format;
assert_eq!(format, copied);
}
#[test]
fn test_text_report_without_jidoka() {
let result = sample_result();
let report = ReportGenerator::text(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("Jidoka: OK"));
}
#[test]
fn test_markdown_report_content_details() {
let result = sample_result();
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("| Field | Value |"));
assert!(report.contains("| Experiment ID |"));
assert!(report.contains("| Seed |"));
assert!(report.contains("| Status |"));
assert!(report.contains("## Verification Tests"));
assert!(report.contains("**Summary:**"));
assert!(report.contains("## Falsification Criteria"));
assert!(report.contains("## Reproducibility"));
assert!(report.contains("## Execution Metrics"));
assert!(report.contains("| Duration |"));
assert!(report.contains("| Steps |"));
assert!(report.contains("| Replications |"));
}
#[test]
fn test_json_report_deserialization() {
let result = sample_result();
let json = ReportGenerator::json(&result);
assert!(json.is_ok());
let json = json.ok().unwrap();
let parsed: Result<serde_json::Value, _> = serde_json::from_str(&json);
assert!(parsed.is_ok());
}
#[test]
fn test_text_report_full_content() {
let result = sample_result();
let report = ReportGenerator::text(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("EXPERIMENT REPORT:"));
assert!(report.contains("ID:"));
assert!(report.contains("Seed:"));
assert!(report.contains("VERIFICATION TESTS"));
assert!(report.contains("FALSIFICATION CRITERIA"));
assert!(report.contains("EXECUTION"));
assert!(report.contains("Duration:"));
assert!(report.contains("Replications:"));
}
#[test]
fn test_emc_compliance_only_schema_errors() {
let report = EmcComplianceReport {
experiment_name: "Schema Error".to_string(),
passed: false,
schema_errors: vec!["Schema error only".to_string()],
emc_errors: Vec::new(),
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: true,
edd_02_verification_tests: true,
edd_03_seed_specified: true,
edd_04_falsification_criteria: true,
edd_05_hypothesis: true,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
assert!(output.contains("Schema: Schema error only"));
}
#[test]
fn test_emc_compliance_only_emc_errors() {
let report = EmcComplianceReport {
experiment_name: "EMC Error".to_string(),
passed: false,
schema_errors: Vec::new(),
emc_errors: vec!["EMC error only".to_string()],
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: true,
edd_02_verification_tests: true,
edd_03_seed_specified: true,
edd_04_falsification_criteria: true,
edd_05_hypothesis: true,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
assert!(output.contains("EMC: EMC error only"));
}
#[test]
fn test_verification_with_error_field() {
let mut result = sample_result();
result.verification.tests = vec![VerificationTestSummary {
id: "VT-ERR".to_string(),
name: "Test with error".to_string(),
passed: false,
expected: Some(10.0),
actual: Some(15.0),
tolerance: Some(0.001),
error: Some("Out of tolerance".to_string()),
}];
result.verification.passed = 0;
result.verification.failed = 1;
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("VT-ERR"));
assert!(report.contains("FAIL"));
}
#[test]
fn test_reproducibility_not_passed() {
let mut result = sample_result();
result.reproducibility = Some(ReproducibilitySummary {
passed: false,
runs: 3,
identical: false,
reference_hash: "abc123".to_string(),
run_hashes: vec![
"abc123".to_string(),
"def456".to_string(),
"ghi789".to_string(),
],
platform: "x86_64".to_string(),
});
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("| Passed | NO |"));
assert!(report.contains("| Identical | false |"));
}
#[test]
fn test_multiple_verification_tests() {
let mut result = sample_result();
result.verification.tests = vec![
VerificationTestSummary {
id: "VT-001".to_string(),
name: "Test 1".to_string(),
passed: true,
expected: Some(1.0),
actual: Some(1.0),
tolerance: Some(0.01),
error: None,
},
VerificationTestSummary {
id: "VT-002".to_string(),
name: "Test 2".to_string(),
passed: true,
expected: Some(2.0),
actual: Some(2.0),
tolerance: Some(0.01),
error: None,
},
VerificationTestSummary {
id: "VT-003".to_string(),
name: "Test 3".to_string(),
passed: false,
expected: Some(3.0),
actual: Some(3.5),
tolerance: Some(0.01),
error: Some("Test 3 failed".to_string()),
},
];
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("VT-001"));
assert!(report.contains("VT-002"));
assert!(report.contains("VT-003"));
}
#[test]
fn test_multiple_falsification_criteria() {
let mut result = sample_result();
result.falsification.criteria = vec![
FalsificationCriterionResult {
id: "FC-001".to_string(),
name: "Criterion 1".to_string(),
triggered: false,
condition: "x < 1".to_string(),
severity: "warning".to_string(),
value: Some(0.5),
threshold: Some(1.0),
},
FalsificationCriterionResult {
id: "FC-002".to_string(),
name: "Criterion 2".to_string(),
triggered: true,
condition: "y > 10".to_string(),
severity: "critical".to_string(),
value: Some(15.0),
threshold: Some(10.0),
},
];
let report = ReportGenerator::markdown(&result);
assert!(report.is_ok());
let report = report.ok().unwrap();
assert!(report.contains("FC-001"));
assert!(report.contains("FC-002"));
}
#[test]
fn test_all_edd_compliance_fail() {
let report = EmcComplianceReport {
experiment_name: "All Fail".to_string(),
passed: false,
schema_errors: Vec::new(),
emc_errors: Vec::new(),
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: false,
edd_02_verification_tests: false,
edd_03_seed_specified: false,
edd_04_falsification_criteria: false,
edd_05_hypothesis: false,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
let fail_count = output.matches("| FAIL |").count();
assert_eq!(fail_count, 5, "All 5 EDD checks should fail");
}
#[test]
fn test_emc_compliance_footer() {
let report = EmcComplianceReport {
experiment_name: "Footer Test".to_string(),
passed: true,
schema_errors: Vec::new(),
emc_errors: Vec::new(),
warnings: Vec::new(),
edd_compliance: EddComplianceChecklist {
edd_01_emc_reference: true,
edd_02_verification_tests: true,
edd_03_seed_specified: true,
edd_04_falsification_criteria: true,
edd_05_hypothesis: true,
},
};
let output = ReportGenerator::emc_compliance_markdown(&report);
assert!(output.is_ok());
let output = output.ok().unwrap();
assert!(output.contains("---"));
assert!(output.contains("*Generated by simular v"));
}
}