From f4a572dc858df17c02bf653f089d826d772acc54 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Thu, 13 May 2021 21:39:04 +0100 Subject: [PATCH 01/18] Experiments towards doc locking --- serial_test_test/Cargo.toml | 4 +- serial_test_test/src/lib.rs | 88 ++++++++++++++++++++++++------------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/serial_test_test/Cargo.toml b/serial_test_test/Cargo.toml index 5b83f2e..1dd50a9 100644 --- a/serial_test_test/Cargo.toml +++ b/serial_test_test/Cargo.toml @@ -6,8 +6,10 @@ version = "0.5.1" authors = ["Tom Parker-Shemilt "] edition = "2018" -[dev-dependencies] +[dependencies] serial_test = { path="../serial_test" } lazy_static = "^1.2" env_logger = ">= 0.7, <0.9" + +[dev-dependencies] tokio = { version = "0.2", features = ["macros", "rt-threaded"] } # Can't upgrade or we break 1.39 \ No newline at end of file diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index a82a11d..0b515f8 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -1,19 +1,60 @@ +use lazy_static::lazy_static; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::thread; +use std::time::Duration; + +lazy_static! { + static ref LOCK: Arc = Arc::new(AtomicUsize::new(0)); +} + +fn init() { + let _ = env_logger::builder().is_test(true).try_init(); +} + +/// Not inside the cfg(test) block because of https://github.com/rust-lang/rust/issues/45599 +/// ``` +/// #[macro_use] extern crate serial_test; +/// extern crate serial_test_test; +/// use serial_test_test::{test_fn}; +/// // #[serial_test::serial] +/// fn main() { +/// test_fn(4); +/// } +/// ``` +/// ``` +/// #[macro_use] extern crate serial_test; +/// extern crate serial_test_test; +/// use serial_test_test::{test_fn}; +/// // #[serial_test::serial] +/// fn main() { +/// test_fn(5); +/// } +/// ``` +/// ``` +/// #[macro_use] extern crate serial_test; +/// extern crate serial_test_test; +/// use serial_test_test::{test_fn}; +/// // #[serial_test::serial] +/// fn main() { +/// test_fn(6); +/// } +/// ``` +pub fn test_fn(count: usize) { + init(); + println!("Start {}", count); + LOCK.store(count, Ordering::Relaxed); + thread::sleep(Duration::from_millis(1000 * (count as u64))); + println!("End {}", count); + assert_eq!(LOCK.load(Ordering::Relaxed), count); +} + + #[cfg(test)] mod tests { - use lazy_static::lazy_static; + use super::{init, test_fn}; use serial_test::serial; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Arc; - use std::thread; - use std::time::Duration; - - lazy_static! { - static ref LOCK: Arc = Arc::new(AtomicUsize::new(0)); - } - - fn init() { - let _ = env_logger::builder().is_test(true).try_init(); - } #[test] #[serial] @@ -24,34 +65,19 @@ mod tests { #[test] #[serial(alpha)] fn test_serial_1() { - init(); - println!("Start 1"); - LOCK.store(1, Ordering::Relaxed); - thread::sleep(Duration::from_millis(100)); - println!("End 1"); - assert_eq!(LOCK.load(Ordering::Relaxed), 1); + test_fn(1) } #[test] #[serial(alpha)] fn test_serial_2() { - init(); - println!("Start 2"); - LOCK.store(2, Ordering::Relaxed); - thread::sleep(Duration::from_millis(200)); - println!("End 2"); - assert_eq!(LOCK.load(Ordering::Relaxed), 2); + test_fn(2) } #[test] #[serial(alpha)] fn test_serial_3() { - init(); - println!("Start 3"); - LOCK.store(3, Ordering::Relaxed); - thread::sleep(Duration::from_millis(300)); - println!("End 3"); - assert_eq!(LOCK.load(Ordering::Relaxed), 3); + test_fn(3) } #[test] From d06c8b2b50c3093734df31cbbebe24fa30120024 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Thu, 11 Nov 2021 23:27:16 +0000 Subject: [PATCH 02/18] Initial work on file-based locking --- Cargo.lock | 11 ++++ serial_test/Cargo.toml | 1 + serial_test/src/code_lock.rs | 69 ++++++++++++++++++++++++ serial_test/src/file_lock.rs | 102 +++++++++++++++++++++++++++++++++++ serial_test/src/lib.rs | 76 ++++---------------------- 5 files changed, 192 insertions(+), 67 deletions(-) create mode 100644 serial_test/src/code_lock.rs create mode 100644 serial_test/src/file_lock.rs diff --git a/Cargo.lock b/Cargo.lock index cc98562..4a40d90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "fslock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31bbcaaf785e09a604b93b15b9ccb41cbf4716fa04c788748d157cb3134d717b" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -499,6 +509,7 @@ dependencies = [ name = "serial_test" version = "0.5.1" dependencies = [ + "fslock", "lazy_static", "parking_lot", "serial_test_derive", diff --git a/serial_test/Cargo.toml b/serial_test/Cargo.toml index 346f47e..3204414 100644 --- a/serial_test/Cargo.toml +++ b/serial_test/Cargo.toml @@ -14,3 +14,4 @@ keywords = ["sequential"] lazy_static = "1.2" parking_lot = ">= 0.10, < 0.12" serial_test_derive = { version = "~0.5.1", path = "../serial_test_derive" } +fslock = "0.2" \ No newline at end of file diff --git a/serial_test/src/code_lock.rs b/serial_test/src/code_lock.rs new file mode 100644 index 0000000..19117f2 --- /dev/null +++ b/serial_test/src/code_lock.rs @@ -0,0 +1,69 @@ +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>>> = + 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 serial_core_with_return(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 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 async_serial_core_with_return( + name: &str, + fut: impl std::future::Future>, +) -> 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 async_serial_core(name: &str, fut: impl std::future::Future) { + 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/serial_test/src/file_lock.rs b/serial_test/src/file_lock.rs new file mode 100644 index 0000000..daec509 --- /dev/null +++ b/serial_test/src/file_lock.rs @@ -0,0 +1,102 @@ +use fslock::LockFile; +use std::{env, fs, process, str::from_utf8}; + +struct Lock { + lockfile: LockFile, + needs_unlock: bool, +} + +impl Lock { + fn unlock(self: &mut Lock) { + if self.needs_unlock { + self.lockfile.unlock().unwrap(); + println!("Unlock"); + } + } +} + +fn do_lock(path: &str) -> Lock { + let mut lockfile = LockFile::open(path).unwrap(); + println!("Waiting on {:?}", path); + let pid_str = format!("{}", process::id()); + let can_lock = lockfile.try_lock().unwrap(); + if !can_lock { + let raw_file = fs::read(path).unwrap(); + let get_pid = from_utf8(&raw_file).unwrap(); + if get_pid != pid_str { + lockfile.lock().unwrap(); + fs::write(path, pid_str).unwrap(); + } + } else { + println!("Got lock for {:?}", path); + fs::write(path, pid_str).unwrap(); + } + Lock { + lockfile, + needs_unlock: can_lock, + } +} + +fn path_for_name(name: &str) -> String { + let mut pathbuf = env::temp_dir(); + pathbuf.push(format!("serial-core-{}", 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( + 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( + name: &str, + path: Option<&str>, + fut: impl std::future::Future>, +) -> 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, +) { + let mut lock = make_lock_for_name_and_path(name, path); + fut.await; + lock.unlock(); +} + +#[test] +fn test_serial() { + fs_serial_core("test", None, || { + fs_serial_core("test", None, || {}); + }); +} diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index 44e84d7..f435351 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -19,75 +19,17 @@ //! Multiple tests with the [serial](attr.serial.html) attribute are guaranteed to be executed in serial. Ordering //! of the tests is not guaranteed however. -use lazy_static::lazy_static; -use parking_lot::ReentrantMutex; -use std::collections::HashMap; -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, RwLock}; +mod code_lock; +mod file_lock; -lazy_static! { - static ref LOCKS: Arc>>> = - Arc::new(RwLock::new(HashMap::new())); -} +pub use code_lock::{ + async_serial_core, async_serial_core_with_return, serial_core, serial_core_with_return, +}; -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 serial_core_with_return(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 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 async_serial_core_with_return( - name: &str, - fut: impl std::future::Future>, -) -> 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 async_serial_core(name: &str, fut: impl std::future::Future) { - 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 -} +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]. #[allow(unused_imports)] From 655e22a06e6884b3544d686c04c2441df7fa9230 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sat, 13 Nov 2021 23:30:43 +0000 Subject: [PATCH 03/18] Add macro support --- serial_test/src/code_lock.rs | 11 +- serial_test/src/lib.rs | 3 +- serial_test/tests/tests.rs | 4 +- serial_test_derive/src/lib.rs | 306 +++++++++++++++++++++++----------- 4 files changed, 219 insertions(+), 105 deletions(-) diff --git a/serial_test/src/code_lock.rs b/serial_test/src/code_lock.rs index 19117f2..a09b2f8 100644 --- a/serial_test/src/code_lock.rs +++ b/serial_test/src/code_lock.rs @@ -26,7 +26,10 @@ fn check_new_key(name: &str) { } #[doc(hidden)] -pub fn serial_core_with_return(name: &str, function: fn() -> Result<(), E>) -> Result<(), E> { +pub fn local_serial_core_with_return( + name: &str, + function: fn() -> Result<(), E>, +) -> Result<(), E> { check_new_key(name); let unlock = LOCKS.read().unwrap(); @@ -36,7 +39,7 @@ pub fn serial_core_with_return(name: &str, function: fn() -> Result<(), E>) - } #[doc(hidden)] -pub fn serial_core(name: &str, function: fn()) { +pub fn local_serial_core(name: &str, function: fn()) { check_new_key(name); let unlock = LOCKS.read().unwrap(); @@ -46,7 +49,7 @@ pub fn serial_core(name: &str, function: fn()) { } #[doc(hidden)] -pub async fn async_serial_core_with_return( +pub async fn local_async_serial_core_with_return( name: &str, fut: impl std::future::Future>, ) -> Result<(), E> { @@ -59,7 +62,7 @@ pub async fn async_serial_core_with_return( } #[doc(hidden)] -pub async fn async_serial_core(name: &str, fut: impl std::future::Future) { +pub async fn local_async_serial_core(name: &str, fut: impl std::future::Future) { check_new_key(name); let unlock = LOCKS.read().unwrap(); diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index 76dd4cf..f3c8b77 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -21,7 +21,8 @@ mod code_lock; mod file_lock; pub use code_lock::{ - async_serial_core, async_serial_core_with_return, serial_core, serial_core_with_return, + local_async_serial_core, local_async_serial_core_with_return, local_serial_core, + local_serial_core_with_return, }; pub use file_lock::{ diff --git a/serial_test/tests/tests.rs b/serial_test/tests/tests.rs index 01d9785..add8b10 100644 --- a/serial_test/tests/tests.rs +++ b/serial_test/tests/tests.rs @@ -1,8 +1,8 @@ -use serial_test::serial_core; +use serial_test::local_serial_core; #[test] fn test_empty_serial_call() { - serial_core("beta", || { + local_serial_core("beta", || { println!("Bar"); }); } diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index c6ad477..c0681a6 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -6,7 +6,7 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenTree; use proc_macro_error::{abort_call_site, proc_macro_error}; -use quote::quote; +use quote::{format_ident, quote}; use std::ops::Deref; /// Allows for the creation of serialised Rust tests @@ -57,10 +57,16 @@ use std::ops::Deref; #[proc_macro_attribute] #[proc_macro_error] pub fn serial(attr: TokenStream, input: TokenStream) -> TokenStream { - serial_core(attr.into(), input.into()).into() + local_serial_core(attr.into(), input.into()).into() } -fn serial_core( +#[proc_macro_attribute] +#[proc_macro_error] +pub fn file_serial(attr: TokenStream, input: TokenStream) -> TokenStream { + fs_serial_core(attr.into(), input.into()).into() +} + +fn local_serial_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { @@ -78,6 +84,49 @@ fn serial_core( panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); } }; + serial_setup(input, &vec![key], "local") +} + +fn fs_serial_core( + attr: proc_macro2::TokenStream, + input: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let attrs = attr.into_iter().collect::>(); + match attrs.len() { + 0 => serial_setup(input, &vec!["".to_string(), "".to_string()], "fs"), + 1 => { + if let TokenTree::Ident(id) = &attrs[0] { + serial_setup(input, &vec![id.to_string(), "".to_string()], "fs") + } else { + panic!("Expected a single name as argument, got {:?}", attrs); + } + } + 2 => { + let key; + let path; + if let TokenTree::Ident(id) = &attrs[0] { + key = id.to_string() + } else { + panic!("Expected name as first argument, got {:?}", attrs); + } + if let TokenTree::Ident(id) = &attrs[1] { + path = id.to_string() + } else { + panic!("Expected path as second argument, got {:?}", attrs); + } + serial_setup(input, &vec![key, path], "fs") + } + n => { + panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); + } + } +} + +fn serial_setup<'a>( + input: proc_macro2::TokenStream, + args: &[String], + prefix: &str, +) -> proc_macro2::TokenStream { let ast: syn::ItemFn = syn::parse2(input).unwrap(); let asyncness = ast.sig.asyncness; let name = ast.sig.ident; @@ -110,114 +159,175 @@ fn serial_core( .collect(); if let Some(ret) = return_type { match asyncness { - Some(_) => quote! { - #(#attrs) - * - async fn #name () -> #ret { - serial_test::async_serial_core_with_return(#key, || async #block ).await; + Some(_) => { + let fnname = format_ident!("{}_async_serial_core_with_return", prefix); + quote! { + #(#attrs) + * + async fn #name () -> #ret { + serial_test::#fnname(#(#args ),*, || async #block ).await; + } } - }, - None => quote! { - #(#attrs) - * - fn #name () -> #ret { - serial_test::serial_core_with_return(#key, || #block ) + } + None => { + let fnname = format_ident!("{}_serial_core_with_return", prefix); + quote! { + #(#attrs) + * + fn #name () -> #ret { + serial_test::#fnname(#(#args ),*, || #block ) + } } - }, + } } } else { match asyncness { - Some(_) => quote! { - #(#attrs) - * - async fn #name () { - serial_test::async_serial_core(#key, || async #block ).await; + Some(_) => { + let fnname = format_ident!("{}_async_serial_core", prefix); + quote! { + #(#attrs) + * + async fn #name () { + serial_test::#fnname(#(#args ),*, || async #block ).await; + } } - }, - None => quote! { - #(#attrs) - * - fn #name () { - serial_test::serial_core(#key, || #block ); + } + None => { + let fnname = format_ident!("{}_serial_core", prefix); + quote! { + #(#attrs) + * + fn #name () { + serial_test::#fnname(#(#args ),*, || #block ); + } } - }, + } } } } -#[test] -fn test_serial() { - let attrs = proc_macro2::TokenStream::new(); - let input = quote! { - #[test] - fn foo() {} - }; - let stream = serial_core(attrs.into(), input); - let compare = quote! { - #[test] - fn foo () { - serial_test::serial_core("", || {} ); - } - }; - assert_eq!(format!("{}", compare), format!("{}", stream)); -} +#[cfg(test)] +mod tests { + use super::{format_ident, fs_serial_core, local_serial_core, quote, TokenTree}; + use std::iter::FromIterator; -#[test] -fn test_stripped_attributes() { - let _ = env_logger::builder().is_test(true).try_init(); - let attrs = proc_macro2::TokenStream::new(); - let input = quote! { - #[test] - #[ignore] - #[should_panic(expected = "Testing panic")] - #[something_else] - fn foo() {} - }; - let stream = serial_core(attrs.into(), input); - let compare = quote! { - #[test] - #[something_else] - fn foo () { - serial_test::serial_core("", || {} ); - } - }; - assert_eq!(format!("{}", compare), format!("{}", stream)); -} + #[test] + fn test_serial() { + let attrs = proc_macro2::TokenStream::new(); + let input = quote! { + #[test] + fn foo() {} + }; + let stream = local_serial_core(attrs.into(), input); + let compare = quote! { + #[test] + fn foo () { + serial_test::local_serial_core("", || {} ); + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } -#[test] -fn test_serial_async() { - let attrs = proc_macro2::TokenStream::new(); - let input = quote! { - async fn foo() {} - }; - let stream = serial_core(attrs.into(), input); - let compare = quote! { - async fn foo () { - serial_test::async_serial_core("", || async {} ).await; - } - }; - assert_eq!(format!("{}", compare), format!("{}", stream)); -} + #[test] + fn test_stripped_attributes() { + let _ = env_logger::builder().is_test(true).try_init(); + let attrs = proc_macro2::TokenStream::new(); + let input = quote! { + #[test] + #[ignore] + #[should_panic(expected = "Testing panic")] + #[something_else] + fn foo() {} + }; + let stream = local_serial_core(attrs.into(), input); + let compare = quote! { + #[test] + #[something_else] + fn foo () { + serial_test::local_serial_core("", || {} ); + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } -#[test] -fn test_serial_async_return() { - let attrs = proc_macro2::TokenStream::new(); - let input = quote! { - async fn foo() -> Result<(), ()> { Ok(()) } - }; - let stream = serial_core(attrs.into(), input); - let compare = quote! { - async fn foo () -> Result<(), ()> { - serial_test::async_serial_core_with_return("", || async { Ok(()) } ).await; - } - }; - assert_eq!(format!("{}", compare), format!("{}", stream)); -} + #[test] + fn test_serial_async() { + let attrs = proc_macro2::TokenStream::new(); + let input = quote! { + async fn foo() {} + }; + let stream = local_serial_core(attrs.into(), input); + let compare = quote! { + async fn foo () { + serial_test::local_async_serial_core("", || async {} ).await; + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } + + #[test] + fn test_serial_async_return() { + let attrs = proc_macro2::TokenStream::new(); + let input = quote! { + async fn foo() -> Result<(), ()> { Ok(()) } + }; + let stream = local_serial_core(attrs.into(), input); + let compare = quote! { + async fn foo () -> Result<(), ()> { + serial_test::local_async_serial_core_with_return("", || async { Ok(()) } ).await; + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } + + // 1.54 needed for https://github.com/rust-lang/rust/commit/9daf546b77dbeab7754a80d7336cd8d00c6746e4 change in note message + #[rustversion::since(1.54)] + #[test] + fn test_serial_async_before_wrapper() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/broken/test_serial_async_before_wrapper.rs"); + } + + #[test] + fn test_file_serial() { + let attrs = vec![TokenTree::Ident(format_ident!("foo"))]; + let input = quote! { + #[test] + fn foo() {} + }; + let stream = fs_serial_core( + proc_macro2::TokenStream::from_iter(attrs.into_iter()), + input, + ); + let compare = quote! { + #[test] + fn foo () { + serial_test::fs_serial_core("foo", "", || {} ); + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } -// 1.54 needed for https://github.com/rust-lang/rust/commit/9daf546b77dbeab7754a80d7336cd8d00c6746e4 change in note message -#[rustversion::since(1.54)] -#[test] -fn test_serial_async_before_wrapper() { - let t = trybuild::TestCases::new(); - t.compile_fail("tests/broken/test_serial_async_before_wrapper.rs"); + #[test] + fn test_file_serial_with_path() { + let attrs = vec![ + TokenTree::Ident(format_ident!("foo")), + TokenTree::Ident(format_ident!("bar_path")), + ]; + let input = quote! { + #[test] + fn foo() {} + }; + let stream = fs_serial_core( + proc_macro2::TokenStream::from_iter(attrs.into_iter()), + input, + ); + let compare = quote! { + #[test] + fn foo () { + serial_test::fs_serial_core("foo", "bar_path", || {} ); + } + }; + assert_eq!(format!("{}", compare), format!("{}", stream)); + } } From 53071a8d2f89f983dc64d13e3c52c3125bcc258e Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 00:07:28 +0000 Subject: [PATCH 04/18] Fix path args and doc tests --- serial_test/src/file_lock.rs | 2 +- serial_test/src/lib.rs | 4 ++-- serial_test_derive/src/lib.rs | 30 +++++++++++++---------- serial_test_test/src/lib.rs | 45 ++++++++++++++++++++++++++--------- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/serial_test/src/file_lock.rs b/serial_test/src/file_lock.rs index daec509..eddb472 100644 --- a/serial_test/src/file_lock.rs +++ b/serial_test/src/file_lock.rs @@ -39,7 +39,7 @@ fn do_lock(path: &str) -> Lock { fn path_for_name(name: &str) -> String { let mut pathbuf = env::temp_dir(); - pathbuf.push(format!("serial-core-{}", name)); + pathbuf.push(format!("serial-test-{}", name)); pathbuf.into_os_string().into_string().unwrap() } diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index f3c8b77..e8e3c3a 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -30,6 +30,6 @@ pub use file_lock::{ fs_serial_core_with_return, }; -// Re-export #[serial]. +// Re-export #[serial/file_serial]. #[allow(unused_imports)] -pub use serial_test_derive::serial; +pub use serial_test_derive::{file_serial, serial}; diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index c0681a6..2238bd2 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -84,7 +84,7 @@ fn local_serial_core( panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); } }; - serial_setup(input, &vec![key], "local") + serial_setup(input, vec![Box::new(key)], "local") } fn fs_serial_core( @@ -92,41 +92,47 @@ fn fs_serial_core( input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let attrs = attr.into_iter().collect::>(); + let mut args: Vec> = Vec::new(); match attrs.len() { - 0 => serial_setup(input, &vec!["".to_string(), "".to_string()], "fs"), + 0 => { + args.push(Box::new("".to_string())); + args.push(Box::new(format_ident!("None"))); + } 1 => { if let TokenTree::Ident(id) = &attrs[0] { - serial_setup(input, &vec![id.to_string(), "".to_string()], "fs") + args.push(Box::new(id.to_string())); + args.push(Box::new(format_ident!("None"))); } else { panic!("Expected a single name as argument, got {:?}", attrs); } } 2 => { - let key; - let path; if let TokenTree::Ident(id) = &attrs[0] { - key = id.to_string() + args.push(Box::new(id.to_string())) } else { panic!("Expected name as first argument, got {:?}", attrs); } if let TokenTree::Ident(id) = &attrs[1] { - path = id.to_string() + args.push(Box::new(id.to_string())) } else { panic!("Expected path as second argument, got {:?}", attrs); } - serial_setup(input, &vec![key, path], "fs") } n => { panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); } } + serial_setup(input, args, "fs") } -fn serial_setup<'a>( +fn serial_setup<'a, T: ?Sized>( input: proc_macro2::TokenStream, - args: &[String], + args: Vec>, prefix: &str, -) -> proc_macro2::TokenStream { +) -> proc_macro2::TokenStream +where + T: quote::ToTokens, +{ let ast: syn::ItemFn = syn::parse2(input).unwrap(); let asyncness = ast.sig.asyncness; let name = ast.sig.ident; @@ -302,7 +308,7 @@ mod tests { let compare = quote! { #[test] fn foo () { - serial_test::fs_serial_core("foo", "", || {} ); + serial_test::fs_serial_core("foo", None, || {} ); } }; assert_eq!(format!("{}", compare), format!("{}", stream)); diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index e0dc001..9513ea1 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -1,4 +1,7 @@ use lazy_static::lazy_static; +use std::convert::TryInto; +use std::env; +use std::fs; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -17,28 +20,28 @@ fn init() { /// ``` /// #[macro_use] extern crate serial_test; /// extern crate serial_test_test; -/// use serial_test_test::{test_fn}; -/// // #[serial_test::serial] +/// use serial_test_test::{fs_test_fn}; +/// #[serial_test::file_serial] /// fn main() { -/// test_fn(4); +/// fs_test_fn(1); /// } /// ``` /// ``` /// #[macro_use] extern crate serial_test; /// extern crate serial_test_test; -/// use serial_test_test::{test_fn}; -/// // #[serial_test::serial] +/// use serial_test_test::{fs_test_fn}; +/// #[serial_test::file_serial] /// fn main() { -/// test_fn(5); +/// fs_test_fn(2); /// } /// ``` /// ``` /// #[macro_use] extern crate serial_test; /// extern crate serial_test_test; -/// use serial_test_test::{test_fn}; -/// // #[serial_test::serial] +/// use serial_test_test::{fs_test_fn}; +/// #[serial_test::file_serial] /// fn main() { -/// test_fn(6); +/// fs_test_fn(3); /// } /// ``` pub fn test_fn(count: usize) { @@ -50,10 +53,24 @@ pub fn test_fn(count: usize) { assert_eq!(LOCK.load(Ordering::Relaxed), count); } +pub fn fs_test_fn(count: usize) { + init(); + println!("Start {}", count); + let mut pathbuf = env::temp_dir(); + pathbuf.push("serial-test-test"); + fs::write(pathbuf.as_path(), count.to_ne_bytes()).unwrap(); + thread::sleep(Duration::from_millis(1000 * (count as u64))); + println!("End {}", count); + let loaded = fs::read(pathbuf.as_path()) + .and_then(|bytes| Ok(usize::from_ne_bytes(bytes.try_into().unwrap()))) + .unwrap(); + assert_eq!(loaded, count); +} + #[cfg(test)] mod tests { - use super::{init, test_fn}; - use serial_test::serial; + use super::{fs_test_fn, init, test_fn}; + use serial_test::{file_serial, serial}; #[test] #[serial] @@ -127,4 +144,10 @@ mod tests { init(); Ok(()) } + + #[test] + #[file_serial] + fn test_file_1() { + fs_test_fn(1); + } } From d41303466fc38585651c967f88d4767d13853524 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 15:16:07 +0000 Subject: [PATCH 05/18] Fix clippy issues --- serial_test_derive/src/lib.rs | 4 ++-- serial_test_test/src/lib.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index 2238bd2..0e00ce2 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -125,13 +125,13 @@ fn fs_serial_core( serial_setup(input, args, "fs") } -fn serial_setup<'a, T: ?Sized>( +fn serial_setup( input: proc_macro2::TokenStream, args: Vec>, prefix: &str, ) -> proc_macro2::TokenStream where - T: quote::ToTokens, + T: quote::ToTokens + ?Sized, { let ast: syn::ItemFn = syn::parse2(input).unwrap(); let asyncness = ast.sig.asyncness; diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 9513ea1..33345a5 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -61,8 +61,9 @@ pub fn fs_test_fn(count: usize) { fs::write(pathbuf.as_path(), count.to_ne_bytes()).unwrap(); thread::sleep(Duration::from_millis(1000 * (count as u64))); println!("End {}", count); + let loaded = fs::read(pathbuf.as_path()) - .and_then(|bytes| Ok(usize::from_ne_bytes(bytes.try_into().unwrap()))) + .map(|bytes| usize::from_ne_bytes(bytes.try_into().unwrap())) .unwrap(); assert_eq!(loaded, count); } From e87d6d37e5de39af99b7500d7be7f74cf4636b13 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 15:29:38 +0000 Subject: [PATCH 06/18] Stop using post-1.39 API for bytes --- serial_test_test/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 33345a5..88f551c 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -63,7 +63,7 @@ pub fn fs_test_fn(count: usize) { println!("End {}", count); let loaded = fs::read(pathbuf.as_path()) - .map(|bytes| usize::from_ne_bytes(bytes.try_into().unwrap())) + .map(|bytes| usize::from_ne_bytes(bytes.as_slice().try_into().unwrap())) .unwrap(); assert_eq!(loaded, count); } From 724968545e76da87d3a7d042bba55e2acf449924 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 15:34:56 +0000 Subject: [PATCH 07/18] More file testing --- serial_test_derive/src/lib.rs | 4 ++-- serial_test_test/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index 0e00ce2..f36fa29 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -106,7 +106,7 @@ fn fs_serial_core( panic!("Expected a single name as argument, got {:?}", attrs); } } - 2 => { + 3 => { if let TokenTree::Ident(id) = &attrs[0] { args.push(Box::new(id.to_string())) } else { @@ -119,7 +119,7 @@ fn fs_serial_core( } } n => { - panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); + panic!("Expected 0-2 arguments, got {}: {:?}", n, attrs); } } serial_setup(input, args, "fs") diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 88f551c..954d577 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -151,4 +151,21 @@ mod tests { fn test_file_1() { fs_test_fn(1); } + + #[test] + #[file_serial] + fn test_file_2() { + fs_test_fn(2); + } + + #[test] + #[file_serial] + fn test_file_3() { + fs_test_fn(3); + } + + #[test] + #[file_serial("test", "/tmp/test")] + fn test_file_with_path() { + } } From 992b0df7f0649f10cce69fcc8e20403522dab257 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 16:41:18 +0000 Subject: [PATCH 08/18] Fix support for optional file paths and string args --- serial_test/src/file_lock.rs | 5 +- serial_test_derive/src/lib.rs | 103 +++++++++++++++++++++++----------- serial_test_test/src/lib.rs | 13 +++-- 3 files changed, 82 insertions(+), 39 deletions(-) diff --git a/serial_test/src/file_lock.rs b/serial_test/src/file_lock.rs index eddb472..1ff3515 100644 --- a/serial_test/src/file_lock.rs +++ b/serial_test/src/file_lock.rs @@ -1,5 +1,5 @@ use fslock::LockFile; -use std::{env, fs, process, str::from_utf8}; +use std::{env, fs, path::Path, process, str::from_utf8}; struct Lock { lockfile: LockFile, @@ -16,6 +16,9 @@ impl Lock { } 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); let pid_str = format!("{}", process::id()); diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index f36fa29..dbc8b10 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -6,7 +6,7 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenTree; use proc_macro_error::{abort_call_site, proc_macro_error}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use std::ops::Deref; /// Allows for the creation of serialised Rust tests @@ -66,22 +66,63 @@ pub fn file_serial(attr: TokenStream, input: TokenStream) -> TokenStream { fs_serial_core(attr.into(), input.into()).into() } +// Based off of https://github.com/dtolnay/quote/issues/20#issuecomment-437341743 +struct QuoteOption(Option); + +impl ToTokens for QuoteOption { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.append_all(match self.0 { + Some(ref t) => quote! { ::std::option::Option::Some(#t) }, + None => quote! { ::std::option::Option::None }, + }); + } +} + +fn get_raw_args(attr: proc_macro2::TokenStream) -> Vec { + let mut attrs = attr.into_iter().collect::>(); + let mut raw_args: Vec = Vec::new(); + while !attrs.is_empty() { + match attrs.remove(0) { + TokenTree::Ident(id) => { + raw_args.push(id.to_string()); + } + TokenTree::Literal(literal) => { + let string_literal = literal.to_string(); + if !string_literal.starts_with('\"') || !string_literal.ends_with('\"') { + panic!("Expected a string literal, got '{}'", string_literal); + } + // Hacky way of getting a string without the enclosing quotes + raw_args.push(string_literal[1..string_literal.len() - 1].to_string()); + } + x => { + panic!("Expected either strings or literals as args, not {}", x); + } + } + if !attrs.is_empty() { + match attrs.remove(0) { + TokenTree::Punct(p) if p.as_char() == ',' => {} + x => { + panic!("Expected , between args, not {}", x); + } + } + } + } + raw_args +} + fn local_serial_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { - let attrs = attr.into_iter().collect::>(); - let key = match attrs.len() { + let mut raw_args = get_raw_args(attr); + let key = match raw_args.len() { 0 => "".to_string(), - 1 => { - if let TokenTree::Ident(id) = &attrs[0] { - id.to_string() - } else { - panic!("Expected a single name as argument, got {:?}", attrs); - } - } + 1 => raw_args.pop().unwrap(), n => { - panic!("Expected either 0 or 1 arguments, got {}: {:?}", n, attrs); + panic!( + "Expected either 0 or 1 arguments, got {}: {:?}", + n, raw_args + ); } }; serial_setup(input, vec![Box::new(key)], "local") @@ -91,35 +132,26 @@ fn fs_serial_core( attr: proc_macro2::TokenStream, input: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { - let attrs = attr.into_iter().collect::>(); + let none_ident = Box::new(format_ident!("None")); let mut args: Vec> = Vec::new(); - match attrs.len() { + let mut raw_args = get_raw_args(attr); + match raw_args.len() { 0 => { args.push(Box::new("".to_string())); - args.push(Box::new(format_ident!("None"))); + args.push(none_ident); } 1 => { - if let TokenTree::Ident(id) = &attrs[0] { - args.push(Box::new(id.to_string())); - args.push(Box::new(format_ident!("None"))); - } else { - panic!("Expected a single name as argument, got {:?}", attrs); - } + args.push(Box::new(raw_args.pop().unwrap())); + args.push(none_ident); } - 3 => { - if let TokenTree::Ident(id) = &attrs[0] { - args.push(Box::new(id.to_string())) - } else { - panic!("Expected name as first argument, got {:?}", attrs); - } - if let TokenTree::Ident(id) = &attrs[1] { - args.push(Box::new(id.to_string())) - } else { - panic!("Expected path as second argument, got {:?}", attrs); - } + 2 => { + let key = raw_args.remove(0); + let path = raw_args.remove(0); + args.push(Box::new(key)); + args.push(Box::new(QuoteOption(Some(path)))); } n => { - panic!("Expected 0-2 arguments, got {}: {:?}", n, attrs); + panic!("Expected 0-2 arguments, got {}: {:?}", n, raw_args); } } serial_setup(input, args, "fs") @@ -214,6 +246,8 @@ where #[cfg(test)] mod tests { + use proc_macro2::{Literal, Punct, Spacing}; + use super::{format_ident, fs_serial_core, local_serial_core, quote, TokenTree}; use std::iter::FromIterator; @@ -318,7 +352,8 @@ mod tests { fn test_file_serial_with_path() { let attrs = vec![ TokenTree::Ident(format_ident!("foo")), - TokenTree::Ident(format_ident!("bar_path")), + TokenTree::Punct(Punct::new(',', Spacing::Alone)), + TokenTree::Literal(Literal::string("bar_path")), ]; let input = quote! { #[test] @@ -331,7 +366,7 @@ mod tests { let compare = quote! { #[test] fn foo () { - serial_test::fs_serial_core("foo", "bar_path", || {} ); + serial_test::fs_serial_core("foo", ::std::option::Option::Some("bar_path"), || {} ); } }; assert_eq!(format!("{}", compare), format!("{}", stream)); diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 954d577..7dffcfc 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -156,16 +156,21 @@ mod tests { #[file_serial] fn test_file_2() { fs_test_fn(2); - } + } #[test] #[file_serial] fn test_file_3() { fs_test_fn(3); - } + } #[test] - #[file_serial("test", "/tmp/test")] - fn test_file_with_path() { + #[file_serial(test, "/tmp/test")] + fn test_file_with_path() {} + + #[test] + #[serial(test_key)] + fn test_with_key() { + init(); } } From 5b041017df91d8e2237a7d87bd8b47b6a9b37c06 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 16:51:39 +0000 Subject: [PATCH 09/18] Fix doc test placement --- serial_test_test/src/lib.rs | 57 +++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 7dffcfc..f7ba7cb 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -1,3 +1,32 @@ +//! Not inside the cfg(test) block because of +//! ``` +//! #[macro_use] extern crate serial_test; +//! extern crate serial_test_test; +//! use serial_test_test::{fs_test_fn}; +//! #[serial_test::file_serial] +//! fn main() { +//! fs_test_fn(1); +//! } +//! ``` +//! ``` +//! #[macro_use] extern crate serial_test; +//! extern crate serial_test_test; +//! use serial_test_test::{fs_test_fn}; +//! #[serial_test::file_serial] +//! fn main() { +//! fs_test_fn(2); +//! } +//! ``` +//! ``` +//! #[macro_use] extern crate serial_test; +//! extern crate serial_test_test; +//! use serial_test_test::{fs_test_fn}; +//! #[serial_test::file_serial] +//! fn main() { +//! fs_test_fn(3); +//! } +//! ``` + use lazy_static::lazy_static; use std::convert::TryInto; use std::env; @@ -16,34 +45,6 @@ fn init() { let _ = env_logger::builder().is_test(true).try_init(); } -/// Not inside the cfg(test) block because of https://github.com/rust-lang/rust/issues/45599 -/// ``` -/// #[macro_use] extern crate serial_test; -/// extern crate serial_test_test; -/// use serial_test_test::{fs_test_fn}; -/// #[serial_test::file_serial] -/// fn main() { -/// fs_test_fn(1); -/// } -/// ``` -/// ``` -/// #[macro_use] extern crate serial_test; -/// extern crate serial_test_test; -/// use serial_test_test::{fs_test_fn}; -/// #[serial_test::file_serial] -/// fn main() { -/// fs_test_fn(2); -/// } -/// ``` -/// ``` -/// #[macro_use] extern crate serial_test; -/// extern crate serial_test_test; -/// use serial_test_test::{fs_test_fn}; -/// #[serial_test::file_serial] -/// fn main() { -/// fs_test_fn(3); -/// } -/// ``` pub fn test_fn(count: usize) { init(); println!("Start {}", count); From f97b0e02f95a978fd36f62d526f8203c78a71cd8 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 20:58:47 +0000 Subject: [PATCH 10/18] Add Windows/Mac testing --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0219a83..2577cf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,27 @@ jobs: with: command: test + multi-os-testing: + name: Test suite + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - windows-latest + - macos-latest + steps: + - uses: actions/checkout@v2.3.4 + - uses: actions-rs/toolchain@v1.0.7 + with: + profile: minimal + toolchain: stable + override: true + - uses: Swatinem/rust-cache@v1 + - name: Build and test + uses: actions-rs/cargo@v1.0.3 + with: + command: test + minimal-versions: name: minimal versions check runs-on: ubuntu-latest From 095644a1ac42505c18b212319c50c89c97f8f179 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 21:04:38 +0000 Subject: [PATCH 11/18] Extra debug for locking --- serial_test/src/file_lock.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/serial_test/src/file_lock.rs b/serial_test/src/file_lock.rs index 1ff3515..d6d1b41 100644 --- a/serial_test/src/file_lock.rs +++ b/serial_test/src/file_lock.rs @@ -27,11 +27,13 @@ fn do_lock(path: &str) -> Lock { let raw_file = fs::read(path).unwrap(); let get_pid = from_utf8(&raw_file).unwrap(); if get_pid != pid_str { + println!("Locked for other pid {} != {}", get_pid, pid_str); lockfile.lock().unwrap(); + println!("Got lock for {:?} (2nd path)", path); fs::write(path, pid_str).unwrap(); } } else { - println!("Got lock for {:?}", path); + println!("Got lock for {:?} (1st path)", path); fs::write(path, pid_str).unwrap(); } Lock { From cec664e95a71b5d40a4edd5b896fc7d33daf1664 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 21:33:39 +0000 Subject: [PATCH 12/18] Remove buggy support for recursive locks --- serial_test/src/file_lock.rs | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/serial_test/src/file_lock.rs b/serial_test/src/file_lock.rs index d6d1b41..20cd99e 100644 --- a/serial_test/src/file_lock.rs +++ b/serial_test/src/file_lock.rs @@ -1,17 +1,14 @@ use fslock::LockFile; -use std::{env, fs, path::Path, process, str::from_utf8}; +use std::{env, fs, path::Path}; struct Lock { lockfile: LockFile, - needs_unlock: bool, } impl Lock { fn unlock(self: &mut Lock) { - if self.needs_unlock { - self.lockfile.unlock().unwrap(); - println!("Unlock"); - } + self.lockfile.unlock().unwrap(); + println!("Unlock"); } } @@ -21,25 +18,9 @@ fn do_lock(path: &str) -> Lock { } let mut lockfile = LockFile::open(path).unwrap(); println!("Waiting on {:?}", path); - let pid_str = format!("{}", process::id()); - let can_lock = lockfile.try_lock().unwrap(); - if !can_lock { - let raw_file = fs::read(path).unwrap(); - let get_pid = from_utf8(&raw_file).unwrap(); - if get_pid != pid_str { - println!("Locked for other pid {} != {}", get_pid, pid_str); - lockfile.lock().unwrap(); - println!("Got lock for {:?} (2nd path)", path); - fs::write(path, pid_str).unwrap(); - } - } else { - println!("Got lock for {:?} (1st path)", path); - fs::write(path, pid_str).unwrap(); - } - Lock { - lockfile, - needs_unlock: can_lock, - } + lockfile.lock().unwrap(); + println!("Locked for {:?}", path); + Lock { lockfile } } fn path_for_name(name: &str) -> String { @@ -101,7 +82,5 @@ pub async fn fs_async_serial_core( #[test] fn test_serial() { - fs_serial_core("test", None, || { - fs_serial_core("test", None, || {}); - }); + fs_serial_core("test", None, || {}); } From 65c86c3d36622bd5500be8b98088f583a11a8b47 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 22:46:45 +0000 Subject: [PATCH 13/18] Add file_locks feature --- serial_test/Cargo.toml | 6 +++++- serial_test/src/lib.rs | 7 ++++++- serial_test_test/Cargo.toml | 4 ++++ serial_test_test/src/lib.rs | 22 ++++++++++++++++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/serial_test/Cargo.toml b/serial_test/Cargo.toml index 3204414..23566df 100644 --- a/serial_test/Cargo.toml +++ b/serial_test/Cargo.toml @@ -14,4 +14,8 @@ keywords = ["sequential"] lazy_static = "1.2" parking_lot = ">= 0.10, < 0.12" serial_test_derive = { version = "~0.5.1", path = "../serial_test_derive" } -fslock = "0.2" \ No newline at end of file +fslock = {version = "0.2", optional = true} + +[features] +default = [] +file_locks = ["fslock"] \ No newline at end of file diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index e8e3c3a..eb80a39 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -18,6 +18,7 @@ //! of the tests is not guaranteed however. mod code_lock; +#[cfg(feature = "file_locks")] mod file_lock; pub use code_lock::{ @@ -25,6 +26,7 @@ pub use code_lock::{ 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, @@ -32,4 +34,7 @@ pub use file_lock::{ // Re-export #[serial/file_serial]. #[allow(unused_imports)] -pub use serial_test_derive::{file_serial, serial}; +pub use serial_test_derive::serial; + +#[cfg(feature = "file_locks")] +pub use serial_test_derive::file_serial; diff --git a/serial_test_test/Cargo.toml b/serial_test_test/Cargo.toml index c949fe1..324a7e0 100644 --- a/serial_test_test/Cargo.toml +++ b/serial_test_test/Cargo.toml @@ -17,3 +17,7 @@ actix-rt = { version = "1.0", default_features = false } # futures 0.3.15 breaks 1.39 futures-util = {version = ">=0.3, <0.3.15", default_features = false } + +[features] +default = [] +file_locks = ["serial_test/file_locks"] \ No newline at end of file diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index f7ba7cb..78eb824 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -3,28 +3,37 @@ //! #[macro_use] extern crate serial_test; //! extern crate serial_test_test; //! use serial_test_test::{fs_test_fn}; +//! #[cfg(feature = "file_locks")] //! #[serial_test::file_serial] //! fn main() { //! fs_test_fn(1); //! } +//! #[cfg(not(feature = "file_locks"))] +//! fn main() {} //! ``` //! ``` //! #[macro_use] extern crate serial_test; //! extern crate serial_test_test; //! use serial_test_test::{fs_test_fn}; +//! #[cfg(feature = "file_locks")] //! #[serial_test::file_serial] //! fn main() { //! fs_test_fn(2); //! } +//! #[cfg(not(feature = "file_locks"))] +//! fn main() {} //! ``` //! ``` //! #[macro_use] extern crate serial_test; //! extern crate serial_test_test; //! use serial_test_test::{fs_test_fn}; +//! #[cfg(feature = "file_locks")] //! #[serial_test::file_serial] //! fn main() { //! fs_test_fn(3); //! } +//! #[cfg(not(feature = "file_locks"))] +//! fn main() {} //! ``` use lazy_static::lazy_static; @@ -71,8 +80,13 @@ pub fn fs_test_fn(count: usize) { #[cfg(test)] mod tests { - use super::{fs_test_fn, init, test_fn}; - use serial_test::{file_serial, serial}; + use super::{init, test_fn}; + use serial_test::serial; + + #[cfg(feature = "file_locks")] + use serial_test::file_serial; + #[cfg(feature = "file_locks")] + use super::fs_test_fn; #[test] #[serial] @@ -147,24 +161,28 @@ mod tests { Ok(()) } + #[cfg(feature = "file_locks")] #[test] #[file_serial] fn test_file_1() { fs_test_fn(1); } + #[cfg(feature = "file_locks")] #[test] #[file_serial] fn test_file_2() { fs_test_fn(2); } + #[cfg(feature = "file_locks")] #[test] #[file_serial] fn test_file_3() { fs_test_fn(3); } + #[cfg(feature = "file_locks")] #[test] #[file_serial(test, "/tmp/test")] fn test_file_with_path() {} From 748d7fb846d6a55b08bcc9be108b2ca08d5d93fa Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 22:52:26 +0000 Subject: [PATCH 14/18] Add file_locks testing --- .github/workflows/ci.yml | 9 ++++++++- serial_test_test/src/lib.rs | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2577cf3..f64d785 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,9 @@ jobs: - beta - nightly - 1.39.0 + features: + - default + - file_locks steps: - uses: actions/checkout@v2.3.4 - uses: actions-rs/toolchain@v1.0.7 @@ -33,17 +36,20 @@ jobs: with: command: fmt args: -- --check + if: ${{ matrix.features == 'default' }} - name: Clippy uses: actions-rs/cargo@v1.0.3 env: RUSTFLAGS: -Dwarnings with: command: clippy + if: ${{ matrix.features == 'default' }} continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }} - name: Build and test uses: actions-rs/cargo@v1.0.3 with: command: test + args: --features ${{ matrix.features }} multi-os-testing: name: Test suite @@ -64,7 +70,8 @@ jobs: - name: Build and test uses: actions-rs/cargo@v1.0.3 with: - command: test + command: test + args: --all-features minimal-versions: name: minimal versions check diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 78eb824..1d739f3 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -83,10 +83,10 @@ mod tests { use super::{init, test_fn}; use serial_test::serial; - #[cfg(feature = "file_locks")] - use serial_test::file_serial; #[cfg(feature = "file_locks")] use super::fs_test_fn; + #[cfg(feature = "file_locks")] + use serial_test::file_serial; #[test] #[serial] From 3dc992d1ce19b34c4c794f5bdaa0ba6711fffb4c Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 22:56:12 +0000 Subject: [PATCH 15/18] Add windows-specific test temp path --- serial_test_test/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index 1d739f3..b1ee996 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -184,7 +184,8 @@ mod tests { #[cfg(feature = "file_locks")] #[test] - #[file_serial(test, "/tmp/test")] + #[cfg_attr(windows, file_serial(test, "c:\\windows\\temp"))] + #[cfg_attr(not(windows), file_serial(test, "/tmp/test"))] fn test_file_with_path() {} #[test] From e8188c4534a90bf9c5f3607268071fd43d36ba2c Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 23:13:15 +0000 Subject: [PATCH 16/18] Add docs for file_serial --- serial_test/src/lib.rs | 16 ++++++++++++-- serial_test_derive/src/lib.rs | 41 +++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index eb80a39..b81a2c6 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -1,5 +1,5 @@ //! # serial_test -//! `serial_test` allows for the creation of serialised Rust tests using the [serial](attr.serial.html) attribute +//! `serial_test` allows for the creation of serialised Rust tests using the [serial](macro@serial) attribute //! e.g. //! ```` //! #[test] @@ -14,8 +14,20 @@ //! // Do things //! } //! ```` -//! Multiple tests with the [serial](attr.serial.html) attribute are guaranteed to be executed in serial. Ordering +//! 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 +//! } +//! ```` mod code_lock; #[cfg(feature = "file_locks")] diff --git a/serial_test_derive/src/lib.rs b/serial_test_derive/src/lib.rs index dbc8b10..f3a2076 100644 --- a/serial_test_derive/src/lib.rs +++ b/serial_test_derive/src/lib.rs @@ -23,9 +23,9 @@ use std::ops::Deref; /// // Do things /// } /// ```` -/// Multiple tests with the [serial](attr.serial.html) attribute are guaranteed to be executed in serial. Ordering +/// Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering /// of the tests is not guaranteed however. If you want different subsets of tests to be serialised with each -/// other, but not depend on other subsets, you can add an argument to [serial](attr.serial.html), and all calls +/// other, but not depend on other subsets, you can add an argument to [serial](macro@serial), and all calls /// with identical arguments will be called in serial. e.g. /// ```` /// #[test] @@ -54,12 +54,49 @@ use std::ops::Deref; /// ```` /// `test_serial_one` and `test_serial_another` will be executed in serial, as will `test_serial_third` and `test_serial_fourth` /// but neither sequence will be blocked by the other +/// +/// Nested serialised tests (i.e. a [serial](macro@serial) tagged test calling another) is supported #[proc_macro_attribute] #[proc_macro_error] pub fn serial(attr: TokenStream, input: TokenStream) -> TokenStream { local_serial_core(attr.into(), input.into()).into() } +/// Allows for the creation of file-serialised Rust tests +/// ```` +/// #[test] +/// #[file_serial] +/// fn test_serial_one() { +/// // Do things +/// } +/// +/// #[test] +/// #[file_serial] +/// fn test_serial_another() { +/// // Do things +/// } +/// ```` +/// +/// Multiple tests with the [file_serial](macro@file_serial) attribute are guaranteed to run in serial, as per the [serial](macro@serial) +/// attribute. 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, and [file_serial](macro@file_serial) does not support nested serialised tests, but otherwise acts +/// like [serial](macro@serial). +/// +/// It also supports an optional `path` arg e.g +/// ```` +/// #[test] +/// #[file_serial(key, "/tmp/foo")] +/// fn test_serial_one() { +/// // Do things +/// } +/// +/// #[test] +/// #[file_serial(key, "/tmp/foo")] +/// fn test_serial_another() { +/// // Do things +/// } +/// ```` +/// Note that in this case you need to specify the `name` arg as well (as per [serial](macro@serial)). The path defaults to a reasonable temp directory for the OS if not specified. #[proc_macro_attribute] #[proc_macro_error] pub fn file_serial(attr: TokenStream, input: TokenStream) -> TokenStream { From 1cd3a407268c663af0b39bbed73b11768222f427 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 23:15:02 +0000 Subject: [PATCH 17/18] Disable test_file_with_path on Windows --- serial_test_test/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/serial_test_test/src/lib.rs b/serial_test_test/src/lib.rs index b1ee996..b63e9a4 100644 --- a/serial_test_test/src/lib.rs +++ b/serial_test_test/src/lib.rs @@ -182,10 +182,9 @@ mod tests { fs_test_fn(3); } - #[cfg(feature = "file_locks")] + #[cfg(all(feature = "file_locks", not(windows)))] #[test] - #[cfg_attr(windows, file_serial(test, "c:\\windows\\temp"))] - #[cfg_attr(not(windows), file_serial(test, "/tmp/test"))] + #[file_serial(test, "/tmp/test")] fn test_file_with_path() {} #[test] From 93c9706617c7893215d42e37fcbf5a6c5d3f3194 Mon Sep 17 00:00:00 2001 From: Tom Parker-Shemilt Date: Sun, 14 Nov 2021 23:26:08 +0000 Subject: [PATCH 18/18] Add some docs.rs magic features to fix feature links --- serial_test/Cargo.toml | 5 ++++- serial_test/src/lib.rs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/serial_test/Cargo.toml b/serial_test/Cargo.toml index 23566df..e217772 100644 --- a/serial_test/Cargo.toml +++ b/serial_test/Cargo.toml @@ -18,4 +18,7 @@ fslock = {version = "0.2", optional = true} [features] default = [] -file_locks = ["fslock"] \ No newline at end of file +file_locks = ["fslock"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/serial_test/src/lib.rs b/serial_test/src/lib.rs index b81a2c6..b3b6f7c 100644 --- a/serial_test/src/lib.rs +++ b/serial_test/src/lib.rs @@ -1,3 +1,5 @@ +#![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.