use super::*;
use crate::function::Function;
use crate::traits::{ArgumentHandle, CalcValue, FunctionContext};
use std::sync::Arc;
#[derive(Debug)]
struct Pause;
impl Function for Pause {
fn name(&self) -> &'static str {
"JOB_RESOURCE_PAUSE"
}
fn eval<'a, 'b, 'c>(
&self,
_: &'c [ArgumentHandle<'a, 'b>],
_: &dyn FunctionContext<'b>,
) -> Result<CalcValue<'b>, formualizer_common::ExcelError> {
std::thread::sleep(Duration::from_millis(10));
Ok(CalcValue::Scalar(LiteralValue::Number(1.)))
}
}
#[test]
fn job_deadline_accumulates_active_time_and_excludes_idle() {
if isolated_test("resources::job_deadline_accumulates_active_time_and_excludes_idle") {
return;
}
crate::function_registry::register_function(Arc::new(Pause));
let make = || {
let mut e = Engine::new(
TestWorkbook::new(),
EvalConfig {
family_execution: false,
enable_parallel: false,
..Default::default()
},
);
for c in 1..=100 {
e.set_cell_formula("Sheet1", 1, c, parse("=JOB_RESOURCE_PAUSE()").unwrap())
.unwrap();
}
e.resumable_stop_after_units_for_test = Some(1);
e
};
let mut idle = make();
idle.set_evaluation_resource_budgets(EvaluationBudgets {
deadline: DeadlineResourceBudget {
max_elapsed: Some(Duration::from_secs(1)),
},
..Default::default()
});
idle.begin_recalc(Default::default()).unwrap();
idle.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
idle.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
std::thread::sleep(Duration::from_millis(1100));
idle.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
idle.cancel_recalc();
let mut e = make();
e.set_evaluation_resource_budgets(EvaluationBudgets {
deadline: DeadlineResourceBudget {
max_elapsed: Some(Duration::from_millis(100)),
},
..Default::default()
});
e.begin_recalc(Default::default()).unwrap();
let mut calls = 0;
loop {
calls += 1;
match e.step_recalc(EvaluationStop::after(Duration::ZERO)) {
Err(RecalcJobError::Evaluation(error)) => {
assert!(matches!(
error.extra(),
formualizer_common::ExcelErrorExtra::Resource { .. }
));
break;
}
Err(error) => panic!("unexpected job error: {error}"),
Ok(report) => assert_ne!(report.phase, StepPhase::Finished),
}
assert!(calls < 100);
}
assert!(calls > 2);
assert!(!e.has_active_recalc_job());
}
#[test]
fn job_staged_cancel_is_a_soft_stop() {
if isolated_test("resources::job_staged_cancel_is_a_soft_stop") {
return;
}
crate::builtins::load_builtins();
for explicit in [false, true] {
let mut e = Engine::new(
TestWorkbook::new(),
EvalConfig {
defer_graph_building: true,
..Default::default()
},
);
e.stage_formula_text("Sheet1", 1, 1, "=ABS(-1)".into());
let token = CancelToken::new();
token.cancel();
if explicit {
e.begin_recalc(Default::default()).unwrap();
let report = e
.step_recalc(EvaluationStop::after(Duration::ZERO).with_cancel(token))
.unwrap();
assert_eq!(report.phase, StepPhase::Cancelled);
assert!(report.result.is_none());
assert!(!e.has_active_recalc_job());
} else {
assert!(matches!(
e.evaluate_all_until(EvaluationStop::cancel(token)).unwrap(),
EvaluationProgress::Incomplete(_)
));
assert!(matches!(
e.evaluate_all_until(EvaluationStop::none()).unwrap(),
EvaluationProgress::Complete(_)
));
assert_eq!(
e.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.))
);
}
}
}
#[test]
fn job_graph_build_and_scheduling_have_separate_boundaries() {
if isolated_test("resources::job_graph_build_and_scheduling_have_separate_boundaries") {
return;
}
crate::builtins::load_builtins();
let mut e = Engine::new(
TestWorkbook::new(),
EvalConfig {
defer_graph_building: true,
..Default::default()
},
);
for r in 1..=10 {
e.stage_formula_text(
"Sheet1",
r,
1,
if r == 1 {
"=ABS(-1)".into()
} else {
format!("={r}")
},
);
}
let sheet = e.graph.sheet_id("Sheet1").unwrap();
e.begin_recalc(Default::default()).unwrap();
assert!(e.has_pending_in_rect(sheet, 0, 0, 9, 0));
let build = e
.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
assert_eq!(build.phase, StepPhase::BuildingGraph);
assert_eq!(build.step_computed_vertices, 0);
assert_eq!(build.progress.staged_formulas_remaining, 0);
let plan = e
.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
assert_eq!(plan.phase, StepPhase::Scheduling);
assert_eq!(plan.step_computed_vertices, 0);
assert_eq!(plan.progress.scheduled_in_pass, 10);
assert!(!plan.progress.total_exact);
while e.has_active_recalc_job() {
e.step_recalc(EvaluationStop::after(Duration::ZERO))
.unwrap();
}
assert_eq!(
e.get_cell_value("Sheet1", 1, 1),
Some(LiteralValue::Number(1.))
);
}