mod common;
use hegel::TestCase;
use hegel::generators as gs;
use std::sync::{Arc, Mutex};
fn _assert_test_case_is_send()
where
TestCase: Send,
{
}
#[allow(dead_code)]
fn _assert_test_case_is_not_sync() {
trait AmbiguousIfSync<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
<TestCase as AmbiguousIfSync<_>>::some_item();
}
#[allow(dead_code)]
fn _assert_explicit_test_case_is_not_send() {
trait AmbiguousIfSend<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfSend<()> for T {}
impl<T: ?Sized + Send> AmbiguousIfSend<u8> for T {}
<hegel::ExplicitTestCase as AmbiguousIfSend<_>>::some_item();
}
#[allow(dead_code)]
fn _assert_explicit_test_case_is_not_sync() {
trait AmbiguousIfSync<A> {
fn some_item() {}
}
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
impl<T: ?Sized + Sync> AmbiguousIfSync<u8> for T {}
<hegel::ExplicitTestCase as AmbiguousIfSync<_>>::some_item();
}
#[hegel::test(test_cases = 20)]
fn test_spawn_thread_with_clone_does_generation(tc: TestCase) {
let tc_clone = tc.clone();
let handle = std::thread::spawn(move || {
let n: u32 = tc_clone.draw(gs::integers());
n
});
let thread_value = handle.join().expect("thread panicked");
let main_value: bool = tc.draw(gs::booleans());
let _ = (thread_value, main_value);
}
#[hegel::test(test_cases = 20)]
fn test_main_then_thread_then_main(tc: TestCase) {
let _a: u32 = tc.draw(gs::integers());
let tc_clone = tc.clone();
let handle = std::thread::spawn(move || {
let b: String = tc_clone.draw(gs::text());
let c: bool = tc_clone.draw(gs::booleans());
(b, c)
});
let _ = handle.join().expect("thread panicked");
let _d: u32 = tc.draw(gs::integers());
}
#[hegel::test(test_cases = 10)]
fn test_sequential_threads_each_do_one_draw(tc: TestCase) {
for _ in 0..3 {
let tc_clone = tc.clone();
let handle = std::thread::spawn(move || {
let v: u32 = tc_clone.draw(gs::integers());
v
});
let _ = handle.join().expect("thread panicked");
}
let _: bool = tc.draw(gs::booleans());
}
#[hegel::test(test_cases = 5)]
fn test_nested_generators_work_across_thread_boundary(tc: TestCase) {
let tc_clone = tc.clone();
let captured: Arc<Mutex<Option<Vec<i32>>>> = Arc::new(Mutex::new(None));
let captured_clone = Arc::clone(&captured);
let handle = std::thread::spawn(move || {
let xs: Vec<i32> = tc_clone.draw(gs::vecs(gs::integers::<i32>()).max_size(5));
*captured_clone.lock().unwrap() = Some(xs);
});
handle.join().expect("thread panicked");
let xs = captured
.lock()
.unwrap()
.take()
.expect("thread produced no value");
assert!(xs.len() <= 5);
}
#[hegel::composite]
fn thread_mid_generator(tc: TestCase) -> (i32, i64, bool) {
let a: i32 = tc.draw(gs::integers::<i32>());
let tc_worker = tc.clone();
let handle = std::thread::spawn(move || tc_worker.draw(gs::integers::<i64>()));
let b: i64 = handle.join().expect("thread panicked");
let c: bool = tc.draw(gs::booleans());
(a, b, c)
}
#[hegel::test(test_cases = 10)]
fn test_thread_inside_composite_does_not_deadlock(tc: TestCase) {
let (_a, _b, _c) = tc.draw(thread_mid_generator());
}
mod threading {
use hegel::generators::{self as gs};
use hegel::{Hegel, Settings};
use std::sync::{Arc, Barrier};
use std::thread;
#[test]
fn test_run_given_concurrently() {
let n_threads = 2;
let barrier = Arc::new(Barrier::new(n_threads));
let handles: Vec<_> = (0..n_threads)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
Hegel::new(move |tc| {
let _n: i64 = tc.draw(gs::integers());
barrier.wait();
})
.settings(Settings::new().database(None))
.run();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
}
mod independent_streams {
use hegel::generators::{self as gs};
use hegel::{Hegel, Settings, TestCase};
use std::sync::{Arc, Mutex};
use std::thread;
fn small_int(tc: &TestCase) -> i64 {
tc.draw(gs::integers::<i64>().min_value(0).max_value(1000))
}
#[test]
fn concurrent_clone_streams_replay_deterministically_per_seed() {
let run_once = || -> Vec<(i64, i64)> {
let seen = Arc::new(Mutex::new(Vec::new()));
let seen_in_test = Arc::clone(&seen);
Hegel::new(move |tc: TestCase| {
let worker = tc.clone();
let handle = thread::spawn(move || small_int(&worker));
let main_val = small_int(&tc);
let child_val = handle.join().unwrap();
seen_in_test.lock().unwrap().push((main_val, child_val));
})
.settings(Settings::new().test_cases(20).database(None).seed(Some(99)))
.run();
seen.lock().unwrap().clone()
};
let first = run_once();
assert!(!first.is_empty());
assert_eq!(first, run_once());
}
#[test]
fn concurrent_clone_failure_shrinks_to_the_minimal_counterexample() {
let found: Arc<Mutex<Option<(i64, i64)>>> = Arc::new(Mutex::new(None));
let found_in_test = Arc::clone(&found);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
Hegel::new(move |tc: TestCase| {
let worker = tc.clone();
let handle = thread::spawn(move || small_int(&worker));
let main_val = small_int(&tc);
let child_val = handle.join().unwrap();
if child_val >= 100 {
*found_in_test.lock().unwrap() = Some((main_val, child_val));
panic!("HEGEL_CLONE_MINIMAL_FOUND");
}
})
.settings(
Settings::new()
.test_cases(200)
.database(None)
.derandomize(true),
)
.run();
}));
let payload = result.unwrap_err();
let msg = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(|s| s.as_str()))
.unwrap_or_default()
.to_string();
assert!(msg.contains("HEGEL_CLONE_MINIMAL_FOUND"), "{msg}");
let (main_val, child_val) = found.lock().unwrap().take().unwrap();
assert_eq!(child_val, 100);
assert_eq!(main_val, 0);
}
}