use super::common::{create_binary_op_ast, create_cell_ref_ast};
use crate::engine::{Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::{ASTNode, ASTNodeType};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::Duration;
#[test]
fn test_cancellation_between_layers() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
let a1_ref = create_cell_ref_ast(None, 1, 1);
let one = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(1)),
source_token: None,
contains_volatile: false,
};
let b1_formula = create_binary_op_ast(a1_ref, one.clone(), "+");
engine.set_cell_formula("Sheet1", 1, 2, b1_formula).unwrap();
let b1_ref = create_cell_ref_ast(None, 1, 2);
let c1_formula = create_binary_op_ast(b1_ref, one.clone(), "+");
engine.set_cell_formula("Sheet1", 1, 3, c1_formula).unwrap();
let c1_ref = create_cell_ref_ast(None, 1, 3);
let d1_formula = create_binary_op_ast(c1_ref, one, "+");
engine.set_cell_formula("Sheet1", 1, 4, d1_formula).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag_clone = Arc::clone(&cancel_flag);
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(1));
cancel_flag_clone.store(true, Ordering::Relaxed);
});
let result = engine.evaluate_all_cancellable(cancel_flag);
handle.join().unwrap();
match result {
Err(ExcelError {
kind: ExcelErrorKind::Cancelled,
..
}) => {
}
Ok(_) => {
assert_eq!(
engine.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(2.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3),
Some(LiteralValue::Number(3.0))
);
assert_eq!(
engine.get_cell_value("Sheet1", 1, 4),
Some(LiteralValue::Number(4.0))
);
}
Err(other_error) => {
panic!("Expected cancellation error, got: {other_error:?}");
}
}
}
#[test]
fn test_cancellation_within_large_layer() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
let one = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(1)),
source_token: None,
contains_volatile: false,
};
for row in 2..=301 {
engine
.set_cell_formula("Sheet1", row, 1, one.clone())
.unwrap();
}
let cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag_clone = Arc::clone(&cancel_flag);
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(5)); cancel_flag_clone.store(true, Ordering::Relaxed);
});
let result = engine.evaluate_all_cancellable(cancel_flag);
handle.join().unwrap();
match result {
Err(ExcelError {
kind: ExcelErrorKind::Cancelled,
..
}) => {
}
Ok(eval_result) => {
assert!(eval_result.computed_vertices > 0);
assert_eq!(eval_result.cycle_errors, 0);
}
Err(other_error) => {
panic!("Expected cancellation error or success, got: {other_error:?}");
}
}
}
#[test]
fn test_cancellation_in_demand_driven_evaluation() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
let a1_ref = create_cell_ref_ast(None, 1, 1);
engine.set_cell_formula("Sheet1", 1, 2, a1_ref).unwrap();
let b1_ref = create_cell_ref_ast(None, 1, 2);
engine.set_cell_formula("Sheet1", 1, 3, b1_ref).unwrap();
let c1_ref = create_cell_ref_ast(None, 1, 3);
engine.set_cell_formula("Sheet1", 1, 4, c1_ref).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag_clone = Arc::clone(&cancel_flag);
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(1));
cancel_flag_clone.store(true, Ordering::Relaxed);
});
let result = engine.evaluate_until_cancellable(&["D1"], cancel_flag);
handle.join().unwrap();
match result {
Err(ExcelError {
kind: ExcelErrorKind::Cancelled,
..
}) => {
}
Ok(_) => {
assert_eq!(
engine.get_cell_value("Sheet1", 1, 4),
Some(LiteralValue::Number(1.0))
);
}
Err(other_error) => {
panic!("Expected cancellation error, got: {other_error:?}");
}
}
}
#[test]
fn test_cancellation_during_cycle_handling() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
let b1_ref = create_cell_ref_ast(None, 1, 2);
engine.set_cell_formula("Sheet1", 1, 1, b1_ref).unwrap();
let a1_ref = create_cell_ref_ast(None, 1, 1);
engine.set_cell_formula("Sheet1", 1, 2, a1_ref).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(true));
let result = engine.evaluate_all_cancellable(cancel_flag);
match result {
Err(ExcelError {
kind: ExcelErrorKind::Cancelled,
..
}) => {
}
Ok(_) => {
panic!("Expected cancellation, but evaluation completed");
}
Err(other_error) => {
panic!("Expected cancellation error, got: {other_error:?}");
}
}
}
#[test]
fn test_non_cancelled_evaluation_works_normally() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
let a1_ref = create_cell_ref_ast(None, 1, 1);
let five = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(5)),
source_token: None,
contains_volatile: false,
};
let b1_formula = create_binary_op_ast(a1_ref, five, "+");
engine.set_cell_formula("Sheet1", 1, 2, b1_formula).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(false));
let result = engine.evaluate_all_cancellable(cancel_flag).unwrap();
assert_eq!(result.computed_vertices, 1); assert_eq!(result.cycle_errors, 0);
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(15.0))
);
}
#[test]
fn test_demand_driven_non_cancelled_works_normally() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(100))
.unwrap();
let a1_ref = create_cell_ref_ast(None, 1, 1);
engine.set_cell_formula("Sheet1", 1, 2, a1_ref).unwrap();
let b1_ref = create_cell_ref_ast(None, 1, 2);
let ten = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(10)),
source_token: None,
contains_volatile: false,
};
let c1_formula = create_binary_op_ast(b1_ref, ten, "*");
engine.set_cell_formula("Sheet1", 1, 3, c1_formula).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(false));
let result = engine
.evaluate_until_cancellable(&["C1"], cancel_flag)
.unwrap();
assert_eq!(result.computed_vertices, 2); assert_eq!(result.cycle_errors, 0);
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3),
Some(LiteralValue::Number(1000.0))
);
}
#[test]
fn test_cancellation_message_differentiation() {
let workbook = TestWorkbook::new();
let config = EvalConfig::default();
let mut engine = Engine::new(workbook, config);
let one = ASTNode {
node_type: ASTNodeType::Literal(LiteralValue::Int(1)),
source_token: None,
contains_volatile: false,
};
engine.set_cell_formula("Sheet1", 1, 1, one).unwrap();
let cancel_flag = Arc::new(AtomicBool::new(true));
let result = engine.evaluate_all_cancellable(cancel_flag);
match result {
Err(ExcelError {
kind: ExcelErrorKind::Cancelled,
message: Some(msg),
..
}) => {
assert!(
msg.contains("before scheduling")
|| msg.contains("between layers")
|| msg.contains("cycle handling")
|| msg.contains("within layer")
|| msg.contains("before starting")
|| msg.contains("during execution")
);
}
_ => panic!("Expected cancellation error with message"),
}
}