diff options
Diffstat (limited to 'third_party/rust/serial_test/src')
-rw-r--r-- | third_party/rust/serial_test/src/code_lock.rs | 72 | ||||
-rw-r--r-- | third_party/rust/serial_test/src/file_lock.rs | 86 | ||||
-rw-r--r-- | third_party/rust/serial_test/src/lib.rs | 60 | ||||
-rw-r--r-- | third_party/rust/serial_test/src/rwlock.rs | 30 |
4 files changed, 248 insertions, 0 deletions
diff --git a/third_party/rust/serial_test/src/code_lock.rs b/third_party/rust/serial_test/src/code_lock.rs new file mode 100644 index 0000000000..a09b2f8d1e --- /dev/null +++ b/third_party/rust/serial_test/src/code_lock.rs @@ -0,0 +1,72 @@ +use lazy_static::lazy_static; +use parking_lot::ReentrantMutex; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, RwLock}; + +lazy_static! { + static ref LOCKS: Arc<RwLock<HashMap<String, ReentrantMutex<()>>>> = + Arc::new(RwLock::new(HashMap::new())); +} + +fn check_new_key(name: &str) { + // Check if a new key is needed. Just need a read lock, which can be done in sync with everyone else + let new_key = { + let unlock = LOCKS.read().unwrap(); + !unlock.deref().contains_key(name) + }; + if new_key { + // This is the rare path, which avoids the multi-writer situation mostly + LOCKS + .write() + .unwrap() + .deref_mut() + .insert(name.to_string(), ReentrantMutex::new(())); + } +} + +#[doc(hidden)] +pub fn local_serial_core_with_return<E>( + name: &str, + function: fn() -> Result<(), E>, +) -> Result<(), E> { + check_new_key(name); + + let unlock = LOCKS.read().unwrap(); + // _guard needs to be named to avoid being instant dropped + let _guard = unlock.deref()[name].lock(); + function() +} + +#[doc(hidden)] +pub fn local_serial_core(name: &str, function: fn()) { + check_new_key(name); + + let unlock = LOCKS.read().unwrap(); + // _guard needs to be named to avoid being instant dropped + let _guard = unlock.deref()[name].lock(); + function(); +} + +#[doc(hidden)] +pub async fn local_async_serial_core_with_return<E>( + name: &str, + fut: impl std::future::Future<Output = Result<(), E>>, +) -> Result<(), E> { + check_new_key(name); + + let unlock = LOCKS.read().unwrap(); + // _guard needs to be named to avoid being instant dropped + let _guard = unlock.deref()[name].lock(); + fut.await +} + +#[doc(hidden)] +pub async fn local_async_serial_core(name: &str, fut: impl std::future::Future<Output = ()>) { + check_new_key(name); + + let unlock = LOCKS.read().unwrap(); + // _guard needs to be named to avoid being instant dropped + let _guard = unlock.deref()[name].lock(); + fut.await; +} diff --git a/third_party/rust/serial_test/src/file_lock.rs b/third_party/rust/serial_test/src/file_lock.rs new file mode 100644 index 0000000000..20cd99eddb --- /dev/null +++ b/third_party/rust/serial_test/src/file_lock.rs @@ -0,0 +1,86 @@ +use fslock::LockFile; +use std::{env, fs, path::Path}; + +struct Lock { + lockfile: LockFile, +} + +impl Lock { + fn unlock(self: &mut Lock) { + self.lockfile.unlock().unwrap(); + println!("Unlock"); + } +} + +fn do_lock(path: &str) -> Lock { + if !Path::new(path).exists() { + fs::write(path, "").unwrap_or_else(|_| panic!("Lock file path was {:?}", path)) + } + let mut lockfile = LockFile::open(path).unwrap(); + println!("Waiting on {:?}", path); + lockfile.lock().unwrap(); + println!("Locked for {:?}", path); + Lock { lockfile } +} + +fn path_for_name(name: &str) -> String { + let mut pathbuf = env::temp_dir(); + pathbuf.push(format!("serial-test-{}", name)); + pathbuf.into_os_string().into_string().unwrap() +} + +fn make_lock_for_name_and_path(name: &str, path: Option<&str>) -> Lock { + if let Some(opt_path) = path { + do_lock(opt_path) + } else { + let default_path = path_for_name(name); + do_lock(&default_path) + } +} + +#[doc(hidden)] +pub fn fs_serial_core(name: &str, path: Option<&str>, function: fn()) { + let mut lock = make_lock_for_name_and_path(name, path); + function(); + lock.unlock(); +} + +#[doc(hidden)] +pub fn fs_serial_core_with_return<E>( + name: &str, + path: Option<&str>, + function: fn() -> Result<(), E>, +) -> Result<(), E> { + let mut lock = make_lock_for_name_and_path(name, path); + let ret = function(); + lock.unlock(); + ret +} + +#[doc(hidden)] +pub async fn fs_async_serial_core_with_return<E>( + name: &str, + path: Option<&str>, + fut: impl std::future::Future<Output = Result<(), E>>, +) -> Result<(), E> { + let mut lock = make_lock_for_name_and_path(name, path); + let ret = fut.await; + lock.unlock(); + ret +} + +#[doc(hidden)] +pub async fn fs_async_serial_core( + name: &str, + path: Option<&str>, + fut: impl std::future::Future<Output = ()>, +) { + let mut lock = make_lock_for_name_and_path(name, path); + fut.await; + lock.unlock(); +} + +#[test] +fn test_serial() { + fs_serial_core("test", None, || {}); +} diff --git a/third_party/rust/serial_test/src/lib.rs b/third_party/rust/serial_test/src/lib.rs new file mode 100644 index 0000000000..e83eec042d --- /dev/null +++ b/third_party/rust/serial_test/src/lib.rs @@ -0,0 +1,60 @@ +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +//! # serial_test +//! `serial_test` allows for the creation of serialised Rust tests using the [serial](macro@serial) attribute +//! e.g. +//! ```` +//! #[test] +//! #[serial] +//! fn test_serial_one() { +//! // Do things +//! } +//! +//! #[test] +//! #[serial] +//! fn test_serial_another() { +//! // Do things +//! } +//! ```` +//! Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering +//! of the tests is not guaranteed however. +//! +//! For cases like doctests and integration tests where the tests are run as separate processes, we also support +//! [file_serial](macro@file_serial), with similar properties but based off file locking. Note that there are no +//! guarantees about one test with [serial](macro@serial) and another with [file_serial](macro@file_serial) +//! as they lock using different methods. +//! ```` +//! #[test] +//! #[file_serial] +//! fn test_serial_three() { +//! // Do things +//! } +//! ```` +//! +//! ## Feature flags +#![cfg_attr( + feature = "docsrs", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] + +mod code_lock; +#[cfg(feature = "file_locks")] +mod file_lock; + +pub use code_lock::{ + local_async_serial_core, local_async_serial_core_with_return, local_serial_core, + local_serial_core_with_return, +}; + +#[cfg(feature = "file_locks")] +pub use file_lock::{ + fs_async_serial_core, fs_async_serial_core_with_return, fs_serial_core, + fs_serial_core_with_return, +}; + +// Re-export #[serial/file_serial]. +#[allow(unused_imports)] +pub use serial_test_derive::serial; + +#[cfg(feature = "file_locks")] +pub use serial_test_derive::file_serial; diff --git a/third_party/rust/serial_test/src/rwlock.rs b/third_party/rust/serial_test/src/rwlock.rs new file mode 100644 index 0000000000..42c1a99707 --- /dev/null +++ b/third_party/rust/serial_test/src/rwlock.rs @@ -0,0 +1,30 @@ +use std::sync::{ Arc, Mutex, Condvar}; + +// LockState can be in several possible states: + +// 1. 0 readers, false upgradeable_reader, false writer. No-one has any locks, anyone can acquire anything (initial state) +// 2. 1+ readers, false upgradeable_reader, false writer - readers. writer cannot be acquired, but upgradeable_reader can be +// 3. 1+ readers, true upgradeable_reader, false writer - bunch of readers, and one thread that could upgrade to writer, but not yet +// 4. 0 readers, true upgradeable_reader, false writer - upgradeable_reader thread can upgrade to writer +// 5. 0 readers, false upgradeable_reader, true writer - writer only. Nothing else can be acquired. + +struct LockState { + readers: u32, + upgradeable_reader: bool, + writer: bool +} + +struct Locks(Arc<(Mutex<LockState>, Condvar)>); + +impl Locks { + pub fn new() -> Locks { + Locks(Arc::new((Mutex::new( + LockState { + readers: 0, + upgradeable_reader: false, + writer: false + }), Condvar::new()))) + } + + pub fn read() -> +}
\ No newline at end of file |