use lazy_static::lazy_static; use std::sync::{RwLock, Mutex}; use std::cell::Cell; lazy_static! { static ref SINGLETON: Mutex> = Mutex::new(None); } #[derive(Debug, PartialEq)] struct Singleton { state: String, } impl Singleton { pub fn initialize(state: String) { let mut st = SINGLETON.lock().unwrap(); if st.is_none() { *st = Some(Singleton { state }) } else { panic!("Singleton is already initialized"); } } pub fn get() -> &'static Mutex> { if SINGLETON.lock().unwrap().is_some() { &SINGLETON } else { panic!("Singleton must be initialized before use"); } } } #[cfg(test)] mod tests { use super::{Singleton, SINGLETON}; use std::sync::Mutex; use lazy_static::lazy_static; use std::panic; use std::sync::atomic::{AtomicUsize, Ordering}; lazy_static! { // Force tests to run one-at-a-time static ref TEST_CTR: AtomicUsize = AtomicUsize::new(0); } fn run_test(order: usize, body: impl Fn() + panic::RefUnwindSafe) { // Wait until the order is correct while TEST_CTR.load(Ordering::Acquire) != order {}; // Remove value from singleton, make it uninit SINGLETON.lock().unwrap().take(); // Catch panic so we could increment the atomic before crashing test if panic occured let result = panic::catch_unwind(|| body()); // Let the next test run TEST_CTR.store(order + 1, Ordering::Release); // Possibly crash result.unwrap(); } #[test] #[should_panic] fn use_uninitialized() { run_test(3, || { Singleton::get(); }); } #[test] #[should_panic] fn double_initialize() { run_test(2,|| { Singleton::initialize(String::from("hello")); Singleton::initialize(String::from("world")); }); } #[test] fn initialize() { run_test(1, || { Singleton::initialize(String::from("hello, world!")); assert_eq!( *SINGLETON.lock().unwrap(), Some(Singleton { state: String::from("hello, world!") }) ) }); } #[test] fn initialize_and_get() { run_test(0, || { Singleton::initialize(String::from("hello, world!")); let instance = Singleton::get().lock().unwrap(); assert_eq!( *instance, Some(Singleton { state: String::from("hello, world!") }) ); }); } }