use std::cell::Cell;
thread_local! {
static RUNS_LEFT: Cell<u32> = const { Cell::new(0) };
}
pub(crate) const PROBE_PANIC: &str = "unwind probe: injected panic after the steps of a run";
pub(crate) fn panic_after_run(nth: u32) {
RUNS_LEFT.with(|left| left.set(nth));
}
pub(crate) fn disarm() {
RUNS_LEFT.with(|left| left.set(0));
}
pub(crate) fn after_steps() {
let fire = RUNS_LEFT.with(|left| match left.get() {
0 => false,
1 => {
left.set(0);
true
}
n => {
left.set(n - 1);
false
}
});
if fire {
std::panic::panic_any(PROBE_PANIC);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
fn expect_probe_panic<T>(f: impl FnOnce() -> T) {
let outcome = catch_unwind(AssertUnwindSafe(f));
disarm();
let payload = match outcome {
Ok(_) => panic!("the probe did not fire"),
Err(payload) => payload,
};
assert_eq!(payload.downcast_ref::<&str>(), Some(&PROBE_PANIC));
}
#[test]
fn a_panicking_run_rolls_its_writes_back() {
let mut graph = TraversalGraph::new();
panic_after_run(1);
expect_probe_panic(|| {
graph
.traversal_mut()
.add_v("ghost")
.property("k", 1)
.to_list()
});
assert_eq!(graph.vertex_count(), 0);
graph.traversal_mut().add_v("kept").to_list().unwrap();
assert_eq!(graph.vertex_count(), 1);
assert_eq!(graph.undo_depth(), 0);
}
#[test]
fn a_panicking_execute_rolls_its_writes_back() {
let mut graph = TraversalGraph::new();
panic_after_run(1);
expect_probe_panic(|| graph.traversal_mut().add_v("ghost").execute());
assert_eq!(graph.vertex_count(), 0);
assert_eq!(graph.undo_depth(), 0);
}
#[test]
fn a_panic_in_a_transaction_rolls_the_whole_transaction_back() {
let mut graph = TraversalGraph::new();
panic_after_run(2);
expect_probe_panic(|| {
graph.transaction(|g| -> TraverserResult<()> {
g.traversal_mut().add_v("first").to_list()?;
g.traversal_mut().add_v("second").to_list()?;
Ok(())
})
});
assert_eq!(
graph.vertex_count(),
0,
"the committed savepoint is undone too"
);
assert_eq!(graph.undo_depth(), 0);
}
#[test]
fn a_panic_in_a_dry_run_discards_it() {
let mut graph = TraversalGraph::new();
panic_after_run(2);
expect_probe_panic(|| {
graph.dry_run(|g| -> TraverserResult<()> {
g.traversal_mut().add_v("first").to_list()?;
g.traversal_mut().add_v("second").to_list()?;
Ok(())
})
});
assert_eq!(graph.vertex_count(), 0);
assert_eq!(graph.undo_depth(), 0);
}
#[test]
fn an_unarmed_probe_changes_nothing() {
let mut graph = TraversalGraph::new();
graph.traversal_mut().add_v("a").to_list().unwrap();
assert_eq!(graph.vertex_count(), 1);
assert_eq!(graph.undo_depth(), 0);
}
#[cfg(feature = "script")]
mod script {
use super::*;
use crate::script::{ScriptLimits, eval_value_atomic, eval_value_dry_run};
use std::sync::Arc;
const SCRIPT: &str = r#"g.addV("ghost").next(); g.V().count().next()"#;
#[test]
fn a_panic_in_an_atomic_script_rolls_the_script_back() {
let graph = Arc::new(GraphSource::tinkerpop_modern());
panic_after_run(2);
expect_probe_panic(|| {
eval_value_atomic(
graph.clone(),
SCRIPT,
Default::default(),
&ScriptLimits::default(),
Arc::new(crate::graph::AllowAll),
)
});
let graph = graph.read();
assert_eq!(
graph.vertex_count(),
6,
"the first statement is rolled back"
);
assert_eq!(graph.undo_depth(), 0);
}
#[test]
fn a_panic_in_a_dry_run_script_leaves_the_graph_as_it_was() {
let graph = Arc::new(GraphSource::tinkerpop_modern());
panic_after_run(2);
expect_probe_panic(|| {
eval_value_dry_run(
graph.clone(),
SCRIPT,
Default::default(),
&ScriptLimits::default(),
Arc::new(crate::graph::AllowAll),
)
});
let graph = graph.read();
assert_eq!(graph.vertex_count(), 6);
assert_eq!(graph.undo_depth(), 0);
}
}
}