summaryrefslogtreecommitdiffstats
path: root/third_party/rust/serial_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/serial_test/src')
-rw-r--r--third_party/rust/serial_test/src/code_lock.rs72
-rw-r--r--third_party/rust/serial_test/src/file_lock.rs86
-rw-r--r--third_party/rust/serial_test/src/lib.rs60
-rw-r--r--third_party/rust/serial_test/src/rwlock.rs30
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