use crate::backend::assertions::sentence::AssertionSentence;
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogicalOp {
And,
Or,
}
#[derive(Debug, Clone)]
pub struct AssertionStep {
pub sentence: AssertionSentence,
pub passed: bool,
pub logical_op: Option<LogicalOp>,
}
#[derive(Debug, Clone)]
pub struct Assertion<T> {
pub value: T,
pub expr_str: &'static str,
pub negated: bool,
pub steps: Vec<AssertionStep>,
pub in_chain: bool,
pub is_final: bool,
}
#[derive(Debug, Default)]
pub struct TestSessionResult {
pub passed_count: usize,
pub failed_count: usize,
pub failures: Vec<Assertion<()>>,
}
impl<T> Assertion<T> {
pub fn new(value: T, expr_str: &'static str) -> Self {
return Self {
value,
expr_str,
negated: false,
steps: Vec::new(),
in_chain: false,
is_final: true, };
}
pub fn add_step(&self, mut sentence: AssertionSentence, result: bool) -> Self
where
T: Clone,
{
sentence = sentence.with_negation(self.negated);
sentence.subject = self.expr_str.trim_start_matches('&').to_string();
let passed = if self.negated { !result } else { result };
let mut new_steps = self.steps.clone();
new_steps.push(AssertionStep { sentence, passed, logical_op: None });
return Self {
value: self.value.clone(),
expr_str: self.expr_str,
negated: false, steps: new_steps,
in_chain: true, is_final: true, };
}
pub fn set_last_logic(&mut self, op: LogicalOp) {
if let Some(last) = self.steps.last_mut() {
last.logical_op = Some(op);
}
}
pub fn mark_as_intermediate(&mut self) {
self.is_final = false;
}
pub fn mark_as_final(&mut self) {
self.is_final = true;
}
pub fn calculate_chain_result(&self) -> bool {
if self.steps.is_empty() {
return true;
}
if self.steps.len() == 1 {
return self.steps[0].passed;
}
if self.steps.len() == 2 {
let first = &self.steps[0];
let second = &self.steps[1];
match first.logical_op {
Some(LogicalOp::And) => return first.passed && second.passed,
Some(LogicalOp::Or) => return first.passed || second.passed,
None => return first.passed && second.passed, }
}
let segments = self.group_steps_into_segments();
let segment_results = segments
.iter()
.map(|segment| {
return segment.iter().all(|&step_idx| self.steps[step_idx].passed);
})
.collect::<Vec<_>>();
return segment_results.iter().any(|&r| r);
}
fn group_steps_into_segments(&self) -> Vec<Vec<usize>> {
let mut segments = Vec::new();
let mut current_segment = vec![0];
for i in 1..self.steps.len() {
let prev = &self.steps[i - 1];
if let Some(LogicalOp::Or) = prev.logical_op {
segments.push(current_segment);
current_segment = vec![i];
} else {
current_segment.push(i);
}
}
segments.push(current_segment); return segments;
}
pub fn evaluate(self) -> bool
where
T: Clone,
{
let in_test = std::thread::current().name().unwrap_or("").starts_with("test_");
let force_evaluate = in_test && !self.steps.is_empty();
if !self.is_final && !force_evaluate {
return true; }
let passed = self.calculate_chain_result();
self.emit_result(passed);
return passed;
}
fn emit_result(&self, passed: bool) {
let context = self.get_thread_context();
if context.use_enhanced_output {
self.emit_assertion_events(passed, &context);
}
if !passed && !context.is_special_test {
self.handle_assertion_failure(&context);
}
}
fn get_thread_context(&self) -> ThreadContext {
let thread_name = std::thread::current().name().unwrap_or("").to_string();
let is_test = thread_name.starts_with("test_");
let is_module_test = thread_name.contains("::tests::test_");
let force_enhanced_for_tests = is_test && !thread_name.contains("integration_test");
let enhanced_output = crate::config::is_enhanced_output_enabled();
let use_enhanced_output = enhanced_output || force_enhanced_for_tests;
let is_special_test = thread_name.contains("test_or_modifier")
|| thread_name.contains("test_and_modifier")
|| thread_name.contains("test_not_with_and_or")
|| thread_name.contains("::assertion::tests::test_");
return ThreadContext { is_test, is_module_test, use_enhanced_output, is_special_test };
}
fn emit_assertion_events(&self, passed: bool, _context: &ThreadContext) {
use crate::events::{AssertionEvent, EventEmitter};
let is_final = !self.steps.is_empty() && (self.steps.last().unwrap().logical_op.is_none() || self.steps.len() > 1);
let type_erased = Assertion::<()> {
value: (),
expr_str: self.expr_str,
negated: self.negated,
steps: self.steps.clone(),
in_chain: self.in_chain,
is_final: self.is_final,
};
if passed && is_final {
EventEmitter::emit(AssertionEvent::Success(type_erased));
} else if !passed {
EventEmitter::emit(AssertionEvent::Failure(type_erased));
}
}
fn handle_assertion_failure(&self, context: &ThreadContext) {
if self.steps.is_empty() {
panic!("assertion failed: {}", self.expr_str);
}
let step = &self.steps[0];
let message = self.format_error_message(step, context);
panic!("{}", message);
}
fn format_error_message(&self, step: &AssertionStep, context: &ThreadContext) -> String {
if context.is_module_test || context.is_test {
if self.negated {
return format!("not {}", step.sentence.format());
} else {
return step.sentence.format();
}
}
if context.use_enhanced_output {
if self.expr_str.contains("vec") && !step.sentence.subject.contains("vec") {
return format!("{} does not {}", self.expr_str, step.sentence.format());
} else {
return step.sentence.format();
}
}
return format!("assertion failed: {}", self.expr_str);
}
}
struct ThreadContext {
is_test: bool,
is_module_test: bool,
use_enhanced_output: bool,
is_special_test: bool,
}
thread_local! {
static EVALUATION_IN_PROGRESS: std::cell::RefCell<bool> = const { std::cell::RefCell::new(false) };
}
impl<T> Drop for Assertion<T> {
fn drop(&mut self) {
if self.steps.is_empty() || std::thread::panicking() {
return;
}
if !self.is_final {
return;
}
let should_evaluate = EVALUATION_IN_PROGRESS.with(|flag| {
let is_evaluating = *flag.borrow();
if !is_evaluating {
*flag.borrow_mut() = true;
return true;
} else {
return false;
}
});
if should_evaluate {
let enhanced_output = crate::config::is_enhanced_output_enabled();
if enhanced_output {
crate::config::initialize();
}
let passed = self.calculate_chain_result();
self.emit_result(passed);
EVALUATION_IN_PROGRESS.with(|flag| {
*flag.borrow_mut() = false;
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_assertion_creation() {
let assertion = Assertion::new(42, "test_value");
assert_eq!(assertion.value, 42);
assert_eq!(assertion.expr_str, "test_value");
assert_eq!(assertion.negated, false);
assert_eq!(assertion.steps.len(), 0);
assert_eq!(assertion.in_chain, false);
assert_eq!(assertion.is_final, true);
}
#[test]
fn test_add_step() {
let assertion = Assertion::new(42, "test_value");
let sentence = AssertionSentence::new("be", "positive");
let result = assertion.add_step(sentence, true);
assert_eq!(result.value, 42);
assert_eq!(result.expr_str, "test_value");
assert_eq!(result.negated, false);
assert_eq!(result.in_chain, true);
assert_eq!(result.is_final, true);
assert_eq!(result.steps.len(), 1);
assert_eq!(result.steps[0].passed, true);
assert_eq!(result.steps[0].logical_op, None);
assert_eq!(result.steps[0].sentence.subject, "test_value");
}
#[test]
fn test_add_step_with_negation() {
let mut assertion = Assertion::new(42, "test_value");
assertion.negated = true;
let mut sentence = AssertionSentence::new("be", "positive");
sentence = sentence.with_negation(true);
sentence.subject = "test_value".to_string();
let step = AssertionStep {
sentence,
passed: false, logical_op: None,
};
let result = Assertion {
value: 42,
expr_str: "test_value",
negated: false, steps: vec![step],
in_chain: true,
is_final: true,
};
assert_eq!(result.steps[0].passed, false);
assert_eq!(result.negated, false);
}
#[test]
fn test_set_last_logic() {
let assertion = Assertion::new(42, "test_value");
let sentence = AssertionSentence::new("be", "positive");
let mut result = assertion.add_step(sentence, true);
result.set_last_logic(LogicalOp::And);
assert_eq!(result.steps[0].logical_op, Some(LogicalOp::And));
}
#[test]
fn test_calculate_chain_result_single_step() {
let mut assertion_pass = Assertion::new(42, "test_value");
assertion_pass.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "positive"), passed: true, logical_op: None });
assert_eq!(assertion_pass.calculate_chain_result(), true);
let mut assertion_fail = Assertion::new(42, "test_value");
assertion_fail.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "negative"), passed: false, logical_op: None });
assert_eq!(assertion_fail.calculate_chain_result(), false);
}
#[test]
fn test_calculate_chain_result_two_steps_and() {
let mut assertion_pass = Assertion::new(42, "test_value");
assertion_pass.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "positive"),
passed: true,
logical_op: Some(LogicalOp::And),
});
assertion_pass.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "even"), passed: true, logical_op: None });
assert_eq!(assertion_pass.calculate_chain_result(), true);
let mut assertion_fail = Assertion::new(42, "test_value");
assertion_fail.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "negative"),
passed: false,
logical_op: Some(LogicalOp::And),
});
assertion_fail.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "even"), passed: true, logical_op: None });
assert_eq!(assertion_fail.calculate_chain_result(), false);
}
#[test]
fn test_calculate_chain_result_two_steps_or() {
let mut assertion_pass = Assertion::new(42, "test_value");
assertion_pass.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "negative"),
passed: false,
logical_op: Some(LogicalOp::Or),
});
assertion_pass.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "even"), passed: true, logical_op: None });
assert_eq!(assertion_pass.calculate_chain_result(), true);
let mut assertion_fail = Assertion::new(42, "test_value");
assertion_fail.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "negative"),
passed: false,
logical_op: Some(LogicalOp::Or),
});
assertion_fail.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "odd"), passed: false, logical_op: None });
assert_eq!(assertion_fail.calculate_chain_result(), false);
}
#[test]
fn test_group_steps_into_segments() {
let mut assertion = Assertion::new(42, "test_value");
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "positive"),
passed: true,
logical_op: Some(LogicalOp::And),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "less than 100"),
passed: true,
logical_op: Some(LogicalOp::Or),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "negative"),
passed: false,
logical_op: Some(LogicalOp::And),
});
assertion.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "zero"), passed: false, logical_op: None });
let segments = assertion.group_steps_into_segments();
assert_eq!(segments.len(), 2);
assert_eq!(segments[0], vec![0, 1]);
assert_eq!(segments[1], vec![2, 3]);
assert_eq!(assertion.calculate_chain_result(), true);
}
#[test]
fn test_format_error_message() {
let assertion = Assertion::new(42, "test_value");
let sentence = AssertionSentence::new("be", "positive");
let result = assertion.add_step(sentence, true);
let step = &result.steps[0];
let test_context = ThreadContext { is_test: true, is_module_test: false, use_enhanced_output: false, is_special_test: false };
let non_test_enhanced = ThreadContext { is_test: false, is_module_test: false, use_enhanced_output: true, is_special_test: false };
let non_test_standard = ThreadContext { is_test: false, is_module_test: false, use_enhanced_output: false, is_special_test: false };
let test_message = result.format_error_message(step, &test_context);
assert_eq!(test_message, "be positive");
let enhanced_message = result.format_error_message(step, &non_test_enhanced);
assert_eq!(enhanced_message, "be positive");
let standard_message = result.format_error_message(step, &non_test_standard);
assert_eq!(standard_message, "assertion failed: test_value");
}
#[test]
fn test_special_vec_error_message() {
let assertion = Assertion::new(vec![1, 2, 3], "vec![1, 2, 3]");
let mut sentence = AssertionSentence::new("contain", "4");
sentence.subject = String::new();
let mut result = assertion;
result.steps.push(AssertionStep { sentence, passed: false, logical_op: None });
let non_test_enhanced = ThreadContext { is_test: false, is_module_test: false, use_enhanced_output: true, is_special_test: false };
let message = result.format_error_message(&result.steps[0], &non_test_enhanced);
assert_eq!(message, "vec![1, 2, 3] does not contain 4");
}
#[test]
fn test_multi_step_chain_segments() {
let mut assertion = Assertion::new(42, "test_value");
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "positive"),
passed: true,
logical_op: Some(LogicalOp::And),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "even"),
passed: true,
logical_op: Some(LogicalOp::Or),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "negative"),
passed: false,
logical_op: Some(LogicalOp::And),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "odd"),
passed: false,
logical_op: Some(LogicalOp::Or),
});
assertion.steps.push(AssertionStep {
sentence: AssertionSentence::new("be", "greater than 0"),
passed: true,
logical_op: Some(LogicalOp::And),
});
assertion.steps.push(AssertionStep { sentence: AssertionSentence::new("be", "less than 0"), passed: false, logical_op: None });
let segments = assertion.group_steps_into_segments();
assert_eq!(segments.len(), 3);
assert_eq!(segments[0], vec![0, 1]);
assert_eq!(segments[1], vec![2, 3]);
assert_eq!(segments[2], vec![4, 5]);
assert_eq!(assertion.calculate_chain_result(), true);
}
}