use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum RequirementLevel {
Must,
Should,
May,
}
impl fmt::Display for RequirementLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RequirementLevel::Must => write!(f, "MUST"),
RequirementLevel::Should => write!(f, "SHOULD"),
RequirementLevel::May => write!(f, "MAY"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TestCategory {
Unit,
Integration,
EdgeCase,
Performance,
Differential,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status")]
pub enum ConformanceResult {
Pass,
Fail {
reason: String,
details: Option<String>,
},
Skipped { reason: String },
ExpectedFailure {
reason: String,
discrepancy_id: String, },
Blocked { reason: String, blocker_id: String },
Unsupported { reason: String, blocker_id: String },
}
impl ConformanceResult {
pub fn is_passing(&self) -> bool {
matches!(
self,
ConformanceResult::Pass | ConformanceResult::ExpectedFailure { .. }
)
}
pub fn is_failing(&self) -> bool {
matches!(self, ConformanceResult::Fail { .. })
}
pub fn description(&self) -> String {
match self {
ConformanceResult::Pass => "PASS".to_string(),
ConformanceResult::Fail { reason, .. } => format!("FAIL: {reason}"),
ConformanceResult::Skipped { reason } => format!("SKIP: {reason}"),
ConformanceResult::ExpectedFailure {
reason,
discrepancy_id,
} => {
format!("XFAIL: {reason} (see {discrepancy_id})")
}
ConformanceResult::Blocked { reason, blocker_id } => {
format!("BLOCKED: {reason} (see {blocker_id})")
}
ConformanceResult::Unsupported { reason, blocker_id } => {
format!("UNSUPPORTED: {reason} (see {blocker_id})")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EvidenceKind {
LiveChecked,
FixtureOnly,
Blocked,
Unsupported,
ExpectedFail,
Failed,
}
impl fmt::Display for EvidenceKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EvidenceKind::LiveChecked => write!(f, "live_checked"),
EvidenceKind::FixtureOnly => write!(f, "fixture_only"),
EvidenceKind::Blocked => write!(f, "blocked"),
EvidenceKind::Unsupported => write!(f, "unsupported"),
EvidenceKind::ExpectedFail => write!(f, "expected_fail"),
EvidenceKind::Failed => write!(f, "failed"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TestStatus {
Pass,
Fail,
Skip,
ExpectedFail,
Blocked,
Unsupported,
}
impl fmt::Display for TestStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TestStatus::Pass => write!(f, "pass"),
TestStatus::Fail => write!(f, "fail"),
TestStatus::Skip => write!(f, "skip"),
TestStatus::ExpectedFail => write!(f, "expected_fail"),
TestStatus::Blocked => write!(f, "blocked"),
TestStatus::Unsupported => write!(f, "unsupported"),
}
}
}
impl From<&ConformanceResult> for TestStatus {
fn from(result: &ConformanceResult) -> Self {
match result {
ConformanceResult::Pass => Self::Pass,
ConformanceResult::Fail { .. } => Self::Fail,
ConformanceResult::Skipped { .. } => Self::Skip,
ConformanceResult::ExpectedFailure { .. } => Self::ExpectedFail,
ConformanceResult::Blocked { .. } => Self::Blocked,
ConformanceResult::Unsupported { .. } => Self::Unsupported,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvidenceMetadata {
pub evidence_kind: EvidenceKind,
pub test_status: TestStatus,
pub blocker_id: Option<String>,
pub fixture_reference: Option<String>,
pub production_seam_path: Option<String>,
}
impl EvidenceMetadata {
fn from_test_and_result(test: &dyn ConformanceTest, result: &ConformanceResult) -> Self {
let test_status = TestStatus::from(result);
let blocker_id = match result {
ConformanceResult::Blocked { blocker_id, .. }
| ConformanceResult::Unsupported { blocker_id, .. } => Some(blocker_id.clone()),
ConformanceResult::ExpectedFailure { discrepancy_id, .. } => {
Some(discrepancy_id.clone())
}
_ => test.blocker_id().map(str::to_string),
};
let production_seam_path = test.production_seam_path().map(str::to_string);
let fixture_reference = test.fixture_reference().map(str::to_string);
let evidence_kind = match result {
ConformanceResult::Pass => {
if production_seam_path.is_some() {
EvidenceKind::LiveChecked
} else if fixture_reference.is_some() {
EvidenceKind::FixtureOnly
} else {
EvidenceKind::Unsupported
}
}
ConformanceResult::Fail { .. } => EvidenceKind::Failed,
ConformanceResult::Skipped { .. } => EvidenceKind::Blocked,
ConformanceResult::ExpectedFailure { .. } => EvidenceKind::ExpectedFail,
ConformanceResult::Blocked { .. } => EvidenceKind::Blocked,
ConformanceResult::Unsupported { .. } => EvidenceKind::Unsupported,
};
Self {
evidence_kind,
test_status,
blocker_id,
fixture_reference,
production_seam_path,
}
}
}
#[derive(Debug, Clone)]
pub struct ConformanceContext {
pub timeout: Duration,
pub enable_differential: bool,
pub fixtures_path: Option<std::path::PathBuf>,
pub random_seed: u64,
pub verbose: bool,
}
impl Default for ConformanceContext {
fn default() -> Self {
Self {
timeout: Duration::from_secs(30),
enable_differential: false,
fixtures_path: None,
random_seed: 42,
verbose: false,
}
}
}
pub trait ConformanceTest: Send + Sync {
fn rfc_clause(&self) -> &str;
fn section(&self) -> &str;
fn requirement_level(&self) -> RequirementLevel;
fn category(&self) -> TestCategory;
fn description(&self) -> &str;
fn run(&self, ctx: &ConformanceContext) -> ConformanceResult;
fn name(&self) -> String {
self.rfc_clause().to_string()
}
fn dependencies(&self) -> Vec<&str> {
Vec::new()
}
fn tags(&self) -> Vec<&str> {
Vec::new()
}
fn production_seam_path(&self) -> Option<&str> {
None
}
fn fixture_reference(&self) -> Option<&str> {
None
}
fn blocker_id(&self) -> Option<&str> {
None
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestExecution {
pub test_name: String,
pub rfc_clause: String,
pub section: String,
pub level: RequirementLevel,
pub category: TestCategory,
pub description: String,
pub result: ConformanceResult,
pub evidence: EvidenceMetadata,
pub duration: Duration,
pub timestamp: std::time::SystemTime,
}
pub struct ConformanceRunner {
tests: Vec<Box<dyn ConformanceTest>>,
context: ConformanceContext,
}
impl ConformanceRunner {
pub fn new() -> Self {
Self {
tests: Vec::new(),
context: ConformanceContext::default(),
}
}
pub fn with_context(context: ConformanceContext) -> Self {
Self {
tests: Vec::new(),
context,
}
}
pub fn register_test<T: ConformanceTest + 'static>(&mut self, test: T) {
self.tests.push(Box::new(test));
}
pub fn register_tests<I, T>(&mut self, tests: I)
where
I: IntoIterator<Item = T>,
T: ConformanceTest + 'static,
{
for test in tests {
self.register_test(test);
}
}
pub fn run_all_tests(&self) -> Vec<TestExecution> {
self.run_tests_by_filter(|_| true)
}
pub fn run_section_tests(&self, section: &str) -> Vec<TestExecution> {
self.run_tests_by_filter(|test| test.section() == section)
}
pub fn run_level_tests(&self, level: RequirementLevel) -> Vec<TestExecution> {
self.run_tests_by_filter(|test| test.requirement_level() == level)
}
pub fn run_category_tests(&self, category: TestCategory) -> Vec<TestExecution> {
self.run_tests_by_filter(|test| test.category() == category)
}
pub fn run_tests_by_filter<F>(&self, filter: F) -> Vec<TestExecution>
where
F: Fn(&dyn ConformanceTest) -> bool,
{
let mut executions = Vec::new();
for test in &self.tests {
if filter(test.as_ref()) {
let start = Instant::now();
let timestamp = std::time::SystemTime::now();
if self.context.verbose {
eprintln!("Running conformance test: {}", test.name());
}
let test_result = test.run(&self.context);
let duration = start.elapsed();
if self.context.verbose {
eprintln!(" Result: {}", test_result.description());
eprintln!(" Duration: {duration:?}");
}
let evidence = EvidenceMetadata::from_test_and_result(test.as_ref(), &test_result);
executions.push(TestExecution {
test_name: test.name(),
rfc_clause: test.rfc_clause().to_string(),
section: test.section().to_string(),
level: test.requirement_level(),
category: test.category(),
description: test.description().to_string(),
result: test_result,
evidence,
duration,
timestamp,
});
}
}
executions
}
pub fn test_count(&self) -> usize {
self.tests.len()
}
pub fn test_count_by_level(&self, level: RequirementLevel) -> usize {
self.tests
.iter()
.filter(|test| test.requirement_level() == level)
.count()
}
pub fn test_names(&self) -> Vec<String> {
self.tests.iter().map(|test| test.name()).collect()
}
}
impl Default for ConformanceRunner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionCoverage {
pub section: String,
pub title: String,
pub must_total: usize,
pub must_passing: usize,
pub should_total: usize,
pub should_passing: usize,
pub may_total: usize,
pub may_passing: usize,
pub score: f64,
pub status: ConformanceStatus,
}
impl SectionCoverage {
fn calculate_score(&mut self) {
let weighted_total = (self.must_total * 2) + self.should_total;
let weighted_passing = (self.must_passing * 2) + self.should_passing;
self.score = if weighted_total > 0 {
weighted_passing as f64 / weighted_total as f64
} else {
1.0 };
self.status = if self.must_total > 0 {
let must_score = self.must_passing as f64 / self.must_total as f64;
if must_score >= 0.95 {
ConformanceStatus::Conformant
} else if must_score >= 0.80 {
ConformanceStatus::PartiallyConformant
} else {
ConformanceStatus::NonConformant
}
} else {
ConformanceStatus::Conformant
};
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConformanceStatus {
Conformant,
PartiallyConformant,
NonConformant,
}
impl fmt::Display for ConformanceStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConformanceStatus::Conformant => write!(f, "✅ Conformant"),
ConformanceStatus::PartiallyConformant => write!(f, "⚠️ Partially Conformant"),
ConformanceStatus::NonConformant => write!(f, "❌ Non-Conformant"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoverageMatrix {
pub sections: BTreeMap<String, SectionCoverage>,
pub overall: OverallCoverage,
pub generated_at: std::time::SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverallCoverage {
pub total_requirements: usize,
pub must_requirements: usize,
pub should_requirements: usize,
pub may_requirements: usize,
pub passing_requirements: usize,
pub must_passing: usize,
pub should_passing: usize,
pub may_passing: usize,
pub failed_requirements: usize,
pub must_failed: usize,
pub should_failed: usize,
pub may_failed: usize,
pub skipped_requirements: usize,
pub conformance_score: f64,
pub conformance_status: ConformanceStatus,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct EvidenceSummary {
pub live_checked: usize,
pub fixture_only: usize,
pub blocked: usize,
pub unsupported: usize,
pub expected_fail: usize,
pub failed: usize,
pub passed: usize,
pub skipped: usize,
}
impl EvidenceSummary {
pub fn from_executions(executions: &[TestExecution]) -> Self {
let mut summary = Self::default();
for execution in executions {
match execution.evidence.evidence_kind {
EvidenceKind::LiveChecked => summary.live_checked += 1,
EvidenceKind::FixtureOnly => summary.fixture_only += 1,
EvidenceKind::Blocked => summary.blocked += 1,
EvidenceKind::Unsupported => summary.unsupported += 1,
EvidenceKind::ExpectedFail => summary.expected_fail += 1,
EvidenceKind::Failed => summary.failed += 1,
}
match execution.evidence.test_status {
TestStatus::Pass => summary.passed += 1,
TestStatus::Skip => summary.skipped += 1,
TestStatus::ExpectedFail
| TestStatus::Fail
| TestStatus::Blocked
| TestStatus::Unsupported => {}
}
}
summary
}
}
impl CoverageMatrix {
pub fn from_results(results: &[TestExecution]) -> Self {
let mut sections: BTreeMap<String, SectionCoverage> = BTreeMap::new();
let rfc_sections = load_rfc_section_metadata();
for (section_id, title) in rfc_sections {
sections.insert(
section_id.clone(),
SectionCoverage {
section: section_id,
title,
must_total: 0,
must_passing: 0,
should_total: 0,
should_passing: 0,
may_total: 0,
may_passing: 0,
score: 0.0,
status: ConformanceStatus::NonConformant,
},
);
}
for execution in results {
if let Some(section_coverage) = sections.get_mut(&execution.section) {
match execution.level {
RequirementLevel::Must => {
section_coverage.must_total += 1;
if execution.result.is_passing() {
section_coverage.must_passing += 1;
}
}
RequirementLevel::Should => {
section_coverage.should_total += 1;
if execution.result.is_passing() {
section_coverage.should_passing += 1;
}
}
RequirementLevel::May => {
section_coverage.may_total += 1;
if execution.result.is_passing() {
section_coverage.may_passing += 1;
}
}
}
}
}
for section_coverage in sections.values_mut() {
section_coverage.calculate_score();
}
let overall = calculate_overall_coverage(§ions);
Self {
sections,
overall,
generated_at: std::time::SystemTime::now(),
}
}
pub fn overall_score(&self) -> f64 {
self.overall.conformance_score
}
pub fn overall_status(&self) -> ConformanceStatus {
self.overall.conformance_status
}
pub fn meets_conformance_threshold(&self) -> bool {
matches!(
self.overall.conformance_status,
ConformanceStatus::Conformant
)
}
pub fn failing_sections(&self) -> Vec<&SectionCoverage> {
self.sections
.values()
.filter(|section| matches!(section.status, ConformanceStatus::NonConformant))
.collect()
}
}
fn calculate_overall_coverage(sections: &BTreeMap<String, SectionCoverage>) -> OverallCoverage {
let mut overall = OverallCoverage {
total_requirements: 0,
must_requirements: 0,
should_requirements: 0,
may_requirements: 0,
passing_requirements: 0,
must_passing: 0,
should_passing: 0,
may_passing: 0,
failed_requirements: 0,
must_failed: 0,
should_failed: 0,
may_failed: 0,
skipped_requirements: 0,
conformance_score: 0.0,
conformance_status: ConformanceStatus::NonConformant,
};
for section in sections.values() {
overall.must_requirements += section.must_total;
overall.should_requirements += section.should_total;
overall.may_requirements += section.may_total;
overall.must_passing += section.must_passing;
overall.should_passing += section.should_passing;
overall.may_passing += section.may_passing;
overall.must_failed += section.must_total - section.must_passing;
overall.should_failed += section.should_total - section.should_passing;
overall.may_failed += section.may_total - section.may_passing;
}
overall.total_requirements =
overall.must_requirements + overall.should_requirements + overall.may_requirements;
overall.passing_requirements =
overall.must_passing + overall.should_passing + overall.may_passing;
overall.failed_requirements = overall.must_failed + overall.should_failed + overall.may_failed;
let weighted_total = (overall.must_requirements * 2) + overall.should_requirements;
let weighted_passing = (overall.must_passing * 2) + overall.should_passing;
overall.conformance_score = if weighted_total > 0 {
weighted_passing as f64 / weighted_total as f64
} else {
1.0
};
let must_score = if overall.must_requirements > 0 {
overall.must_passing as f64 / overall.must_requirements as f64
} else {
1.0
};
overall.conformance_status = if must_score >= 0.95 {
ConformanceStatus::Conformant
} else if must_score >= 0.80 {
ConformanceStatus::PartiallyConformant
} else {
ConformanceStatus::NonConformant
};
overall
}
fn load_rfc_section_metadata() -> Vec<(String, String)> {
vec![
("4.1".to_string(), "Objects and Source Blocks".to_string()),
("4.2".to_string(), "Encoding Process".to_string()),
("4.3".to_string(), "Decoding Process".to_string()),
("5.1".to_string(), "Systematic Index".to_string()),
("5.2".to_string(), "Parameter Derivation".to_string()),
("5.3".to_string(), "Tuple Generation".to_string()),
("5.4".to_string(), "Constraint Matrix Structure".to_string()),
("5.5".to_string(), "Lookup Tables".to_string()),
]
}
#[derive(Debug, Serialize)]
pub struct ConformanceLogEntry {
pub timestamp: String,
pub clause_id: String,
pub rfc_clause: String,
pub section: String,
pub requirement_level: RequirementLevel,
pub level: RequirementLevel,
pub evidence_kind: EvidenceKind,
pub test_status: TestStatus,
pub status: String,
pub command: String,
pub duration_ms: u64,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocker_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fixture_reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub production_seam_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discrepancy_id: Option<String>,
}
pub fn generate_jsonl_logs(executions: &[TestExecution]) -> String {
generate_jsonl_logs_with_command(executions, "raptorq_rfc6330_conformance")
}
pub fn generate_jsonl_logs_with_command(executions: &[TestExecution], command: &str) -> String {
let mut logs = String::new();
for execution in executions {
let entry = ConformanceLogEntry {
timestamp: execution
.timestamp
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
.to_string(),
clause_id: execution.rfc_clause.clone(),
rfc_clause: execution.rfc_clause.clone(),
section: execution.section.clone(),
requirement_level: execution.level,
level: execution.level,
evidence_kind: execution.evidence.evidence_kind,
test_status: execution.evidence.test_status,
status: match &execution.result {
ConformanceResult::Pass => "PASS".to_string(),
ConformanceResult::Fail { .. } => "FAIL".to_string(),
ConformanceResult::Skipped { .. } => "SKIP".to_string(),
ConformanceResult::ExpectedFailure { .. } => "XFAIL".to_string(),
ConformanceResult::Blocked { .. } => "BLOCKED".to_string(),
ConformanceResult::Unsupported { .. } => "UNSUPPORTED".to_string(),
},
command: command.to_string(),
duration_ms: execution.duration.as_millis() as u64,
description: execution.description.clone(),
blocker_id: execution.evidence.blocker_id.clone(),
fixture_reference: execution.evidence.fixture_reference.clone(),
production_seam_path: execution.evidence.production_seam_path.clone(),
failure_reason: match &execution.result {
ConformanceResult::Fail { reason, .. } => Some(reason.clone()),
ConformanceResult::Skipped { reason } => Some(reason.clone()),
ConformanceResult::ExpectedFailure { reason, .. } => Some(reason.clone()),
ConformanceResult::Blocked { reason, .. } => Some(reason.clone()),
ConformanceResult::Unsupported { reason, .. } => Some(reason.clone()),
_ => None,
},
discrepancy_id: match &execution.result {
ConformanceResult::ExpectedFailure { discrepancy_id, .. } => {
Some(discrepancy_id.clone())
}
_ => None,
},
};
if let Ok(json) = serde_json::to_string(&entry) {
logs.push_str(&json);
logs.push('\n');
}
}
logs
}