use rand::{rngs::StdRng, SeedableRng}; use shuttle::rand::Rng; use shuttle::scheduler::{DfsScheduler, Scheduler}; use shuttle::sync::{Arc, Mutex}; use shuttle::{check_uncontrolled_nondeterminism, thread}; use test_log::test; fn check_uncontrolled_nondeterminism_custom_scheduler_and_config(f: F, scheduler: S) where F: Fn() + Send + Sync + 'static, S: Scheduler + 'static, { use shuttle::scheduler::UncontrolledNondeterminismCheckScheduler; use shuttle::Config; let config = Config::default(); let scheduler = UncontrolledNondeterminismCheckScheduler::new(scheduler); let runner = shuttle::Runner::new(scheduler, config); runner.run(f); } fn have_n_threads_acquire_mutex R) + Send + Sync>( thread_rng: &'static F, num_threads: u64, modulus: u64, ) { let lock = Arc::new(Mutex::new(0u64)); let threads: Vec<_> = (0..num_threads) .map(|_| { let my_lock = lock.clone(); thread::spawn(move || { let x = thread_rng().gen::(); if x % modulus == 0 { let mut num = my_lock.lock().unwrap(); *num += 1; } }) }) .collect(); threads.into_iter().for_each(|t| t.join().expect("Failed")); } #[test] fn randomly_acquire_lock_shuttle_rand() { check_uncontrolled_nondeterminism( || have_n_threads_acquire_mutex(&shuttle::rand::thread_rng, 10, 10), 1000, ); } #[test] #[should_panic = "possible nondeterminism"] fn randomly_acquire_lock_regular_rand() { check_uncontrolled_nondeterminism(|| have_n_threads_acquire_mutex(&rand::thread_rng, 10, 10), 1000); } fn spawn_random_amount_of_threads R) + Send + Sync>( thread_rng: &'static F, max_threads: u64, ) { let num_threads: u64 = thread_rng().gen::() % max_threads; have_n_threads_acquire_mutex(&rand::thread_rng, num_threads, 1); } #[test] fn spawn_random_amount_of_threads_shuttle_rand() { check_uncontrolled_nondeterminism(|| spawn_random_amount_of_threads(&shuttle::rand::thread_rng, 10), 1000); } #[test] #[should_panic = "possible nondeterminism"] fn spawn_random_amount_of_threads_regular_rand() { check_uncontrolled_nondeterminism(|| spawn_random_amount_of_threads(&rand::thread_rng, 10), 1000); } #[test] fn spawn_random_amount_of_threads_dfs_shuttle_rand() { let scheduler = DfsScheduler::new(None, true); check_uncontrolled_nondeterminism_custom_scheduler_and_config( || spawn_random_amount_of_threads(&shuttle::rand::thread_rng, 2), scheduler, ); } #[test] #[should_panic] fn spawn_random_amount_of_threads_dfs_regular_rand() { for _ in 0..10 { let scheduler = DfsScheduler::new(None, true); check_uncontrolled_nondeterminism_custom_scheduler_and_config( || spawn_random_amount_of_threads(&rand::thread_rng, 10), scheduler, ); } } fn spawn_random_amount_of_threads_mutex_rng(rng: &Mutex, max_threads: u64) { let num_threads = rng.lock().unwrap().gen::() % max_threads; have_n_threads_acquire_mutex(&rand::thread_rng, num_threads, 1); } #[test] #[should_panic = "possible nondeterminism: current execution should have ended"] fn panic_should_have_ended() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || spawn_random_amount_of_threads_mutex_rng(&rng, 2), scheduler, ); } #[test] #[should_panic = "possible nondeterminism: current execution ended earlier than expected"] fn panic_ended_earlier() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || spawn_random_amount_of_threads_mutex_rng(&rng, 3), scheduler, ); } #[test] #[should_panic = "possible nondeterminism: set of runnable tasks is different than expected"] fn panic_set_of_runnable() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || spawn_random_amount_of_threads_mutex_rng(&rng, 15), scheduler, ); } fn make_random_numbers() { for _ in 0..10 { shuttle::rand::thread_rng().gen::(); } } #[test] #[should_panic = "possible nondeterminism: next step was context switch, but recording expected random number generation"] fn panic_context_switch_when_expecting_rng() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); let modulo = 3; check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || { let test = rng.lock().unwrap().gen::() % modulo == 0; if test { have_n_threads_acquire_mutex(&rand::thread_rng, 10, 1); } else { make_random_numbers(); } }, scheduler, ); } #[test] #[should_panic = "possible nondeterminism: next step was random number generation, but recording expected context switch"] fn panic_rng_when_expecting_context_switch() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); let modulo = 5; check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || { let test = rng.lock().unwrap().gen::() % modulo == 0; if test { have_n_threads_acquire_mutex(&rand::thread_rng, 10, 1); } else { make_random_numbers(); } }, scheduler, ); } fn have_n_threads_yield(num_threads: u64) { let threads: Vec<_> = (0..num_threads) .map(|_| { thread::spawn(move || { thread::yield_now(); }) }) .collect(); threads.into_iter().for_each(|t| t.join().expect("Failed")); } #[test] #[should_panic = "possible nondeterminism: `next_task` was called with `is_yielding`"] fn panic_is_yielding() { let scheduler = DfsScheduler::new(None, true); let rng = Mutex::new(StdRng::seed_from_u64(123)); let modulo = 5; check_uncontrolled_nondeterminism_custom_scheduler_and_config( move || { let test = rng.lock().unwrap().gen::() % modulo == 0; if test { have_n_threads_acquire_mutex(&rand::thread_rng, 10, 1); } else { have_n_threads_yield(10); } }, scheduler, ); } fn iterate_over_hash_set(num_entries: u64) { use std::collections::HashSet; let hash_set: HashSet = HashSet::from_iter(0..num_entries); for e in hash_set { if e % 2 == 0 { let _ = shuttle::rand::thread_rng().gen::(); } else { let _ = thread::spawn(|| {}).join(); } } } #[test] #[should_panic = "possible nondeterminism: next step was"] fn hashset_without_set_seed() { check_uncontrolled_nondeterminism(|| iterate_over_hash_set(10), 1000); }