use apache_datasketches::tuple::generic::{TupleSketch, TupleSketchBuilder, TupleSummary};
const CHILD_ENV: &str = "TUPLE_GENERIC_PANIC_CHILD";
#[derive(Clone, Debug)]
struct Exploding;
impl TupleSummary for Exploding {
type Update = ();
fn create(_: &()) -> Self {
Exploding
}
fn union_combine(&mut self, _other: &Self) {
panic!("deliberate panic from union_combine");
}
fn intersection_combine(&mut self, _other: &Self) {
panic!("deliberate panic from intersection_combine");
}
}
#[test]
fn panicking_union_combine_aborts_with_a_diagnostic() {
if std::env::var(CHILD_ENV).is_ok() {
let mut s: TupleSketch<Exploding> = TupleSketchBuilder::new().build().unwrap();
s.update_u64(1, &());
s.update_u64(1, &());
unreachable!("the second update should have aborted the process");
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"panicking_union_combine_aborts_with_a_diagnostic",
"--nocapture",
])
.env(CHILD_ENV, "1")
.output()
.expect("failed to spawn the child test process");
assert!(
!output.status.success(),
"child was expected to abort, but exited successfully"
);
#[cfg(unix)]
assert!(
output.status.code().is_none(),
"child should have been killed by a signal (aborted), not exited \
with a status code; status was {:?}",
output.status
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("TupleSummary::union_combine"),
"abort message should name the panicking method; stderr was:\n{stderr}"
);
}
#[derive(Debug)]
struct ExplodesOnClone;
impl Clone for ExplodesOnClone {
fn clone(&self) -> Self {
panic!("deliberate panic from Clone::clone");
}
}
impl TupleSummary for ExplodesOnClone {
type Update = ();
fn create(_: &()) -> Self {
ExplodesOnClone
}
fn union_combine(&mut self, _other: &Self) {}
fn intersection_combine(&mut self, _other: &Self) {}
}
const CLONE_CHILD_ENV: &str = "TUPLE_GENERIC_PANIC_CLONE_CHILD";
#[test]
fn panicking_clone_aborts_with_a_diagnostic() {
if std::env::var(CLONE_CHILD_ENV).is_ok() {
let mut s: TupleSketch<ExplodesOnClone> = TupleSketchBuilder::new().build().unwrap();
s.update_u64(1, &());
unreachable!("the update should have aborted the process via the clone trampoline");
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"panicking_clone_aborts_with_a_diagnostic",
"--nocapture",
])
.env(CLONE_CHILD_ENV, "1")
.output()
.expect("failed to spawn the child test process");
assert!(
!output.status.success(),
"child was expected to abort, but exited successfully"
);
#[cfg(unix)]
assert!(
output.status.code().is_none(),
"child should have been killed by a signal (aborted), not exited \
with a status code; status was {:?}",
output.status
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("a Clone::clone implementation panicked"),
"abort message should name the panicking method as the resolvable \
path `Clone::clone`, not a `TupleSummary::clone` that does not \
exist; stderr was:\n{stderr}"
);
}
#[test]
fn panicking_create_is_an_ordinary_rust_panic() {
#[derive(Clone, Debug)]
struct PanicsOnCreate;
impl TupleSummary for PanicsOnCreate {
type Update = ();
fn create(_: &()) -> Self {
panic!("deliberate panic from create");
}
fn union_combine(&mut self, _: &Self) {}
fn intersection_combine(&mut self, _: &Self) {}
}
let mut s: TupleSketch<PanicsOnCreate> = TupleSketchBuilder::new().build().unwrap();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
s.update_u64(1, &());
}));
assert!(result.is_err(), "a panic in create must be catchable");
}