use crate::assertion_types::{
AntiPatternAssertion, Assertion, AssertionResult, AssertionType, AssertionValue,
CriticalPathAssertion, MemoryUsageAssertion, SpanCountAssertion,
};
use crate::unified_trace::UnifiedTrace;
pub struct AssertionEngine {}
impl AssertionEngine {
pub fn new() -> Self {
Self {}
}
pub fn evaluate(&self, assertion: &Assertion, trace: &UnifiedTrace) -> AssertionResult {
if !assertion.enabled {
return AssertionResult::pass(assertion.name.clone(), "Assertion disabled".to_string());
}
contract_pre_error_handling!();
match &assertion.assertion_type {
AssertionType::CriticalPath(cp) => {
self.evaluate_critical_path(&assertion.name, cp, trace)
}
AssertionType::AntiPattern(ap) => {
self.evaluate_anti_pattern(&assertion.name, ap, trace)
}
AssertionType::SpanCount(sc) => self.evaluate_span_count(&assertion.name, sc, trace),
AssertionType::MemoryUsage(mu) => {
self.evaluate_memory_usage(&assertion.name, mu, trace)
}
AssertionType::Custom(c) => {
AssertionResult::pass(
assertion.name.clone(),
format!("Custom assertion '{}' not yet implemented", c.expression),
)
}
}
}
fn evaluate_critical_path(
&self,
name: &str,
assertion: &CriticalPathAssertion,
trace: &UnifiedTrace,
) -> AssertionResult {
let duration_ms = if trace.syscall_spans.is_empty() {
0
} else {
let total_duration_nanos: u64 =
trace.syscall_spans.iter().map(|span| span.duration_nanos).sum();
total_duration_nanos / 1_000_000
};
if duration_ms <= assertion.max_duration_ms {
AssertionResult::pass(
name.to_string(),
format!(
"Critical path duration {}ms <= {}ms",
duration_ms, assertion.max_duration_ms
),
)
.with_values(
AssertionValue::Duration(duration_ms),
AssertionValue::Duration(assertion.max_duration_ms),
)
} else {
AssertionResult::fail(
name.to_string(),
format!(
"Critical path duration {}ms exceeds maximum {}ms",
duration_ms, assertion.max_duration_ms
),
)
.with_values(
AssertionValue::Duration(duration_ms),
AssertionValue::Duration(assertion.max_duration_ms),
)
}
}
fn evaluate_anti_pattern(
&self,
name: &str,
assertion: &AntiPatternAssertion,
_trace: &UnifiedTrace,
) -> AssertionResult {
AssertionResult::pass(
name.to_string(),
format!(
"Anti-pattern {:?} not detected (placeholder implementation)",
assertion.pattern
),
)
}
fn evaluate_span_count(
&self,
name: &str,
assertion: &SpanCountAssertion,
trace: &UnifiedTrace,
) -> AssertionResult {
let span_count = trace.syscall_spans.len();
if span_count <= assertion.max_spans {
AssertionResult::pass(
name.to_string(),
format!("Span count {} <= {}", span_count, assertion.max_spans),
)
.with_values(
AssertionValue::Count(span_count),
AssertionValue::Count(assertion.max_spans),
)
} else {
AssertionResult::fail(
name.to_string(),
format!("Span count {} exceeds maximum {}", span_count, assertion.max_spans),
)
.with_values(
AssertionValue::Count(span_count),
AssertionValue::Count(assertion.max_spans),
)
}
}
fn evaluate_memory_usage(
&self,
name: &str,
assertion: &MemoryUsageAssertion,
trace: &UnifiedTrace,
) -> AssertionResult {
let mut total_bytes = 0u64;
for span in &trace.syscall_spans {
if span.name == "mmap" || span.name == "brk" {
total_bytes += 4096; }
}
if total_bytes <= assertion.max_bytes {
AssertionResult::pass(
name.to_string(),
format!("Memory usage {} bytes <= {}", total_bytes, assertion.max_bytes),
)
.with_values(
AssertionValue::Bytes(total_bytes),
AssertionValue::Bytes(assertion.max_bytes),
)
} else {
AssertionResult::fail(
name.to_string(),
format!(
"Memory usage {} bytes exceeds maximum {}",
total_bytes, assertion.max_bytes
),
)
.with_values(
AssertionValue::Bytes(total_bytes),
AssertionValue::Bytes(assertion.max_bytes),
)
}
}
pub fn evaluate_all(
&self,
assertions: &[Assertion],
trace: &UnifiedTrace,
) -> Vec<AssertionResult> {
assertions.iter().map(|a| self.evaluate(a, trace)).collect()
}
pub fn has_failures(results: &[AssertionResult], assertions: &[Assertion]) -> bool {
results
.iter()
.zip(assertions.iter())
.any(|(result, assertion)| !result.passed && assertion.fail_on_violation)
}
}
impl Default for AssertionEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assertion_types::{CriticalPathAssertion, SpanCountAssertion};
use crate::trace_context::LamportClock;
use crate::unified_trace::SyscallSpan;
use std::borrow::Cow;
fn create_test_span(syscall_name: &str, start_ns: u64, duration_ns: u64) -> SyscallSpan {
let clock = LamportClock::new();
SyscallSpan::new(
1, Cow::Owned(syscall_name.to_string()),
vec![],
0, start_ns,
duration_ns,
None, &clock,
)
}
#[test]
fn test_evaluate_critical_path_pass() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 50_000_000));
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::CriticalPath(CriticalPathAssertion {
max_duration_ms: 100,
trace_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_evaluate_critical_path_fail() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 150_000_000));
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::CriticalPath(CriticalPathAssertion {
max_duration_ms: 100,
trace_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(!result.passed);
assert!(result.message.contains("exceeds maximum"));
}
#[test]
fn test_evaluate_span_count_pass() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
for i in 0..50 {
trace.add_syscall(create_test_span("write", i * 1000, 100)); }
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 100,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_evaluate_span_count_fail() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
for i in 0..150 {
trace.add_syscall(create_test_span("write", i * 1000, 100)); }
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 100,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(!result.passed);
}
#[test]
fn test_evaluate_disabled_assertion() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 150_000_000));
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::CriticalPath(CriticalPathAssertion {
max_duration_ms: 100,
trace_name_pattern: None,
}),
fail_on_violation: true,
enabled: false, };
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
assert_eq!(result.message, "Assertion disabled");
}
#[test]
fn test_evaluate_all() {
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 50_000_000));
let assertions = vec![
Assertion {
name: "critical_path".to_string(),
assertion_type: AssertionType::CriticalPath(CriticalPathAssertion {
max_duration_ms: 100,
trace_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
Assertion {
name: "span_count".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
];
let results = engine.evaluate_all(&assertions, &trace);
assert_eq!(results.len(), 2);
assert!(results[0].passed); assert!(results[1].passed); }
#[test]
fn test_has_failures() {
let results = vec![
AssertionResult::pass("test1".to_string(), "Passed".to_string()),
AssertionResult::fail("test2".to_string(), "Failed".to_string()),
];
let assertions = vec![
Assertion {
name: "test1".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
Assertion {
name: "test2".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
];
assert!(AssertionEngine::has_failures(&results, &assertions));
}
#[test]
fn test_no_failures_when_fail_on_violation_false() {
let results = vec![
AssertionResult::pass("test1".to_string(), "Passed".to_string()),
AssertionResult::fail("test2".to_string(), "Failed".to_string()),
];
let assertions = vec![
Assertion {
name: "test1".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
Assertion {
name: "test2".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: false, enabled: true,
},
];
assert!(!AssertionEngine::has_failures(&results, &assertions));
}
#[test]
fn test_evaluate_anti_pattern() {
use crate::assertion_types::{AntiPatternAssertion, AntiPatternType};
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 100_000));
let assertion = Assertion {
name: "test_anti_pattern".to_string(),
assertion_type: AssertionType::AntiPattern(AntiPatternAssertion {
pattern: AntiPatternType::GodProcess,
threshold: 0.8,
process_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_evaluate_memory_usage_pass() {
use crate::assertion_types::{MemoryTrackingMode, MemoryUsageAssertion};
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 100_000));
trace.add_syscall(create_test_span("read", 100_000, 100_000));
let assertion = Assertion {
name: "test_memory".to_string(),
assertion_type: AssertionType::MemoryUsage(MemoryUsageAssertion {
max_bytes: 1_000_000, tracking_mode: MemoryTrackingMode::Allocations,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_evaluate_memory_usage_with_mmap() {
use crate::assertion_types::{MemoryTrackingMode, MemoryUsageAssertion};
use std::borrow::Cow;
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
let clock = LamportClock::new();
let mmap_span =
SyscallSpan::new(1, Cow::Borrowed("mmap"), vec![], 0, 0, 1000, None, &clock);
trace.add_syscall(mmap_span);
let assertion = Assertion {
name: "test_memory".to_string(),
assertion_type: AssertionType::MemoryUsage(MemoryUsageAssertion {
max_bytes: 10_000, tracking_mode: MemoryTrackingMode::Allocations,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_evaluate_memory_usage_fail() {
use crate::assertion_types::{MemoryTrackingMode, MemoryUsageAssertion};
use std::borrow::Cow;
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
let clock = LamportClock::new();
for _i in 0..10 {
let mmap_span =
SyscallSpan::new(1, Cow::Borrowed("mmap"), vec![], 0, 0, 1000, None, &clock);
trace.add_syscall(mmap_span);
}
let assertion = Assertion {
name: "test_memory".to_string(),
assertion_type: AssertionType::MemoryUsage(MemoryUsageAssertion {
max_bytes: 1000, tracking_mode: MemoryTrackingMode::Allocations,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(!result.passed);
}
#[test]
fn test_evaluate_custom_assertion() {
use crate::assertion_types::CustomAssertion;
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
trace.add_syscall(create_test_span("write", 0, 100_000));
let assertion = Assertion {
name: "test_custom".to_string(),
assertion_type: AssertionType::Custom(CustomAssertion {
expression: "duration < 100ms".to_string(),
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
assert!(result.message.contains("not yet implemented"));
}
#[test]
fn test_evaluate_empty_trace() {
let engine = AssertionEngine::new();
let trace = UnifiedTrace::new(1, "test".to_string());
let assertion = Assertion {
name: "test_empty".to_string(),
assertion_type: AssertionType::CriticalPath(CriticalPathAssertion {
max_duration_ms: 100,
trace_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed); }
#[test]
fn test_default_trait() {
let engine: AssertionEngine = Default::default();
let trace = UnifiedTrace::new(1, "test".to_string());
let assertion = Assertion {
name: "test".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
#[test]
fn test_has_failures_with_all_passing() {
let results = vec![
AssertionResult::pass("test1".to_string(), "Passed".to_string()),
AssertionResult::pass("test2".to_string(), "Passed".to_string()),
];
let assertions = vec![
Assertion {
name: "test1".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
Assertion {
name: "test2".to_string(),
assertion_type: AssertionType::SpanCount(SpanCountAssertion {
max_spans: 10,
span_name_pattern: None,
}),
fail_on_violation: true,
enabled: true,
},
];
assert!(!AssertionEngine::has_failures(&results, &assertions));
}
#[test]
fn test_evaluate_brk_syscall() {
use crate::assertion_types::{MemoryTrackingMode, MemoryUsageAssertion};
use std::borrow::Cow;
let engine = AssertionEngine::new();
let mut trace = UnifiedTrace::new(1, "test".to_string());
let clock = LamportClock::new();
let brk_span = SyscallSpan::new(1, Cow::Borrowed("brk"), vec![], 0, 0, 1000, None, &clock);
trace.add_syscall(brk_span);
let assertion = Assertion {
name: "test_memory".to_string(),
assertion_type: AssertionType::MemoryUsage(MemoryUsageAssertion {
max_bytes: 10_000,
tracking_mode: MemoryTrackingMode::Allocations,
}),
fail_on_violation: true,
enabled: true,
};
let result = engine.evaluate(&assertion, &trace);
assert!(result.passed);
}
}