├── serial_test ├── LICENSE ├── README.md ├── tests │ └── tests.rs ├── Cargo.toml └── src │ ├── serial_file_lock.rs │ ├── serial_code_lock.rs │ ├── lib.rs │ ├── rwlock.rs │ ├── parallel_file_lock.rs │ ├── parallel_code_lock.rs │ ├── code_lock.rs │ └── file_lock.rs ├── serial_test_derive ├── LICENSE ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── rustfmt.toml ├── .gitignore ├── Cargo.toml ├── .cargo └── config.toml ├── serial_test_test ├── tests │ ├── first.rs │ ├── second.rs │ └── third.rs ├── Cargo.toml └── src │ └── lib.rs ├── LICENSE ├── README.md └── Cargo.lock /serial_test/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /serial_test/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /serial_test_derive/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /serial_test_derive/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: palfrey 2 | ko_fi: palfrey 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | newline_style = "Unix" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.jpg 3 | .DS_Store 4 | .vscode/ 5 | serial_test_test/relative -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["serial_test", "serial_test_derive", "serial_test_test"] 3 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(feature = "cargo-clippy")'] 2 | rustflags = [ 3 | "-Dclippy::print_stdout", 4 | "-Dclippy::print_stderr", 5 | "-Dclippy::dbg_macro", 6 | ] -------------------------------------------------------------------------------- /serial_test/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use serial_test::local_serial_core; 2 | 3 | #[test] 4 | fn test_empty_serial_call() { 5 | local_serial_core(vec!["beta"], None, || { 6 | println!("Bar"); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /serial_test_test/tests/first.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "file_locks")] 2 | use serial_test::file_serial; 3 | 4 | #[cfg(feature = "file_locks")] 5 | #[test] 6 | #[file_serial(path => "./relative")] 7 | fn test_file_relative_1() { 8 | use serial_test_test::{fs_test_fn, RELATIVE_FS}; 9 | 10 | fs_test_fn(1, RELATIVE_FS); 11 | } 12 | -------------------------------------------------------------------------------- /serial_test_test/tests/second.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "file_locks")] 2 | use serial_test::file_serial; 3 | 4 | #[cfg(feature = "file_locks")] 5 | #[test] 6 | #[file_serial(path => "./relative")] 7 | fn test_file_relative_2() { 8 | use serial_test_test::{fs_test_fn, RELATIVE_FS}; 9 | 10 | fs_test_fn(2, RELATIVE_FS); 11 | } 12 | -------------------------------------------------------------------------------- /serial_test_test/tests/third.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "file_locks")] 2 | use serial_test::file_serial; 3 | 4 | #[cfg(feature = "file_locks")] 5 | #[test] 6 | #[file_serial(path => "./relative")] 7 | fn test_file_relative_3() { 8 | use serial_test_test::{fs_test_fn, RELATIVE_FS}; 9 | 10 | fs_test_fn(3, RELATIVE_FS); 11 | } 12 | -------------------------------------------------------------------------------- /serial_test_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial_test_derive" 3 | description = "Helper crate for serial_test" 4 | license = "MIT" 5 | version = "3.2.0" 6 | authors = ["Tom Parker-Shemilt "] 7 | edition = "2018" 8 | readme = "README.md" 9 | repository = "https://github.com/palfrey/serial_test/" 10 | categories = ["development-tools::testing"] 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | quote = { version="1", default-features = false} 17 | syn = { version="2", features=["full", "printing", "parsing", "clone-impls"], default-features = false} 18 | proc-macro2 = { version="1.0.60", features = ["proc-macro"], default-features = false} # Because of https://github.com/dtolnay/proc-macro2/issues/356 19 | 20 | [dev-dependencies] 21 | env_logger = {version=">=0.6.1", default-features = false} 22 | prettyplease = {version="0.2", default-features = false} 23 | 24 | [features] 25 | default = [] 26 | async = [] 27 | test_logging = [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Tom Parker-Shemilt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /serial_test_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial_test_test" 3 | description = "External testing crate for serial_test" 4 | license = "MIT" 5 | version = "3.2.0" 6 | authors = ["Tom Parker-Shemilt "] 7 | edition = "2018" 8 | rust-version = "1.68.2" 9 | 10 | [dependencies] 11 | serial_test = { path="../serial_test", default-features = false } 12 | once_cell = { version="^1.19", default-features = false } 13 | env_logger = { version=">=0.6.1", default-features = false } 14 | parking_lot = { version="^0.12", default-features = false } 15 | lock_api = { version="^0.4.7", default-features = false } 16 | wasm-bindgen-test = {version="^0.3.50", optional=true, default-features = false } 17 | scoped-tls = { version="1", optional=true, default-features = false } 18 | log = { version = ">=0.4.4" , default-features = false } 19 | scc = { version = "2", default-features = false} 20 | 21 | [dev-dependencies] 22 | tokio = { version = "=1.38.2", features = ["macros", "rt", "rt-multi-thread"], default-features = false } 23 | actix-rt = { version = "^2.8", features = ["macros"], default-features = false } 24 | futures-util = {version = "^0.3", default-features = false } 25 | 26 | [features] 27 | default = ["serial_test/logging", "async", "serial_test/test_logging"] 28 | file_locks = ["serial_test/file_locks"] 29 | async = ["serial_test/async", "dep:wasm-bindgen-test", "dep:scoped-tls"] 30 | 31 | [package.metadata.cargo-all-features] 32 | skip_optional_dependencies = true -------------------------------------------------------------------------------- /serial_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial_test" 3 | description = "Allows for the creation of serialised Rust tests" 4 | license = "MIT" 5 | version = "3.2.0" 6 | authors = ["Tom Parker-Shemilt "] 7 | edition = "2018" 8 | repository = "https://github.com/palfrey/serial_test/" 9 | readme = "README.md" 10 | categories = ["development-tools::testing"] 11 | keywords = ["sequential", "testing", "parallel"] 12 | 13 | [dependencies] 14 | once_cell = {version="^1.19", features = ["std"], default-features = false} 15 | parking_lot = {version="^0.12", default-features = false} 16 | serial_test_derive = { version = "~3.2.0", path = "../serial_test_derive" } 17 | fslock = { version = "0.2", optional = true, default-features = false, features = ["std"]} 18 | document-features = { version = "0.2", optional = true } 19 | log = { version = ">=0.4.4", optional = true } 20 | futures-executor = { version = "^0.3", optional = true, default-features = false, features = ["std"] } 21 | futures-util = { version = "^0.3", optional = true, default-features = false, features = ["std"] } 22 | 23 | scc = { version = "2", default-features = false} 24 | env_logger = {version=">=0.6.1", optional=true, default-features = false} 25 | 26 | [dev-dependencies] 27 | itertools = {version=">=0.4", default-features = false, features = ["use_std"]} 28 | 29 | [features] 30 | default = ["logging", "async"] 31 | 32 | ## Switches on debug logging 33 | logging = ["dep:log"] 34 | 35 | ## Switches on debug with env_logger. Generally only needed by internal serial_test work. 36 | test_logging = ["logging", "dep:env_logger", "serial_test_derive/test_logging"] 37 | 38 | ## Enables async features (and requires the `futures` package) 39 | async = ["dep:futures-executor", "dep:futures-util", "serial_test_derive/async"] 40 | 41 | ## The file_locks feature unlocks the `file_serial`/`file_parallel` macros 42 | file_locks = ["dep:fslock"] 43 | 44 | docsrs = ["dep:document-features"] 45 | 46 | # docs.rs-specific configuration 47 | [package.metadata.docs.rs] 48 | all-features = true 49 | # defines the configuration attribute `docsrs` 50 | rustdoc-args = ["--cfg", "docsrs"] 51 | 52 | [package.metadata.cargo-all-features] 53 | skip_optional_dependencies = true 54 | denylist = ["docsrs"] 55 | always_include_features = ["test_logging"] 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serial_test 2 | [![Version](https://img.shields.io/crates/v/serial_test.svg)](https://crates.io/crates/serial_test) 3 | [![Downloads](https://img.shields.io/crates/d/serial_test)](https://crates.io/crates/serial_test) 4 | [![Docs](https://docs.rs/serial_test/badge.svg)](https://docs.rs/serial_test/) 5 | [![MIT license](https://img.shields.io/crates/l/serial_test.svg)](./LICENSE) 6 | [![Build Status](https://github.com/palfrey/serial_test/actions/workflows/ci.yml/badge.svg)](https://github.com/palfrey/serial_test/actions) 7 | [![MSRV: 1.68.2](https://flat.badgen.net/badge/MSRV/1.68.2/purple)](https://blog.rust-lang.org/2023/03/28/Rust-1.68.2.html) 8 | 9 | `serial_test` allows for the creation of serialised Rust tests using the `serial` attribute 10 | e.g. 11 | ```rust 12 | use serial_test::serial; 13 | 14 | #[test] 15 | #[serial] 16 | fn test_serial_one() { 17 | // Do things 18 | } 19 | 20 | #[test] 21 | #[serial] 22 | fn test_serial_another() { 23 | // Do things 24 | } 25 | 26 | #[tokio::test] 27 | #[serial] 28 | async fn test_serial_another() { 29 | // Do things asynchronously 30 | } 31 | ``` 32 | Multiple tests with the `serial` attribute are guaranteed to be executed in serial. Ordering of the tests is not guaranteed however. Other tests with the `parallel` attribute may run at the same time as each other, but not at the same time as a test with `serial`. Tests with neither attribute may run at any time and no guarantees are made about their timing! Both support optional keys for defining subsets of tests to run in serial together, see docs for more details. 33 | 34 | For cases like doctests and integration tests where the tests are run as separate processes, we also support `file_serial`, with 35 | similar properties but based off file locking. Note that there are no guarantees about one test with `serial` and another with 36 | `file_serial` as they lock using different methods. 37 | 38 | All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block. 39 | 40 | ## Usage 41 | The minimum supported Rust version here is 1.68.2. Note this is minimum _supported_, as it may well compile with lower versions, but they're not supported at all. Upgrades to this will require at a major version bump. 1.x supports 1.51 if you need a lower version than that. 42 | 43 | Add to your Cargo.toml 44 | ```toml 45 | [dev-dependencies] 46 | serial_test = "*" 47 | ``` 48 | 49 | plus `use serial_test::serial;` in your imports section. 50 | 51 | You can then either add `#[serial]` or `#[serial(some_key)]` to tests as required. 52 | -------------------------------------------------------------------------------- /serial_test/src/serial_file_lock.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | 3 | use crate::file_lock::get_locks; 4 | 5 | #[doc(hidden)] 6 | pub fn fs_serial_core(names: Vec<&str>, path: Option<&str>, function: fn()) { 7 | assert!(names.len() > 0); 8 | let mut locks = get_locks(&names, path); 9 | locks.iter_mut().for_each(|lock| lock.start_serial()); 10 | let res = panic::catch_unwind(function); 11 | locks.into_iter().for_each(|lock| lock.end_serial()); 12 | if let Err(err) = res { 13 | panic::resume_unwind(err); 14 | } 15 | } 16 | 17 | #[doc(hidden)] 18 | pub fn fs_serial_core_with_return( 19 | names: Vec<&str>, 20 | path: Option<&str>, 21 | function: fn() -> Result<(), E>, 22 | ) -> Result<(), E> { 23 | let mut locks = get_locks(&names, path); 24 | locks.iter_mut().for_each(|lock| lock.start_serial()); 25 | let res = panic::catch_unwind(function); 26 | locks.into_iter().for_each(|lock| lock.end_serial()); 27 | match res { 28 | Ok(ret) => ret, 29 | Err(err) => { 30 | panic::resume_unwind(err); 31 | } 32 | } 33 | } 34 | 35 | #[doc(hidden)] 36 | #[cfg(feature = "async")] 37 | pub async fn fs_async_serial_core_with_return( 38 | names: Vec<&str>, 39 | path: Option<&str>, 40 | fut: impl std::future::Future>, 41 | ) -> Result<(), E> { 42 | let mut locks = get_locks(&names, path); 43 | locks.iter_mut().for_each(|lock| lock.start_serial()); 44 | let ret: Result<(), E> = fut.await; 45 | locks.into_iter().for_each(|lock| lock.end_serial()); 46 | ret 47 | } 48 | 49 | #[doc(hidden)] 50 | #[cfg(feature = "async")] 51 | pub async fn fs_async_serial_core( 52 | names: Vec<&str>, 53 | path: Option<&str>, 54 | fut: impl std::future::Future, 55 | ) { 56 | let mut locks = get_locks(&names, path); 57 | locks.iter_mut().for_each(|lock| lock.start_serial()); 58 | fut.await; 59 | locks.into_iter().for_each(|lock| lock.end_serial()); 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use std::panic; 65 | 66 | use fslock::LockFile; 67 | 68 | use super::fs_serial_core; 69 | use crate::file_lock::path_for_name; 70 | 71 | #[test] 72 | fn test_serial() { 73 | fs_serial_core(vec!["test"], None, || {}); 74 | } 75 | 76 | #[test] 77 | fn unlock_on_assert_sync_without_return() { 78 | let lock_path = path_for_name("serial_unlock_on_assert_sync_without_return"); 79 | let _ = panic::catch_unwind(|| { 80 | fs_serial_core( 81 | vec!["serial_unlock_on_assert_sync_without_return"], 82 | Some(&lock_path), 83 | || { 84 | assert!(false); 85 | }, 86 | ) 87 | }); 88 | let mut lockfile = LockFile::open(&lock_path).unwrap(); 89 | assert!(lockfile.try_lock().unwrap()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 1 * *' 8 | 9 | name: Continuous integration 10 | 11 | jobs: 12 | test: 13 | name: Test suite 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | rust: 18 | - stable 19 | - beta 20 | - nightly 21 | - 1.68.2 22 | steps: 23 | - uses: actions/checkout@v3.5.0 24 | - uses: dtolnay/rust-toolchain@stable 25 | with: 26 | toolchain: ${{ matrix.rust }} 27 | components: clippy, rustfmt 28 | - uses: Swatinem/rust-cache@v2.2.1 29 | - name: Check formatting 30 | run: cargo fmt -- --check 31 | if: ${{ matrix.rust == 'nightly' }} 32 | - name: Clippy 33 | run: cargo clippy 34 | env: 35 | RUSTFLAGS: -Dwarnings 36 | continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }} 37 | - name: Install "build all features" 38 | run: cargo install cargo-all-features --version 1.10.0 --force --locked 39 | - name: Build all features 40 | run: cargo build-all-features 41 | - name: Test all features 42 | run: cargo test-all-features 43 | env: 44 | RUST_TEST_THREADS: 3 # So the parallel tests have enough threads 45 | RUST_LOG: debug 46 | 47 | multi-os-testing: 48 | name: Test suite 49 | runs-on: ${{ matrix.os }} 50 | strategy: 51 | matrix: 52 | os: 53 | - windows-latest 54 | - macos-latest 55 | steps: 56 | - uses: actions/checkout@v3.5.0 57 | - uses: dtolnay/rust-toolchain@stable 58 | with: 59 | toolchain: stable 60 | - uses: Swatinem/rust-cache@v2.2.1 61 | - name: Build and test 62 | run: cargo test --all-features -- --nocapture 63 | env: 64 | RUST_TEST_THREADS: 3 # So the parallel tests have enough threads 65 | RUST_LOG: debug 66 | 67 | minimal-versions: 68 | name: minimal versions check 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v3.5.0 72 | - uses: dtolnay/rust-toolchain@stable 73 | with: 74 | toolchain: nightly 75 | - name: Remove lock 76 | run: rm Cargo.lock 77 | - run: cargo build -Z minimal-versions 78 | 79 | dependency-updates: 80 | name: dependency updates 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v3.5.0 84 | - uses: dtolnay/rust-toolchain@stable 85 | with: 86 | toolchain: stable 87 | - name: Update packages 88 | run: cargo update 89 | - name: Build and test 90 | run: cargo test --all-features -- --nocapture 91 | env: 92 | RUST_TEST_THREADS: 3 # So the parallel tests have enough threads 93 | RUST_LOG: debug -------------------------------------------------------------------------------- /serial_test/src/serial_code_lock.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::await_holding_lock)] 2 | 3 | use crate::code_lock::{check_new_key, global_locks}; 4 | 5 | #[doc(hidden)] 6 | macro_rules! core_internal { 7 | ($names: ident) => { 8 | let unlocks: Vec<_> = $names 9 | .into_iter() 10 | .map(|name| { 11 | check_new_key(name); 12 | global_locks() 13 | .get(name) 14 | .expect("key to be set") 15 | .get() 16 | .clone() 17 | }) 18 | .collect(); 19 | let _guards: Vec<_> = unlocks.iter().map(|unlock| unlock.lock()).collect(); 20 | }; 21 | } 22 | 23 | #[doc(hidden)] 24 | pub fn local_serial_core_with_return( 25 | names: Vec<&str>, 26 | _path: Option, 27 | function: fn() -> Result, 28 | ) -> Result { 29 | core_internal!(names); 30 | function() 31 | } 32 | 33 | #[doc(hidden)] 34 | pub fn local_serial_core(names: Vec<&str>, _path: Option<&str>, function: fn()) { 35 | core_internal!(names); 36 | function(); 37 | } 38 | 39 | #[doc(hidden)] 40 | #[cfg(feature = "async")] 41 | pub async fn local_async_serial_core_with_return( 42 | names: Vec<&str>, 43 | _path: Option<&str>, 44 | fut: impl std::future::Future> + std::marker::Send, 45 | ) -> Result { 46 | core_internal!(names); 47 | fut.await 48 | } 49 | 50 | #[doc(hidden)] 51 | #[cfg(feature = "async")] 52 | pub async fn local_async_serial_core( 53 | names: Vec<&str>, 54 | _path: Option<&str>, 55 | fut: impl std::future::Future, 56 | ) { 57 | core_internal!(names); 58 | fut.await; 59 | } 60 | 61 | #[cfg(test)] 62 | #[allow(clippy::print_stdout)] 63 | mod tests { 64 | use super::local_serial_core; 65 | use crate::code_lock::{check_new_key, global_locks}; 66 | use itertools::Itertools; 67 | use parking_lot::RwLock; 68 | use std::{ 69 | sync::{Arc, Barrier}, 70 | thread, 71 | time::Duration, 72 | }; 73 | 74 | #[test] 75 | fn test_hammer_check_new_key() { 76 | let ptrs = Arc::new(RwLock::new(Vec::new())); 77 | let mut threads = Vec::new(); 78 | 79 | let count = 100; 80 | let barrier = Arc::new(Barrier::new(count)); 81 | 82 | for _ in 0..count { 83 | let local_locks = global_locks(); 84 | let local_ptrs = ptrs.clone(); 85 | let c = barrier.clone(); 86 | threads.push(thread::spawn(move || { 87 | c.wait(); 88 | check_new_key("foo"); 89 | { 90 | let unlock = local_locks.get("foo").expect("read didn't work"); 91 | let mutex = unlock.get(); 92 | 93 | let mut ptr_guard = local_ptrs 94 | .try_write_for(Duration::from_secs(1)) 95 | .expect("write lock didn't work"); 96 | ptr_guard.push(mutex.id); 97 | } 98 | 99 | c.wait(); 100 | })); 101 | } 102 | for thread in threads { 103 | thread.join().expect("thread join worked"); 104 | } 105 | let ptrs_read_lock = ptrs 106 | .try_read_recursive_for(Duration::from_secs(1)) 107 | .expect("ptrs read work"); 108 | assert_eq!(ptrs_read_lock.len(), count); 109 | println!("{:?}", ptrs_read_lock); 110 | assert_eq!(ptrs_read_lock.iter().unique().count(), 1); 111 | } 112 | 113 | #[test] 114 | fn unlock_on_assert() { 115 | let _ = std::panic::catch_unwind(|| { 116 | local_serial_core(vec!["assert"], None, || { 117 | assert!(false); 118 | }) 119 | }); 120 | assert!(!global_locks().get("assert").unwrap().get().is_locked()); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /serial_test/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 2 | #![deny(unused_variables)] 3 | #![deny(missing_docs)] 4 | #![deny(unused_imports)] 5 | 6 | //! # serial_test 7 | //! `serial_test` allows for the creation of serialised Rust tests using the [serial](macro@serial) attribute 8 | //! e.g. 9 | //! ```` 10 | //! #[test] 11 | //! #[serial] 12 | //! fn test_serial_one() { 13 | //! // Do things 14 | //! } 15 | //! 16 | //! #[test] 17 | //! #[serial(some_key)] 18 | //! fn test_serial_another() { 19 | //! // Do things 20 | //! } 21 | //! 22 | //! #[test] 23 | //! #[parallel] 24 | //! fn test_parallel_another() { 25 | //! // Do parallel things 26 | //! } 27 | //! ```` 28 | //! Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering 29 | //! of the tests is not guaranteed however. Other tests with the [parallel](macro@parallel) attribute may run 30 | //! at the same time as each other, but not at the same time as a test with [serial](macro@serial). Tests with 31 | //! neither attribute may run at any time and no guarantees are made about their timing! 32 | //! 33 | //! For cases like doctests and integration tests where the tests are run as separate processes, we also support 34 | //! [file_serial](macro@file_serial)/[file_parallel](macro@file_parallel), with similar properties but based off file locking. Note that there are no 35 | //! guarantees about one test with [serial](macro@serial)/[parallel](macro@parallel) and another with [file_serial](macro@file_serial)/[file_parallel](macro@file_parallel) 36 | //! as they lock using different methods. 37 | //! ```` 38 | //! #[test] 39 | //! #[file_serial] 40 | //! fn test_serial_three() { 41 | //! // Do things 42 | //! } 43 | //! ```` 44 | //! 45 | //! All of the attributes can also be applied at a `mod` level and will be automagically applied to all test functions in that block 46 | //! ```` 47 | //! #[cfg(test)] 48 | //! #[serial] 49 | //! mod serial_attr_tests { 50 | //! fn foo() { 51 | //! // Won't have `serial` applied, because not a test function 52 | //! println!("Nothing"); 53 | //! } 54 | //! 55 | //! #[test] 56 | //! fn test_bar() { 57 | //! // Will be run serially 58 | //! } 59 | //!} 60 | //! ```` 61 | //! 62 | //! All of the attributes support an optional `crate` argument for other macros generating 63 | //! the attributes, which lets them re-export the serial_test crate and supply an import path. 64 | //! This defaults to assuming it can just import `serial_test` for the use of internal functions. 65 | //! Note this is `crate = ` not `crate => ` unlike the `path` in [file_serial](macro@file_serial) 66 | //! for historical reasons 67 | //! ```` 68 | //! // Assuming wrapper::refs:serial is a re-export of serial_test 69 | //! #[test] 70 | //! #[serial(crate = wrapper::refs:serial)] 71 | //! fn test_generated() { 72 | //! // Do things 73 | //! } 74 | //! ```` 75 | //! 76 | //! ## Feature flags 77 | #![cfg_attr( 78 | feature = "docsrs", 79 | cfg_attr(doc, doc = ::document_features::document_features!()) 80 | )] 81 | 82 | mod code_lock; 83 | mod parallel_code_lock; 84 | mod rwlock; 85 | mod serial_code_lock; 86 | 87 | #[cfg(feature = "file_locks")] 88 | mod file_lock; 89 | #[cfg(feature = "file_locks")] 90 | mod parallel_file_lock; 91 | #[cfg(feature = "file_locks")] 92 | mod serial_file_lock; 93 | 94 | #[cfg(feature = "async")] 95 | #[doc(hidden)] 96 | pub use parallel_code_lock::{local_async_parallel_core, local_async_parallel_core_with_return}; 97 | 98 | #[doc(hidden)] 99 | pub use parallel_code_lock::{local_parallel_core, local_parallel_core_with_return}; 100 | 101 | #[cfg(feature = "async")] 102 | #[doc(hidden)] 103 | pub use serial_code_lock::{local_async_serial_core, local_async_serial_core_with_return}; 104 | 105 | #[doc(hidden)] 106 | pub use serial_code_lock::{local_serial_core, local_serial_core_with_return}; 107 | 108 | #[cfg(all(feature = "file_locks", feature = "async"))] 109 | #[doc(hidden)] 110 | pub use serial_file_lock::{fs_async_serial_core, fs_async_serial_core_with_return}; 111 | 112 | #[cfg(feature = "file_locks")] 113 | #[doc(hidden)] 114 | pub use serial_file_lock::{fs_serial_core, fs_serial_core_with_return}; 115 | 116 | #[cfg(feature = "file_locks")] 117 | pub use file_lock::is_locked_file_serially; 118 | 119 | #[cfg(all(feature = "file_locks", feature = "async"))] 120 | #[doc(hidden)] 121 | pub use parallel_file_lock::{fs_async_parallel_core, fs_async_parallel_core_with_return}; 122 | 123 | #[cfg(feature = "file_locks")] 124 | #[doc(hidden)] 125 | pub use parallel_file_lock::{fs_parallel_core, fs_parallel_core_with_return}; 126 | 127 | // Re-export #[serial/parallel]. 128 | pub use serial_test_derive::{parallel, serial}; 129 | 130 | #[cfg(feature = "file_locks")] 131 | pub use serial_test_derive::{file_parallel, file_serial}; 132 | 133 | pub use code_lock::is_locked_serially; 134 | -------------------------------------------------------------------------------- /serial_test/src/rwlock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "logging")] 2 | use log::debug; 3 | use parking_lot::{Condvar, Mutex, ReentrantMutex, ReentrantMutexGuard}; 4 | use std::{sync::Arc, time::Duration}; 5 | 6 | struct LockState { 7 | parallels: u32, 8 | } 9 | 10 | struct LockData { 11 | mutex: Mutex, 12 | serial: ReentrantMutex<()>, 13 | condvar: Condvar, 14 | } 15 | 16 | #[derive(Clone)] 17 | pub(crate) struct Locks { 18 | arc: Arc, 19 | // Name we're locking for (mostly test usage) 20 | #[cfg(feature = "logging")] 21 | pub(crate) name: String, 22 | } 23 | 24 | pub(crate) struct MutexGuardWrapper<'a> { 25 | #[allow(dead_code)] // need it around to get dropped 26 | mutex_guard: ReentrantMutexGuard<'a, ()>, 27 | locks: Locks, 28 | } 29 | 30 | impl Drop for MutexGuardWrapper<'_> { 31 | fn drop(&mut self) { 32 | #[cfg(feature = "logging")] 33 | debug!("End serial"); 34 | self.locks.arc.condvar.notify_one(); 35 | } 36 | } 37 | 38 | impl Locks { 39 | #[allow(unused_variables)] 40 | pub fn new(name: &str) -> Locks { 41 | Locks { 42 | arc: Arc::new(LockData { 43 | mutex: Mutex::new(LockState { parallels: 0 }), 44 | condvar: Condvar::new(), 45 | serial: Default::default(), 46 | }), 47 | #[cfg(feature = "logging")] 48 | name: name.to_owned(), 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | pub fn is_locked(&self) -> bool { 54 | self.arc.serial.is_locked() 55 | } 56 | 57 | pub fn is_locked_by_current_thread(&self) -> bool { 58 | self.arc.serial.is_owned_by_current_thread() 59 | } 60 | 61 | pub fn serial(&self) -> MutexGuardWrapper<'_> { 62 | #[cfg(feature = "logging")] 63 | debug!("Get serial lock '{}'", self.name); 64 | let mut lock_state = self.arc.mutex.lock(); 65 | loop { 66 | #[cfg(feature = "logging")] 67 | debug!("Serial acquire {} {}", lock_state.parallels, self.name); 68 | // If all the things we want are true, try to lock out serial 69 | if lock_state.parallels == 0 { 70 | let possible_serial_lock = self.arc.serial.try_lock(); 71 | if let Some(serial_lock) = possible_serial_lock { 72 | #[cfg(feature = "logging")] 73 | debug!("Got serial '{}'", self.name); 74 | return MutexGuardWrapper { 75 | mutex_guard: serial_lock, 76 | locks: self.clone(), 77 | }; 78 | } else { 79 | #[cfg(feature = "logging")] 80 | debug!("Someone else has serial '{}'", self.name); 81 | } 82 | } 83 | 84 | self.arc 85 | .condvar 86 | .wait_for(&mut lock_state, Duration::from_secs(1)); 87 | } 88 | } 89 | 90 | pub fn start_parallel(&self) { 91 | #[cfg(feature = "logging")] 92 | debug!("Get parallel lock '{}'", self.name); 93 | let mut lock_state = self.arc.mutex.lock(); 94 | loop { 95 | #[cfg(feature = "logging")] 96 | debug!( 97 | "Parallel, existing {} '{}'", 98 | lock_state.parallels, self.name 99 | ); 100 | if lock_state.parallels > 0 { 101 | // fast path, as someone else already has it locked 102 | lock_state.parallels += 1; 103 | return; 104 | } 105 | 106 | let possible_serial_lock = self.arc.serial.try_lock(); 107 | if possible_serial_lock.is_some() { 108 | #[cfg(feature = "logging")] 109 | debug!("Parallel first '{}'", self.name); 110 | // We now know no-one else has the serial lock, so we can add to parallel 111 | lock_state.parallels = 1; // Had to have been 0 before, as otherwise we'd have hit the fast path 112 | return; 113 | } 114 | 115 | #[cfg(feature = "logging")] 116 | debug!("Parallel waiting '{}'", self.name); 117 | self.arc 118 | .condvar 119 | .wait_for(&mut lock_state, Duration::from_secs(1)); 120 | } 121 | } 122 | 123 | pub fn end_parallel(&self) { 124 | #[cfg(feature = "logging")] 125 | debug!("End parallel '{}", self.name); 126 | let mut lock_state = self.arc.mutex.lock(); 127 | assert!(lock_state.parallels > 0); 128 | lock_state.parallels -= 1; 129 | drop(lock_state); 130 | self.arc.condvar.notify_one(); 131 | } 132 | 133 | #[cfg(test)] 134 | pub fn parallel_count(&self) -> u32 { 135 | let lock_state = self.arc.mutex.lock(); 136 | lock_state.parallels 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /serial_test/src/parallel_file_lock.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | 3 | #[cfg(feature = "async")] 4 | use futures_util::FutureExt; 5 | 6 | use crate::file_lock::get_locks; 7 | 8 | #[doc(hidden)] 9 | pub fn fs_parallel_core(names: Vec<&str>, path: Option<&str>, function: fn()) { 10 | get_locks(&names, path) 11 | .iter_mut() 12 | .for_each(|lock| lock.start_parallel()); 13 | let res = panic::catch_unwind(|| { 14 | function(); 15 | }); 16 | get_locks(&names, path) 17 | .into_iter() 18 | .for_each(|lock| lock.end_parallel()); 19 | if let Err(err) = res { 20 | panic::resume_unwind(err); 21 | } 22 | } 23 | 24 | #[doc(hidden)] 25 | pub fn fs_parallel_core_with_return( 26 | names: Vec<&str>, 27 | path: Option<&str>, 28 | function: fn() -> Result<(), E>, 29 | ) -> Result<(), E> { 30 | get_locks(&names, path) 31 | .iter_mut() 32 | .for_each(|lock| lock.start_parallel()); 33 | let res = panic::catch_unwind(function); 34 | get_locks(&names, path) 35 | .into_iter() 36 | .for_each(|lock| lock.end_parallel()); 37 | match res { 38 | Ok(ret) => ret, 39 | Err(err) => { 40 | panic::resume_unwind(err); 41 | } 42 | } 43 | } 44 | 45 | #[doc(hidden)] 46 | #[cfg(feature = "async")] 47 | pub async fn fs_async_parallel_core_with_return( 48 | names: Vec<&str>, 49 | path: Option<&str>, 50 | fut: impl std::future::Future> + panic::UnwindSafe, 51 | ) -> Result<(), E> { 52 | get_locks(&names, path) 53 | .iter_mut() 54 | .for_each(|lock| lock.start_parallel()); 55 | let res = fut.catch_unwind().await; 56 | get_locks(&names, path) 57 | .into_iter() 58 | .for_each(|lock| lock.end_parallel()); 59 | match res { 60 | Ok(ret) => ret, 61 | Err(err) => { 62 | panic::resume_unwind(err); 63 | } 64 | } 65 | } 66 | 67 | #[doc(hidden)] 68 | #[cfg(feature = "async")] 69 | pub async fn fs_async_parallel_core( 70 | names: Vec<&str>, 71 | path: Option<&str>, 72 | fut: impl std::future::Future + panic::UnwindSafe, 73 | ) { 74 | get_locks(&names, path) 75 | .iter_mut() 76 | .for_each(|lock| lock.start_parallel()); 77 | 78 | let res = fut.catch_unwind().await; 79 | get_locks(&names, path) 80 | .into_iter() 81 | .for_each(|lock| lock.end_parallel()); 82 | if let Err(err) = res { 83 | panic::resume_unwind(err); 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | #[cfg(feature = "async")] 90 | use crate::{fs_async_parallel_core, fs_async_parallel_core_with_return}; 91 | 92 | use crate::{ 93 | file_lock::{path_for_name, Lock}, 94 | fs_parallel_core, fs_parallel_core_with_return, 95 | }; 96 | use std::{io::Error, panic}; 97 | 98 | fn unlock_ok(lock_path: &str) { 99 | let lock = Lock::new(lock_path); 100 | assert_eq!(lock.parallel_count, 0); 101 | } 102 | 103 | #[test] 104 | fn unlock_on_assert_sync_without_return() { 105 | let lock_path = path_for_name("parallel_unlock_on_assert_sync_without_return"); 106 | let _ = panic::catch_unwind(|| { 107 | fs_parallel_core( 108 | vec!["parallel_unlock_on_assert_sync_without_return"], 109 | Some(&lock_path), 110 | || { 111 | assert!(false); 112 | }, 113 | ) 114 | }); 115 | unlock_ok(&lock_path); 116 | } 117 | 118 | #[test] 119 | fn unlock_on_assert_sync_with_return() { 120 | let lock_path = path_for_name("unlock_on_assert_sync_with_return"); 121 | let _ = panic::catch_unwind(|| { 122 | fs_parallel_core_with_return( 123 | vec!["unlock_on_assert_sync_with_return"], 124 | Some(&lock_path), 125 | || -> Result<(), Error> { 126 | assert!(false); 127 | Ok(()) 128 | }, 129 | ) 130 | }); 131 | unlock_ok(&lock_path); 132 | } 133 | 134 | #[test] 135 | #[cfg(feature = "async")] 136 | fn unlock_on_assert_async_without_return() { 137 | let lock_path = path_for_name("unlock_on_assert_async_without_return"); 138 | async fn demo_assert() { 139 | assert!(false); 140 | } 141 | async fn call_serial_test_fn(lock_path: &str) { 142 | fs_async_parallel_core( 143 | vec!["unlock_on_assert_async_without_return"], 144 | Some(&lock_path), 145 | demo_assert(), 146 | ) 147 | .await 148 | } 149 | 150 | let _ = panic::catch_unwind(|| { 151 | futures_executor::block_on(call_serial_test_fn(&lock_path)); 152 | }); 153 | unlock_ok(&lock_path); 154 | } 155 | 156 | #[test] 157 | #[cfg(feature = "async")] 158 | fn unlock_on_assert_async_with_return() { 159 | let lock_path = path_for_name("unlock_on_assert_async_with_return"); 160 | 161 | async fn demo_assert() -> Result<(), Error> { 162 | assert!(false); 163 | Ok(()) 164 | } 165 | 166 | #[allow(unused_must_use)] 167 | async fn call_serial_test_fn(lock_path: &str) { 168 | fs_async_parallel_core_with_return( 169 | vec!["unlock_on_assert_async_with_return"], 170 | Some(&lock_path), 171 | demo_assert(), 172 | ) 173 | .await; 174 | } 175 | 176 | let _ = panic::catch_unwind(|| { 177 | futures_executor::block_on(call_serial_test_fn(&lock_path)); 178 | }); 179 | unlock_ok(&lock_path); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /serial_test/src/parallel_code_lock.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::await_holding_lock)] 2 | 3 | use crate::code_lock::{check_new_key, global_locks}; 4 | #[cfg(feature = "async")] 5 | use futures_util::FutureExt; 6 | use std::panic; 7 | 8 | fn get_locks(names: Vec<&str>) -> Vec { 9 | names 10 | .into_iter() 11 | .map(|name| { 12 | check_new_key(name); 13 | global_locks() 14 | .get(name) 15 | .expect("key to be set") 16 | .get() 17 | .clone() 18 | }) 19 | .collect::>() 20 | } 21 | 22 | #[doc(hidden)] 23 | pub fn local_parallel_core_with_return( 24 | names: Vec<&str>, 25 | _path: Option<&str>, 26 | function: fn() -> Result<(), E>, 27 | ) -> Result<(), E> { 28 | let locks = get_locks(names); 29 | 30 | locks.iter().for_each(|lock| lock.start_parallel()); 31 | let res = panic::catch_unwind(function); 32 | locks.iter().for_each(|lock| lock.end_parallel()); 33 | match res { 34 | Ok(ret) => ret, 35 | Err(err) => { 36 | panic::resume_unwind(err); 37 | } 38 | } 39 | } 40 | 41 | #[doc(hidden)] 42 | pub fn local_parallel_core(names: Vec<&str>, _path: Option<&str>, function: fn()) { 43 | let locks = get_locks(names); 44 | locks.iter().for_each(|lock| lock.start_parallel()); 45 | let res = panic::catch_unwind(|| { 46 | function(); 47 | }); 48 | locks.iter().for_each(|lock| lock.end_parallel()); 49 | if let Err(err) = res { 50 | panic::resume_unwind(err); 51 | } 52 | } 53 | 54 | #[doc(hidden)] 55 | #[cfg(feature = "async")] 56 | pub async fn local_async_parallel_core_with_return( 57 | names: Vec<&str>, 58 | _path: Option<&str>, 59 | fut: impl std::future::Future> + panic::UnwindSafe, 60 | ) -> Result<(), E> { 61 | let locks = get_locks(names); 62 | locks.iter().for_each(|lock| lock.start_parallel()); 63 | let res = fut.catch_unwind().await; 64 | locks.iter().for_each(|lock| lock.end_parallel()); 65 | match res { 66 | Ok(ret) => ret, 67 | Err(err) => { 68 | panic::resume_unwind(err); 69 | } 70 | } 71 | } 72 | 73 | #[doc(hidden)] 74 | #[cfg(feature = "async")] 75 | pub async fn local_async_parallel_core( 76 | names: Vec<&str>, 77 | _path: Option<&str>, 78 | fut: impl std::future::Future + panic::UnwindSafe, 79 | ) { 80 | let locks = get_locks(names); 81 | locks.iter().for_each(|lock| lock.start_parallel()); 82 | let res = fut.catch_unwind().await; 83 | locks.iter().for_each(|lock| lock.end_parallel()); 84 | if let Err(err) = res { 85 | panic::resume_unwind(err); 86 | } 87 | } 88 | 89 | #[cfg(test)] 90 | mod tests { 91 | #[cfg(feature = "async")] 92 | use crate::{local_async_parallel_core, local_async_parallel_core_with_return}; 93 | 94 | use crate::{code_lock::global_locks, local_parallel_core, local_parallel_core_with_return}; 95 | use std::{io::Error, panic}; 96 | 97 | #[test] 98 | fn unlock_on_assert_sync_without_return() { 99 | let _ = panic::catch_unwind(|| { 100 | local_parallel_core(vec!["unlock_on_assert_sync_without_return"], None, || { 101 | assert!(false); 102 | }) 103 | }); 104 | assert_eq!( 105 | global_locks() 106 | .get("unlock_on_assert_sync_without_return") 107 | .unwrap() 108 | .get() 109 | .parallel_count(), 110 | 0 111 | ); 112 | } 113 | 114 | #[test] 115 | fn unlock_on_assert_sync_with_return() { 116 | let _ = panic::catch_unwind(|| { 117 | local_parallel_core_with_return( 118 | vec!["unlock_on_assert_sync_with_return"], 119 | None, 120 | || -> Result<(), Error> { 121 | assert!(false); 122 | Ok(()) 123 | }, 124 | ) 125 | }); 126 | assert_eq!( 127 | global_locks() 128 | .get("unlock_on_assert_sync_with_return") 129 | .unwrap() 130 | .get() 131 | .parallel_count(), 132 | 0 133 | ); 134 | } 135 | 136 | #[test] 137 | #[cfg(feature = "async")] 138 | fn unlock_on_assert_async_without_return() { 139 | async fn demo_assert() { 140 | assert!(false); 141 | } 142 | async fn call_serial_test_fn() { 143 | local_async_parallel_core( 144 | vec!["unlock_on_assert_async_without_return"], 145 | None, 146 | demo_assert(), 147 | ) 148 | .await 149 | } 150 | // as per https://stackoverflow.com/a/66529014/320546 151 | let _ = panic::catch_unwind(|| { 152 | futures_executor::block_on(call_serial_test_fn()); 153 | }); 154 | assert_eq!( 155 | global_locks() 156 | .get("unlock_on_assert_async_without_return") 157 | .unwrap() 158 | .get() 159 | .parallel_count(), 160 | 0 161 | ); 162 | } 163 | 164 | #[test] 165 | #[cfg(feature = "async")] 166 | fn unlock_on_assert_async_with_return() { 167 | async fn demo_assert() -> Result<(), Error> { 168 | assert!(false); 169 | Ok(()) 170 | } 171 | 172 | #[allow(unused_must_use)] 173 | async fn call_serial_test_fn() { 174 | local_async_parallel_core_with_return( 175 | vec!["unlock_on_assert_async_with_return"], 176 | None, 177 | demo_assert(), 178 | ) 179 | .await; 180 | } 181 | 182 | // as per https://stackoverflow.com/a/66529014/320546 183 | let _ = panic::catch_unwind(|| { 184 | futures_executor::block_on(call_serial_test_fn()); 185 | }); 186 | assert_eq!( 187 | global_locks() 188 | .get("unlock_on_assert_async_with_return") 189 | .unwrap() 190 | .get() 191 | .parallel_count(), 192 | 0 193 | ); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /serial_test/src/code_lock.rs: -------------------------------------------------------------------------------- 1 | use crate::rwlock::{Locks, MutexGuardWrapper}; 2 | use once_cell::sync::OnceCell; 3 | use scc::{hash_map::Entry, HashMap}; 4 | use std::sync::atomic::AtomicU32; 5 | 6 | #[derive(Clone)] 7 | pub(crate) struct UniqueReentrantMutex { 8 | locks: Locks, 9 | 10 | // Only actually used for tests 11 | #[allow(dead_code)] 12 | pub(crate) id: u32, 13 | } 14 | 15 | impl UniqueReentrantMutex { 16 | pub(crate) fn lock(&self) -> MutexGuardWrapper<'_> { 17 | self.locks.serial() 18 | } 19 | 20 | pub(crate) fn start_parallel(&self) { 21 | self.locks.start_parallel(); 22 | } 23 | 24 | pub(crate) fn end_parallel(&self) { 25 | self.locks.end_parallel(); 26 | } 27 | 28 | #[cfg(test)] 29 | pub fn parallel_count(&self) -> u32 { 30 | self.locks.parallel_count() 31 | } 32 | 33 | #[cfg(test)] 34 | pub fn is_locked(&self) -> bool { 35 | self.locks.is_locked() 36 | } 37 | 38 | pub fn is_locked_by_current_thread(&self) -> bool { 39 | self.locks.is_locked_by_current_thread() 40 | } 41 | } 42 | 43 | #[inline] 44 | pub(crate) fn global_locks() -> &'static HashMap { 45 | #[cfg(feature = "test_logging")] 46 | let _ = env_logger::builder().try_init(); 47 | static LOCKS: OnceCell> = OnceCell::new(); 48 | LOCKS.get_or_init(HashMap::new) 49 | } 50 | 51 | /// Check if the current thread is holding a serial lock 52 | /// 53 | /// Can be used to assert that a piece of code can only be called 54 | /// from a test marked `#[serial]`. 55 | /// 56 | /// Example, with `#[serial]`: 57 | /// 58 | /// ``` 59 | /// use serial_test::{is_locked_serially, serial}; 60 | /// 61 | /// fn do_something_in_need_of_serialization() { 62 | /// assert!(is_locked_serially(None)); 63 | /// 64 | /// // ... 65 | /// } 66 | /// 67 | /// #[test] 68 | /// # fn unused() {} 69 | /// #[serial] 70 | /// fn main() { 71 | /// do_something_in_need_of_serialization(); 72 | /// } 73 | /// ``` 74 | /// 75 | /// Example, missing `#[serial]`: 76 | /// 77 | /// ```should_panic 78 | /// use serial_test::{is_locked_serially, serial}; 79 | /// 80 | /// #[test] 81 | /// # fn unused() {} 82 | /// // #[serial] // <-- missing 83 | /// fn main() { 84 | /// assert!(is_locked_serially(None)); 85 | /// } 86 | /// ``` 87 | /// 88 | /// Example, `#[test(some_key)]`: 89 | /// 90 | /// ``` 91 | /// use serial_test::{is_locked_serially, serial}; 92 | /// 93 | /// #[test] 94 | /// # fn unused() {} 95 | /// #[serial(some_key)] 96 | /// fn main() { 97 | /// assert!(is_locked_serially(Some("some_key"))); 98 | /// assert!(!is_locked_serially(None)); 99 | /// } 100 | /// ``` 101 | pub fn is_locked_serially(name: Option<&str>) -> bool { 102 | global_locks() 103 | .get(name.unwrap_or_default()) 104 | .map(|lock| lock.get().is_locked_by_current_thread()) 105 | .unwrap_or_default() 106 | } 107 | 108 | static MUTEX_ID: AtomicU32 = AtomicU32::new(1); 109 | 110 | impl UniqueReentrantMutex { 111 | fn new_mutex(name: &str) -> Self { 112 | Self { 113 | locks: Locks::new(name), 114 | id: MUTEX_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), 115 | } 116 | } 117 | } 118 | 119 | pub(crate) fn check_new_key(name: &str) { 120 | // Check if a new key is needed. Just need a read lock, which can be done in sync with everyone else 121 | if global_locks().contains(name) { 122 | return; 123 | }; 124 | 125 | // This is the rare path, which avoids the multi-writer situation mostly 126 | let entry = global_locks().entry(name.to_owned()); 127 | match entry { 128 | Entry::Occupied(o) => o, 129 | Entry::Vacant(v) => v.insert_entry(UniqueReentrantMutex::new_mutex(name)), 130 | }; 131 | } 132 | 133 | #[cfg(test)] 134 | mod tests { 135 | use super::*; 136 | use crate::{local_parallel_core, local_serial_core}; 137 | 138 | #[test] 139 | fn assert_serially_locked_without_name() { 140 | local_serial_core(vec![""], None, || { 141 | assert!(is_locked_serially(None)); 142 | assert!(!is_locked_serially(Some( 143 | "no_such_name_assert_serially_locked_without_name" 144 | ))); 145 | }); 146 | } 147 | 148 | #[test] 149 | fn assert_serially_locked_with_multiple_names() { 150 | const NAME1: &str = "assert_serially_locked_with_multiple_names-NAME1"; 151 | const NAME2: &str = "assert_serially_locked_with_multiple_names-NAME2"; 152 | local_serial_core(vec![NAME1, NAME2], None, || { 153 | assert!(is_locked_serially(Some(NAME1))); 154 | assert!(is_locked_serially(Some(NAME2))); 155 | assert!(!is_locked_serially(Some( 156 | "no_such_name_assert_serially_locked_with_multiple_names" 157 | ))); 158 | }); 159 | } 160 | 161 | #[test] 162 | fn assert_serially_locked_when_actually_locked_parallel() { 163 | const NAME1: &str = "assert_serially_locked_when_actually_locked_parallel-NAME1"; 164 | const NAME2: &str = "assert_serially_locked_when_actually_locked_parallel-NAME2"; 165 | local_parallel_core(vec![NAME1, NAME2], None, || { 166 | assert!(!is_locked_serially(Some(NAME1))); 167 | assert!(!is_locked_serially(Some(NAME2))); 168 | assert!(!is_locked_serially(Some( 169 | "no_such_name_assert_serially_locked_when_actually_locked_parallel" 170 | ))); 171 | }); 172 | } 173 | 174 | #[test] 175 | fn assert_serially_locked_outside_serial_lock() { 176 | const NAME1: &str = "assert_serially_locked_outside_serial_lock-NAME1"; 177 | const NAME2: &str = "assert_serially_locked_outside_serial_lock-NAME2"; 178 | assert!(!is_locked_serially(Some(NAME1))); 179 | assert!(!is_locked_serially(Some(NAME2))); 180 | 181 | local_serial_core(vec![NAME1], None, || { 182 | // ... 183 | }); 184 | 185 | assert!(!is_locked_serially(Some(NAME1))); 186 | assert!(!is_locked_serially(Some(NAME2))); 187 | } 188 | 189 | #[test] 190 | fn assert_serially_locked_in_different_thread() { 191 | const NAME1: &str = "assert_serially_locked_in_different_thread-NAME1"; 192 | const NAME2: &str = "assert_serially_locked_in_different_thread-NAME2"; 193 | local_serial_core(vec![NAME1, NAME2], None, || { 194 | std::thread::spawn(|| { 195 | assert!(!is_locked_serially(Some(NAME2))); 196 | }) 197 | .join() 198 | .unwrap(); 199 | }); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /serial_test/src/file_lock.rs: -------------------------------------------------------------------------------- 1 | use fslock::LockFile; 2 | #[cfg(feature = "logging")] 3 | use log::debug; 4 | use std::{ 5 | env, 6 | fs::{self, File}, 7 | io::{Read, Write}, 8 | path::Path, 9 | thread, 10 | time::Duration, 11 | }; 12 | 13 | pub(crate) struct Lock { 14 | lockfile: LockFile, 15 | pub(crate) parallel_count: u32, 16 | path: String, 17 | } 18 | 19 | impl Lock { 20 | // Can't use the same file as fslock truncates it 21 | fn gen_count_file(path: &str) -> String { 22 | format!("{}-count", path) 23 | } 24 | 25 | fn read_parallel_count(path: &str) -> u32 { 26 | let parallel_count = match File::open(Lock::gen_count_file(path)) { 27 | Ok(mut file) => { 28 | let mut count_buf = [0; 4]; 29 | match file.read_exact(&mut count_buf) { 30 | Ok(_) => u32::from_ne_bytes(count_buf), 31 | Err(_err) => { 32 | #[cfg(feature = "logging")] 33 | debug!("Error loading count file: {}", _err); 34 | 0u32 35 | } 36 | } 37 | } 38 | Err(_) => 0, 39 | }; 40 | 41 | #[cfg(feature = "logging")] 42 | debug!("Parallel count for {:?} is {}", path, parallel_count); 43 | parallel_count 44 | } 45 | 46 | fn create_lockfile(path: &str) -> LockFile { 47 | if !Path::new(path).exists() { 48 | fs::write(path, "").unwrap_or_else(|_| panic!("Lock file path was {:?}", path)) 49 | } 50 | LockFile::open(path).unwrap() 51 | } 52 | 53 | pub(crate) fn new(path: &str) -> Lock { 54 | #[cfg(feature = "test_logging")] 55 | let _ = env_logger::builder().try_init(); 56 | 57 | let mut lockfile = Self::create_lockfile(path); 58 | 59 | #[cfg(feature = "logging")] 60 | debug!("Waiting on {:?}", path); 61 | 62 | lockfile.lock().unwrap(); 63 | 64 | #[cfg(feature = "logging")] 65 | debug!("Locked for {:?}", path); 66 | 67 | Lock { 68 | lockfile, 69 | parallel_count: Lock::read_parallel_count(path), 70 | path: String::from(path), 71 | } 72 | } 73 | 74 | pub(crate) fn is_locked(path: &str) -> bool { 75 | let mut lockfile = Self::create_lockfile(path); 76 | 77 | #[cfg(feature = "logging")] 78 | debug!("Checking lock on {:?}", path); 79 | 80 | if lockfile 81 | .try_lock() 82 | .expect("try_lock shouldn't generally fail, please provide a bug report") 83 | { 84 | #[cfg(feature = "test_logging")] 85 | debug!("{:?} wasn't locked", path); 86 | lockfile 87 | .unlock() 88 | .expect("unlock shouldn't generally fail, please provide a bug report"); 89 | false 90 | } else { 91 | #[cfg(feature = "test_logging")] 92 | debug!("{:?} was locked", path); 93 | true 94 | } 95 | } 96 | 97 | pub(crate) fn start_serial(self: &mut Lock) { 98 | loop { 99 | if self.parallel_count == 0 { 100 | return; 101 | } 102 | #[cfg(feature = "logging")] 103 | debug!("Waiting because parallel count is {}", self.parallel_count); 104 | // unlock here is safe because we re-lock before returning 105 | self.unlock(); 106 | thread::sleep(Duration::from_millis(50)); 107 | self.lockfile 108 | .lock() 109 | .expect("unlock shouldn't generally fail, please provide a bug report"); 110 | #[cfg(feature = "logging")] 111 | debug!("Locked for {:?}", self.path); 112 | self.parallel_count = Lock::read_parallel_count(&self.path) 113 | } 114 | } 115 | 116 | fn unlock(self: &mut Lock) { 117 | #[cfg(feature = "logging")] 118 | debug!("Unlocking {}", self.path); 119 | self.lockfile.unlock().unwrap(); 120 | } 121 | 122 | pub(crate) fn end_serial(mut self: Lock) { 123 | self.unlock(); 124 | } 125 | 126 | fn write_parallel(self: &Lock) { 127 | let mut file = File::create(&Lock::gen_count_file(&self.path)).unwrap(); 128 | file.write_all(&self.parallel_count.to_ne_bytes()).unwrap(); 129 | } 130 | 131 | pub(crate) fn start_parallel(self: &mut Lock) { 132 | self.parallel_count += 1; 133 | self.write_parallel(); 134 | self.unlock(); 135 | } 136 | 137 | pub(crate) fn end_parallel(mut self: Lock) { 138 | assert!(self.parallel_count > 0); 139 | self.parallel_count -= 1; 140 | self.write_parallel(); 141 | self.unlock(); 142 | } 143 | } 144 | 145 | pub(crate) fn path_for_name(name: &str) -> String { 146 | let mut pathbuf = env::temp_dir(); 147 | pathbuf.push(format!("serial-test-{}", name)); 148 | pathbuf.into_os_string().into_string().unwrap() 149 | } 150 | 151 | fn make_lock_for_name_and_path(name: &str, path_str: Option<&str>) -> Lock { 152 | if let Some(opt_path) = path_str { 153 | #[cfg(feature = "logging")] 154 | { 155 | let path = Path::new(opt_path); 156 | if !path.is_absolute() { 157 | debug!( 158 | "Non-absolute path {opt_path} becomes {:?}", 159 | path.canonicalize().unwrap() 160 | ); 161 | } 162 | } 163 | Lock::new(opt_path) 164 | } else { 165 | let default_path = path_for_name(name); 166 | Lock::new(&default_path) 167 | } 168 | } 169 | 170 | pub(crate) fn get_locks(names: &Vec<&str>, path: Option<&str>) -> Vec { 171 | if names.len() > 1 && path.is_some() { 172 | panic!("Can't do file_serial/parallel with both more than one name _and_ a specific path"); 173 | } 174 | names 175 | .iter() 176 | .map(|name| make_lock_for_name_and_path(name, path)) 177 | .collect::>() 178 | } 179 | 180 | /// Check if the current thread is holding a `file_serial` lock 181 | /// 182 | /// Can be used to assert that a piece of code can only be called 183 | /// from a test marked `#[file_serial]`. 184 | /// 185 | /// Example, with `#[file_serial]`: 186 | /// 187 | /// ```no_run 188 | /// use serial_test::{is_locked_file_serially, file_serial}; 189 | /// 190 | /// fn do_something_in_need_of_serialization() { 191 | /// assert!(is_locked_file_serially(None, None)); 192 | /// 193 | /// // ... 194 | /// } 195 | /// 196 | /// #[test] 197 | /// # fn unused() {} 198 | /// #[file_serial] 199 | /// fn main() { 200 | /// do_something_in_need_of_serialization(); 201 | /// } 202 | /// ``` 203 | /// 204 | /// Example, missing `#[file_serial]`: 205 | /// 206 | /// ```no_run 207 | /// use serial_test::{is_locked_file_serially, file_serial}; 208 | /// 209 | /// #[test] 210 | /// # fn unused() {} 211 | /// // #[file_serial] // <-- missing 212 | /// fn main() { 213 | /// assert!(is_locked_file_serially(None, None)); 214 | /// } 215 | /// ``` 216 | /// 217 | /// Example, `#[test(some_key)]`: 218 | /// 219 | /// ```no_run 220 | /// use serial_test::{is_locked_file_serially, file_serial}; 221 | /// 222 | /// #[test] 223 | /// # fn unused() {} 224 | /// #[file_serial(some_key)] 225 | /// fn main() { 226 | /// assert!(is_locked_file_serially(Some("some_key"), None)); 227 | /// assert!(!is_locked_file_serially(None, None)); 228 | /// } 229 | /// ``` 230 | pub fn is_locked_file_serially(name: Option<&str>, path: Option<&str>) -> bool { 231 | if let Some(opt_path) = path { 232 | Lock::is_locked(opt_path) 233 | } else { 234 | let default_path = path_for_name(name.unwrap_or_default()); 235 | Lock::is_locked(&default_path) 236 | } 237 | } 238 | 239 | #[cfg(test)] 240 | mod tests { 241 | use super::*; 242 | use crate::{fs_parallel_core, fs_serial_core}; 243 | 244 | fn init() { 245 | #[cfg(feature = "test_logging")] 246 | let _ = env_logger::builder().is_test(false).try_init(); 247 | } 248 | 249 | #[test] 250 | fn assert_serially_locked_without_name() { 251 | init(); 252 | fs_serial_core(vec![""], None, || { 253 | assert!(is_locked_file_serially(None, None)); 254 | assert!(!is_locked_file_serially( 255 | Some("no_such_name_assert_serially_locked_without_name"), 256 | None 257 | )); 258 | }); 259 | } 260 | 261 | #[test] 262 | fn assert_serially_locked_with_multiple_names() { 263 | const NAME1: &str = "assert_serially_locked_with_multiple_names-NAME1"; 264 | const NAME2: &str = "assert_serially_locked_with_multiple_names-NAME2"; 265 | init(); 266 | 267 | fs_serial_core(vec![NAME1, NAME2], None, || { 268 | assert!(is_locked_file_serially(Some(NAME1), None)); 269 | assert!(is_locked_file_serially(Some(NAME2), None)); 270 | assert!(!is_locked_file_serially( 271 | Some("no_such_name_assert_serially_locked_with_multiple_names"), 272 | None 273 | )); 274 | }); 275 | } 276 | 277 | #[test] 278 | fn assert_serially_locked_when_actually_locked_parallel() { 279 | const NAME1: &str = "assert_serially_locked_when_actually_locked_parallel-NAME1"; 280 | const NAME2: &str = "assert_serially_locked_when_actually_locked_parallel-NAME2"; 281 | init(); 282 | 283 | fs_parallel_core(vec![NAME1, NAME2], None, || { 284 | assert!(!is_locked_file_serially(Some(NAME1), None)); 285 | assert!(!is_locked_file_serially(Some(NAME2), None)); 286 | assert!(!is_locked_file_serially( 287 | Some("no_such_name_assert_serially_locked_when_actually_locked_parallel"), 288 | None 289 | )); 290 | }); 291 | } 292 | 293 | #[test] 294 | fn assert_serially_locked_outside_serial_lock() { 295 | const NAME1: &str = "assert_serially_locked_outside_serial_lock-NAME1"; 296 | const NAME2: &str = "assert_serially_locked_outside_serial_lock-NAME2"; 297 | init(); 298 | 299 | assert!(!is_locked_file_serially(Some(NAME1), None)); 300 | assert!(!is_locked_file_serially(Some(NAME2), None)); 301 | 302 | fs_serial_core(vec![NAME1], None, || { 303 | // ... 304 | }); 305 | 306 | assert!(!is_locked_file_serially(Some(NAME1), None)); 307 | assert!(!is_locked_file_serially(Some(NAME2), None)); 308 | } 309 | 310 | #[test] 311 | fn assert_serially_locked_in_different_thread() { 312 | const NAME1: &str = "assert_serially_locked_in_different_thread-NAME1"; 313 | const NAME2: &str = "assert_serially_locked_in_different_thread-NAME2"; 314 | 315 | init(); 316 | fs_serial_core(vec![NAME1, NAME2], None, || { 317 | std::thread::spawn(|| { 318 | assert!(is_locked_file_serially(Some(NAME2), None)); 319 | }) 320 | .join() 321 | .unwrap(); 322 | }); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /serial_test_test/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Not inside the cfg(test) block because of 2 | //! ``` 3 | //! #[macro_use] extern crate serial_test; 4 | //! extern crate serial_test_test; 5 | //! use serial_test_test::{fs_test_fn}; 6 | //! #[cfg(feature = "file_locks")] 7 | //! #[serial_test::file_serial] 8 | //! fn main() { 9 | //! fs_test_fn(1, "docs"); 10 | //! } 11 | //! #[cfg(not(feature = "file_locks"))] 12 | //! fn main() {} 13 | //! ``` 14 | //! ``` 15 | //! #[macro_use] extern crate serial_test; 16 | //! extern crate serial_test_test; 17 | //! use serial_test_test::{fs_test_fn}; 18 | //! #[cfg(feature = "file_locks")] 19 | //! #[serial_test::file_serial] 20 | //! fn main() { 21 | //! fs_test_fn(2, "docs"); 22 | //! } 23 | //! #[cfg(not(feature = "file_locks"))] 24 | //! fn main() {} 25 | //! ``` 26 | //! ``` 27 | //! #[macro_use] extern crate serial_test; 28 | //! extern crate serial_test_test; 29 | //! use serial_test_test::{fs_test_fn}; 30 | //! #[cfg(feature = "file_locks")] 31 | //! #[serial_test::file_serial] 32 | //! fn main() { 33 | //! fs_test_fn(3, "docs"); 34 | //! } 35 | //! #[cfg(not(feature = "file_locks"))] 36 | //! fn main() {} 37 | //! ``` 38 | 39 | use log::info; 40 | use once_cell::sync::OnceCell; 41 | use scc::HashMap; 42 | #[cfg(test)] 43 | use serial_test::{parallel, serial}; 44 | use std::{ 45 | convert::TryInto, 46 | env, fs, 47 | path::PathBuf, 48 | sync::atomic::{AtomicUsize, Ordering}, 49 | thread, 50 | time::Duration, 51 | }; 52 | 53 | static LOCKS: OnceCell> = OnceCell::new(); 54 | 55 | fn init() { 56 | let _ = env_logger::builder().is_test(false).try_init(); 57 | } 58 | 59 | pub fn test_fn(key: &str, count: usize) { 60 | init(); 61 | let local_locks = LOCKS.get_or_init(HashMap::new); 62 | let entry = local_locks 63 | .entry(key.to_string()) 64 | .or_insert(AtomicUsize::new(0)); 65 | let local_lock = entry.get(); 66 | info!("(non-fs) Start {}", count); 67 | local_lock.store(count, Ordering::Relaxed); 68 | thread::sleep(Duration::from_millis(1000 * (count as u64))); 69 | info!("(non-fs) End {}", count); 70 | assert_eq!(local_lock.load(Ordering::Relaxed), count); 71 | } 72 | 73 | fn get_fs_path() -> PathBuf { 74 | static FS_PATH: OnceCell = OnceCell::new(); 75 | FS_PATH.get_or_init(env::temp_dir).clone() 76 | } 77 | 78 | pub fn fs_test_fn(count: usize, suffix: &str) { 79 | init(); 80 | info!("(fs) Start {}", count); 81 | let mut pathbuf = get_fs_path(); 82 | pathbuf.push(format!("serial-test-test_{suffix}")); 83 | fs::write(pathbuf.as_path(), count.to_ne_bytes()).unwrap(); 84 | thread::sleep(Duration::from_millis(1000 * (count as u64))); 85 | info!("(fs) End {}", count); 86 | 87 | let loaded = fs::read(pathbuf.as_path()) 88 | .map(|bytes| usize::from_ne_bytes(bytes.as_slice().try_into().unwrap())) 89 | .unwrap(); 90 | assert_eq!(loaded, count); 91 | } 92 | 93 | #[cfg(test)] 94 | #[serial] 95 | mod serial_attr_tests {} 96 | 97 | #[cfg(test)] 98 | #[parallel] 99 | mod parallel_attr_tests {} 100 | 101 | #[cfg(feature = "file_locks")] 102 | pub const RELATIVE_FS: &str = "relative-fs"; 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::{init, test_fn}; 107 | use log::info; 108 | use once_cell::sync::OnceCell; 109 | use parking_lot::Mutex; 110 | use serial_test::{parallel, serial}; 111 | use std::{sync::Barrier, thread, time::Duration}; 112 | #[cfg(feature = "async")] 113 | use wasm_bindgen_test::wasm_bindgen_test; 114 | 115 | static THREAD_ORDERINGS: Mutex> = Mutex::new(Vec::new()); 116 | 117 | #[inline] 118 | fn parallel_barrier() -> &'static Barrier { 119 | static PARALLEL_BARRIER: OnceCell = OnceCell::new(); 120 | PARALLEL_BARRIER.get_or_init(|| Barrier::new(3)) 121 | } 122 | 123 | #[cfg(feature = "file_locks")] 124 | static FS_THREAD_ORDERINGS: Mutex> = Mutex::new(Vec::new()); 125 | 126 | #[cfg(feature = "file_locks")] 127 | #[inline] 128 | fn fs_parallel_barrier() -> &'static Barrier { 129 | static FS_PARALLEL_BARRIER: OnceCell = OnceCell::new(); 130 | FS_PARALLEL_BARRIER.get_or_init(|| Barrier::new(3)) 131 | } 132 | 133 | #[cfg(feature = "file_locks")] 134 | use super::fs_test_fn; 135 | #[cfg(feature = "file_locks")] 136 | use serial_test::{file_parallel, file_serial}; 137 | 138 | #[test] 139 | #[serial] 140 | fn test_serial_no_arg() { 141 | init(); 142 | } 143 | 144 | #[test] 145 | #[serial(one, two)] 146 | fn test_serial_multi_arg() { 147 | init(); 148 | } 149 | 150 | #[test] 151 | #[serial(alpha)] 152 | fn test_serial_1() { 153 | test_fn("alpha", 1) 154 | } 155 | 156 | #[test] 157 | #[serial(alpha)] 158 | fn test_serial_2() { 159 | test_fn("alpha", 2) 160 | } 161 | 162 | #[test] 163 | #[serial(alpha)] 164 | fn test_serial_3() { 165 | test_fn("alpha", 3) 166 | } 167 | 168 | #[test] 169 | #[serial] 170 | #[ignore] 171 | fn test_ignore_fun() { 172 | init(); 173 | assert_eq!(2 + 2, 5); 174 | } 175 | 176 | #[test] 177 | #[serial] 178 | fn test_reentrant_fun() { 179 | init(); 180 | test_serial_no_arg(); 181 | } 182 | 183 | #[test] 184 | #[serial] 185 | #[should_panic] 186 | fn test_should_panic_fun() { 187 | init(); 188 | panic!("Testing panic"); 189 | } 190 | 191 | #[test] 192 | #[serial] 193 | fn test_can_return() -> Result<(), ()> { 194 | init(); 195 | Ok(()) 196 | } 197 | 198 | #[cfg(feature = "async")] 199 | #[tokio::test] 200 | #[serial] 201 | async fn test_async_serial_no_arg_tokio_first() { 202 | init(); 203 | } 204 | 205 | #[cfg(feature = "async")] 206 | #[serial] 207 | #[tokio::test] 208 | async fn test_async_serial_no_arg_serial_first() { 209 | init(); 210 | } 211 | 212 | #[cfg(feature = "async")] 213 | #[serial] 214 | #[actix_rt::test] 215 | async fn test_async_serial_no_arg_actix_with_serial_firs() { 216 | init(); 217 | } 218 | 219 | #[cfg(feature = "async")] 220 | #[actix_rt::test] 221 | #[serial] 222 | async fn test_async_serial_no_arg_actix_first() { 223 | init(); 224 | } 225 | 226 | #[cfg(feature = "async")] 227 | #[tokio::test] 228 | #[serial] 229 | async fn test_async_can_return() -> Result<(), ()> { 230 | init(); 231 | Ok(()) 232 | } 233 | 234 | #[cfg(feature = "file_locks")] 235 | const BASIC_FS_CHECK: &str = "basic-fs"; 236 | 237 | #[cfg(feature = "file_locks")] 238 | #[test] 239 | #[file_serial] 240 | fn test_file_1() { 241 | fs_test_fn(1, BASIC_FS_CHECK); 242 | } 243 | 244 | #[cfg(feature = "file_locks")] 245 | #[test] 246 | #[file_serial] 247 | fn test_file_2() { 248 | fs_test_fn(2, BASIC_FS_CHECK); 249 | } 250 | 251 | #[cfg(feature = "file_locks")] 252 | #[test] 253 | #[file_serial] 254 | fn test_file_3() { 255 | fs_test_fn(3, BASIC_FS_CHECK); 256 | } 257 | 258 | #[cfg(all(feature = "file_locks", not(windows)))] 259 | #[test] 260 | #[file_serial(test, path => "/tmp/test")] 261 | fn test_file_with_path() {} 262 | 263 | #[cfg(all(feature = "file_locks", not(windows)))] 264 | #[test] 265 | #[file_serial(path => "/tmp/test")] 266 | fn test_file_with_path_and_no_key() {} 267 | 268 | #[test] 269 | #[serial(test_key)] 270 | fn test_with_key() { 271 | init(); 272 | } 273 | 274 | #[test] 275 | #[serial(ordering_key)] 276 | fn serial_with_parallel_key_1() { 277 | let count = THREAD_ORDERINGS.lock().len(); 278 | // Can't guarantee before or after the parallels 279 | assert!(count == 0 || count == 3, "count = {}", count); 280 | } 281 | 282 | #[test] 283 | #[parallel(ordering_key)] 284 | fn parallel_with_key_1() { 285 | thread::sleep(Duration::from_secs(1)); 286 | info!("Waiting barrier 1"); 287 | parallel_barrier().wait(); 288 | info!("Waiting lock 1"); 289 | THREAD_ORDERINGS.lock().push(false); 290 | } 291 | 292 | #[test] 293 | #[parallel(ordering_key)] 294 | fn parallel_with_key_2() { 295 | thread::sleep(Duration::from_secs(2)); 296 | info!("Waiting barrier 2"); 297 | parallel_barrier().wait(); 298 | info!("Waiting lock 2"); 299 | THREAD_ORDERINGS.lock().push(false); 300 | } 301 | 302 | #[test] 303 | #[parallel(ordering_key)] 304 | fn parallel_with_key_3() { 305 | thread::sleep(Duration::from_secs(3)); 306 | info!("Waiting barrier 3"); 307 | parallel_barrier().wait(); 308 | info!("Waiting lock 3"); 309 | THREAD_ORDERINGS.lock().push(false); 310 | } 311 | 312 | #[test] 313 | #[serial(ordering_key)] 314 | fn serial_with_parallel_key_2() { 315 | let count = THREAD_ORDERINGS.lock().len(); 316 | // Can't guarantee before or after the parallels 317 | assert!(count == 0 || count == 3, "count = {}", count); 318 | } 319 | 320 | #[cfg(feature = "file_locks")] 321 | #[test] 322 | #[file_serial(ordering_key)] 323 | fn file_serial_with_parallel_key_1() { 324 | let count = FS_THREAD_ORDERINGS.lock().len(); 325 | // Can't guarantee before or after the parallels 326 | assert!(count == 0 || count == 3, "count = {}", count); 327 | } 328 | 329 | #[cfg(feature = "file_locks")] 330 | #[test] 331 | #[file_serial(ordering_key)] 332 | fn file_serial_with_parallel_key_2() { 333 | let count = FS_THREAD_ORDERINGS.lock().len(); 334 | // Can't guarantee before or after the parallels 335 | assert!(count == 0 || count == 3, "count = {}", count); 336 | } 337 | 338 | #[cfg(feature = "file_locks")] 339 | #[test] 340 | #[file_parallel(ordering_key)] 341 | fn file_parallel_with_key_1() { 342 | init(); 343 | thread::sleep(Duration::from_secs(1)); 344 | info!("Waiting barrier 1"); 345 | fs_parallel_barrier().wait(); 346 | info!("Waiting lock 1"); 347 | FS_THREAD_ORDERINGS.lock().push(false); 348 | } 349 | 350 | #[cfg(feature = "file_locks")] 351 | #[test] 352 | #[file_parallel(ordering_key)] 353 | fn file_parallel_with_key_2() { 354 | init(); 355 | thread::sleep(Duration::from_secs(1)); 356 | info!("Waiting barrier 2"); 357 | fs_parallel_barrier().wait(); 358 | info!("Waiting lock 2"); 359 | FS_THREAD_ORDERINGS.lock().push(false); 360 | } 361 | 362 | #[cfg(feature = "file_locks")] 363 | #[test] 364 | #[file_parallel(ordering_key)] 365 | fn file_parallel_with_key_3() { 366 | init(); 367 | thread::sleep(Duration::from_secs(1)); 368 | info!("Waiting barrier 3"); 369 | fs_parallel_barrier().wait(); 370 | info!("Waiting lock 3"); 371 | FS_THREAD_ORDERINGS.lock().push(false); 372 | } 373 | 374 | #[cfg(feature = "file_locks")] 375 | #[test] 376 | #[file_parallel] 377 | fn file_parallel_with_return() -> Result<(), ()> { 378 | init(); 379 | Ok(()) 380 | } 381 | 382 | #[cfg(all(feature = "file_locks", feature = "async"))] 383 | #[tokio::test] 384 | #[file_parallel] 385 | async fn file_parallel_with_async_return() -> Result<(), ()> { 386 | Ok(()) 387 | } 388 | 389 | #[cfg(all(feature = "file_locks", feature = "async"))] 390 | #[tokio::test] 391 | #[file_parallel] 392 | async fn file_parallel_with_async() { 393 | init(); 394 | } 395 | 396 | // Note, not actually a test as such, just a "can you wrap serial functions" compile-time check 397 | #[cfg(feature = "async")] 398 | #[serial] 399 | async fn async_attribute_works() {} 400 | 401 | #[cfg(feature = "async")] 402 | #[serial] 403 | async fn async_attribute_works_with_return() -> Result<(), ()> { 404 | Ok(()) 405 | } 406 | 407 | #[cfg(feature = "async")] 408 | #[wasm_bindgen_test] 409 | #[serial] 410 | async fn wasm_works_first() {} 411 | 412 | #[cfg(feature = "async")] 413 | #[serial] 414 | #[wasm_bindgen_test] 415 | async fn wasm_works_second() {} 416 | 417 | #[tokio::test(flavor = "multi_thread")] 418 | #[serial(slt)] 419 | async fn tokio_multi_1() { 420 | test_fn("tokio", 1); 421 | } 422 | 423 | #[tokio::test(flavor = "multi_thread")] 424 | #[serial(slt)] 425 | async fn tokio_multi_2() { 426 | test_fn("tokio", 2); 427 | } 428 | 429 | #[tokio::test(flavor = "multi_thread")] 430 | #[serial(slt)] 431 | async fn tokio_multi_3() { 432 | test_fn("tokio", 3); 433 | } 434 | 435 | #[test] 436 | #[serial] 437 | fn nested_return() -> Result, ()> { 438 | Ok(Ok(())) 439 | } 440 | 441 | #[cfg(feature = "async")] 442 | #[tokio::test] 443 | #[serial] 444 | async fn nested_async_return() -> Result, ()> { 445 | Ok(Ok(())) 446 | } 447 | 448 | #[test] 449 | #[serial(crate = serial_test)] 450 | fn crate_path() { 451 | init(); 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "actix-macros" 7 | version = "0.2.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" 10 | dependencies = [ 11 | "quote", 12 | "syn 1.0.109", 13 | ] 14 | 15 | [[package]] 16 | name = "actix-rt" 17 | version = "2.8.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" 20 | dependencies = [ 21 | "actix-macros", 22 | "futures-core", 23 | "tokio", 24 | ] 25 | 26 | [[package]] 27 | name = "addr2line" 28 | version = "0.24.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 31 | dependencies = [ 32 | "gimli", 33 | ] 34 | 35 | [[package]] 36 | name = "adler2" 37 | version = "2.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 40 | 41 | [[package]] 42 | name = "autocfg" 43 | version = "1.1.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 46 | 47 | [[package]] 48 | name = "backtrace" 49 | version = "0.3.75" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 52 | dependencies = [ 53 | "addr2line", 54 | "cfg-if", 55 | "libc", 56 | "miniz_oxide", 57 | "object", 58 | "rustc-demangle", 59 | "windows-targets 0.52.6", 60 | ] 61 | 62 | [[package]] 63 | name = "bitflags" 64 | version = "1.3.2" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 67 | 68 | [[package]] 69 | name = "bumpalo" 70 | version = "3.12.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 73 | 74 | [[package]] 75 | name = "cc" 76 | version = "1.2.16" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 79 | dependencies = [ 80 | "shlex", 81 | ] 82 | 83 | [[package]] 84 | name = "cfg-if" 85 | version = "1.0.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 88 | 89 | [[package]] 90 | name = "document-features" 91 | version = "0.2.7" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" 94 | dependencies = [ 95 | "litrs", 96 | ] 97 | 98 | [[package]] 99 | name = "either" 100 | version = "1.8.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 103 | 104 | [[package]] 105 | name = "env_logger" 106 | version = "0.10.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" 109 | dependencies = [ 110 | "log", 111 | ] 112 | 113 | [[package]] 114 | name = "fslock" 115 | version = "0.2.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" 118 | dependencies = [ 119 | "libc", 120 | "winapi", 121 | ] 122 | 123 | [[package]] 124 | name = "futures-core" 125 | version = "0.3.27" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" 128 | 129 | [[package]] 130 | name = "futures-executor" 131 | version = "0.3.27" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" 134 | dependencies = [ 135 | "futures-core", 136 | "futures-task", 137 | "futures-util", 138 | ] 139 | 140 | [[package]] 141 | name = "futures-task" 142 | version = "0.3.27" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" 145 | 146 | [[package]] 147 | name = "futures-util" 148 | version = "0.3.27" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" 151 | dependencies = [ 152 | "futures-core", 153 | "futures-task", 154 | "pin-project-lite", 155 | "pin-utils", 156 | "slab", 157 | ] 158 | 159 | [[package]] 160 | name = "gimli" 161 | version = "0.31.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 164 | 165 | [[package]] 166 | name = "hermit-abi" 167 | version = "0.5.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 170 | 171 | [[package]] 172 | name = "itertools" 173 | version = "0.10.5" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 176 | dependencies = [ 177 | "either", 178 | ] 179 | 180 | [[package]] 181 | name = "js-sys" 182 | version = "0.3.77" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 185 | dependencies = [ 186 | "once_cell", 187 | "wasm-bindgen", 188 | ] 189 | 190 | [[package]] 191 | name = "libc" 192 | version = "0.2.174" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 195 | 196 | [[package]] 197 | name = "litrs" 198 | version = "0.2.3" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" 201 | 202 | [[package]] 203 | name = "lock_api" 204 | version = "0.4.9" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 207 | dependencies = [ 208 | "autocfg", 209 | "scopeguard", 210 | ] 211 | 212 | [[package]] 213 | name = "log" 214 | version = "0.4.20" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 217 | 218 | [[package]] 219 | name = "memchr" 220 | version = "2.7.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 223 | 224 | [[package]] 225 | name = "minicov" 226 | version = "0.3.7" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" 229 | dependencies = [ 230 | "cc", 231 | "walkdir", 232 | ] 233 | 234 | [[package]] 235 | name = "miniz_oxide" 236 | version = "0.8.9" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 239 | dependencies = [ 240 | "adler2", 241 | ] 242 | 243 | [[package]] 244 | name = "mio" 245 | version = "0.8.11" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 248 | dependencies = [ 249 | "libc", 250 | "wasi", 251 | "windows-sys 0.48.0", 252 | ] 253 | 254 | [[package]] 255 | name = "num_cpus" 256 | version = "1.17.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" 259 | dependencies = [ 260 | "hermit-abi", 261 | "libc", 262 | ] 263 | 264 | [[package]] 265 | name = "object" 266 | version = "0.36.7" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 269 | dependencies = [ 270 | "memchr", 271 | ] 272 | 273 | [[package]] 274 | name = "once_cell" 275 | version = "1.19.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 278 | 279 | [[package]] 280 | name = "parking_lot" 281 | version = "0.12.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 284 | dependencies = [ 285 | "lock_api", 286 | "parking_lot_core", 287 | ] 288 | 289 | [[package]] 290 | name = "parking_lot_core" 291 | version = "0.9.7" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 294 | dependencies = [ 295 | "cfg-if", 296 | "libc", 297 | "redox_syscall", 298 | "smallvec", 299 | "windows-sys 0.45.0", 300 | ] 301 | 302 | [[package]] 303 | name = "pin-project-lite" 304 | version = "0.2.16" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 307 | 308 | [[package]] 309 | name = "pin-utils" 310 | version = "0.1.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 313 | 314 | [[package]] 315 | name = "prettyplease" 316 | version = "0.2.15" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" 319 | dependencies = [ 320 | "proc-macro2", 321 | "syn 2.0.43", 322 | ] 323 | 324 | [[package]] 325 | name = "proc-macro2" 326 | version = "1.0.71" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" 329 | dependencies = [ 330 | "unicode-ident", 331 | ] 332 | 333 | [[package]] 334 | name = "quote" 335 | version = "1.0.30" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" 338 | dependencies = [ 339 | "proc-macro2", 340 | ] 341 | 342 | [[package]] 343 | name = "redox_syscall" 344 | version = "0.2.16" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 347 | dependencies = [ 348 | "bitflags", 349 | ] 350 | 351 | [[package]] 352 | name = "rustc-demangle" 353 | version = "0.1.25" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" 356 | 357 | [[package]] 358 | name = "same-file" 359 | version = "1.0.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 362 | dependencies = [ 363 | "winapi-util", 364 | ] 365 | 366 | [[package]] 367 | name = "scc" 368 | version = "2.0.18" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "f6ffca2d370bbde3a120a882da4094bdb29b4fcb5ffbdc482c9fdf2903c16e50" 371 | 372 | [[package]] 373 | name = "scoped-tls" 374 | version = "1.0.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 377 | 378 | [[package]] 379 | name = "scopeguard" 380 | version = "1.1.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 383 | 384 | [[package]] 385 | name = "serial_test" 386 | version = "3.2.0" 387 | dependencies = [ 388 | "document-features", 389 | "env_logger", 390 | "fslock", 391 | "futures-executor", 392 | "futures-util", 393 | "itertools", 394 | "log", 395 | "once_cell", 396 | "parking_lot", 397 | "scc", 398 | "serial_test_derive", 399 | ] 400 | 401 | [[package]] 402 | name = "serial_test_derive" 403 | version = "3.2.0" 404 | dependencies = [ 405 | "env_logger", 406 | "prettyplease", 407 | "proc-macro2", 408 | "quote", 409 | "syn 2.0.43", 410 | ] 411 | 412 | [[package]] 413 | name = "serial_test_test" 414 | version = "3.2.0" 415 | dependencies = [ 416 | "actix-rt", 417 | "env_logger", 418 | "futures-util", 419 | "lock_api", 420 | "log", 421 | "once_cell", 422 | "parking_lot", 423 | "scc", 424 | "scoped-tls", 425 | "serial_test", 426 | "tokio", 427 | "wasm-bindgen-test", 428 | ] 429 | 430 | [[package]] 431 | name = "shlex" 432 | version = "1.3.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 435 | 436 | [[package]] 437 | name = "signal-hook-registry" 438 | version = "1.4.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 441 | dependencies = [ 442 | "libc", 443 | ] 444 | 445 | [[package]] 446 | name = "slab" 447 | version = "0.4.11" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 450 | 451 | [[package]] 452 | name = "smallvec" 453 | version = "1.10.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 456 | 457 | [[package]] 458 | name = "socket2" 459 | version = "0.5.10" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 462 | dependencies = [ 463 | "libc", 464 | "windows-sys 0.52.0", 465 | ] 466 | 467 | [[package]] 468 | name = "syn" 469 | version = "1.0.109" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 472 | dependencies = [ 473 | "proc-macro2", 474 | "quote", 475 | "unicode-ident", 476 | ] 477 | 478 | [[package]] 479 | name = "syn" 480 | version = "2.0.43" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" 483 | dependencies = [ 484 | "proc-macro2", 485 | "quote", 486 | "unicode-ident", 487 | ] 488 | 489 | [[package]] 490 | name = "tokio" 491 | version = "1.38.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d" 494 | dependencies = [ 495 | "backtrace", 496 | "libc", 497 | "mio", 498 | "num_cpus", 499 | "parking_lot", 500 | "pin-project-lite", 501 | "signal-hook-registry", 502 | "socket2", 503 | "tokio-macros", 504 | "windows-sys 0.48.0", 505 | ] 506 | 507 | [[package]] 508 | name = "tokio-macros" 509 | version = "2.3.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 512 | dependencies = [ 513 | "proc-macro2", 514 | "quote", 515 | "syn 2.0.43", 516 | ] 517 | 518 | [[package]] 519 | name = "unicode-ident" 520 | version = "1.0.8" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 523 | 524 | [[package]] 525 | name = "walkdir" 526 | version = "2.5.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 529 | dependencies = [ 530 | "same-file", 531 | "winapi-util", 532 | ] 533 | 534 | [[package]] 535 | name = "wasi" 536 | version = "0.11.0+wasi-snapshot-preview1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 539 | 540 | [[package]] 541 | name = "wasm-bindgen" 542 | version = "0.2.100" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 545 | dependencies = [ 546 | "cfg-if", 547 | "once_cell", 548 | "wasm-bindgen-macro", 549 | ] 550 | 551 | [[package]] 552 | name = "wasm-bindgen-backend" 553 | version = "0.2.100" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 556 | dependencies = [ 557 | "bumpalo", 558 | "log", 559 | "proc-macro2", 560 | "quote", 561 | "syn 2.0.43", 562 | "wasm-bindgen-shared", 563 | ] 564 | 565 | [[package]] 566 | name = "wasm-bindgen-futures" 567 | version = "0.4.50" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 570 | dependencies = [ 571 | "cfg-if", 572 | "js-sys", 573 | "once_cell", 574 | "wasm-bindgen", 575 | "web-sys", 576 | ] 577 | 578 | [[package]] 579 | name = "wasm-bindgen-macro" 580 | version = "0.2.100" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 583 | dependencies = [ 584 | "quote", 585 | "wasm-bindgen-macro-support", 586 | ] 587 | 588 | [[package]] 589 | name = "wasm-bindgen-macro-support" 590 | version = "0.2.100" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 593 | dependencies = [ 594 | "proc-macro2", 595 | "quote", 596 | "syn 2.0.43", 597 | "wasm-bindgen-backend", 598 | "wasm-bindgen-shared", 599 | ] 600 | 601 | [[package]] 602 | name = "wasm-bindgen-shared" 603 | version = "0.2.100" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 606 | dependencies = [ 607 | "unicode-ident", 608 | ] 609 | 610 | [[package]] 611 | name = "wasm-bindgen-test" 612 | version = "0.3.50" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" 615 | dependencies = [ 616 | "js-sys", 617 | "minicov", 618 | "wasm-bindgen", 619 | "wasm-bindgen-futures", 620 | "wasm-bindgen-test-macro", 621 | ] 622 | 623 | [[package]] 624 | name = "wasm-bindgen-test-macro" 625 | version = "0.3.50" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" 628 | dependencies = [ 629 | "proc-macro2", 630 | "quote", 631 | "syn 2.0.43", 632 | ] 633 | 634 | [[package]] 635 | name = "web-sys" 636 | version = "0.3.77" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 639 | dependencies = [ 640 | "js-sys", 641 | "wasm-bindgen", 642 | ] 643 | 644 | [[package]] 645 | name = "winapi" 646 | version = "0.3.9" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 649 | dependencies = [ 650 | "winapi-i686-pc-windows-gnu", 651 | "winapi-x86_64-pc-windows-gnu", 652 | ] 653 | 654 | [[package]] 655 | name = "winapi-i686-pc-windows-gnu" 656 | version = "0.4.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 659 | 660 | [[package]] 661 | name = "winapi-util" 662 | version = "0.1.9" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 665 | dependencies = [ 666 | "windows-sys 0.59.0", 667 | ] 668 | 669 | [[package]] 670 | name = "winapi-x86_64-pc-windows-gnu" 671 | version = "0.4.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 674 | 675 | [[package]] 676 | name = "windows-sys" 677 | version = "0.45.0" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 680 | dependencies = [ 681 | "windows-targets 0.42.2", 682 | ] 683 | 684 | [[package]] 685 | name = "windows-sys" 686 | version = "0.48.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 689 | dependencies = [ 690 | "windows-targets 0.48.5", 691 | ] 692 | 693 | [[package]] 694 | name = "windows-sys" 695 | version = "0.52.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 698 | dependencies = [ 699 | "windows-targets 0.52.6", 700 | ] 701 | 702 | [[package]] 703 | name = "windows-sys" 704 | version = "0.59.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 707 | dependencies = [ 708 | "windows-targets 0.52.6", 709 | ] 710 | 711 | [[package]] 712 | name = "windows-targets" 713 | version = "0.42.2" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 716 | dependencies = [ 717 | "windows_aarch64_gnullvm 0.42.2", 718 | "windows_aarch64_msvc 0.42.2", 719 | "windows_i686_gnu 0.42.2", 720 | "windows_i686_msvc 0.42.2", 721 | "windows_x86_64_gnu 0.42.2", 722 | "windows_x86_64_gnullvm 0.42.2", 723 | "windows_x86_64_msvc 0.42.2", 724 | ] 725 | 726 | [[package]] 727 | name = "windows-targets" 728 | version = "0.48.5" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 731 | dependencies = [ 732 | "windows_aarch64_gnullvm 0.48.5", 733 | "windows_aarch64_msvc 0.48.5", 734 | "windows_i686_gnu 0.48.5", 735 | "windows_i686_msvc 0.48.5", 736 | "windows_x86_64_gnu 0.48.5", 737 | "windows_x86_64_gnullvm 0.48.5", 738 | "windows_x86_64_msvc 0.48.5", 739 | ] 740 | 741 | [[package]] 742 | name = "windows-targets" 743 | version = "0.52.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 746 | dependencies = [ 747 | "windows_aarch64_gnullvm 0.52.6", 748 | "windows_aarch64_msvc 0.52.6", 749 | "windows_i686_gnu 0.52.6", 750 | "windows_i686_gnullvm", 751 | "windows_i686_msvc 0.52.6", 752 | "windows_x86_64_gnu 0.52.6", 753 | "windows_x86_64_gnullvm 0.52.6", 754 | "windows_x86_64_msvc 0.52.6", 755 | ] 756 | 757 | [[package]] 758 | name = "windows_aarch64_gnullvm" 759 | version = "0.42.2" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 762 | 763 | [[package]] 764 | name = "windows_aarch64_gnullvm" 765 | version = "0.48.5" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 768 | 769 | [[package]] 770 | name = "windows_aarch64_gnullvm" 771 | version = "0.52.6" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 774 | 775 | [[package]] 776 | name = "windows_aarch64_msvc" 777 | version = "0.42.2" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 780 | 781 | [[package]] 782 | name = "windows_aarch64_msvc" 783 | version = "0.48.5" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 786 | 787 | [[package]] 788 | name = "windows_aarch64_msvc" 789 | version = "0.52.6" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 792 | 793 | [[package]] 794 | name = "windows_i686_gnu" 795 | version = "0.42.2" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 798 | 799 | [[package]] 800 | name = "windows_i686_gnu" 801 | version = "0.48.5" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 804 | 805 | [[package]] 806 | name = "windows_i686_gnu" 807 | version = "0.52.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 810 | 811 | [[package]] 812 | name = "windows_i686_gnullvm" 813 | version = "0.52.6" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 816 | 817 | [[package]] 818 | name = "windows_i686_msvc" 819 | version = "0.42.2" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 822 | 823 | [[package]] 824 | name = "windows_i686_msvc" 825 | version = "0.48.5" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 828 | 829 | [[package]] 830 | name = "windows_i686_msvc" 831 | version = "0.52.6" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 834 | 835 | [[package]] 836 | name = "windows_x86_64_gnu" 837 | version = "0.42.2" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 840 | 841 | [[package]] 842 | name = "windows_x86_64_gnu" 843 | version = "0.48.5" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 846 | 847 | [[package]] 848 | name = "windows_x86_64_gnu" 849 | version = "0.52.6" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 852 | 853 | [[package]] 854 | name = "windows_x86_64_gnullvm" 855 | version = "0.42.2" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 858 | 859 | [[package]] 860 | name = "windows_x86_64_gnullvm" 861 | version = "0.48.5" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 864 | 865 | [[package]] 866 | name = "windows_x86_64_gnullvm" 867 | version = "0.52.6" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 870 | 871 | [[package]] 872 | name = "windows_x86_64_msvc" 873 | version = "0.42.2" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 876 | 877 | [[package]] 878 | name = "windows_x86_64_msvc" 879 | version = "0.48.5" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 882 | 883 | [[package]] 884 | name = "windows_x86_64_msvc" 885 | version = "0.52.6" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 888 | -------------------------------------------------------------------------------- /serial_test_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # serial_test_derive 2 | //! Helper crate for [serial_test](../serial_test/index.html) 3 | 4 | #![cfg_attr(docsrs, feature(doc_cfg))] 5 | #![deny(missing_docs)] 6 | 7 | extern crate proc_macro; 8 | 9 | use proc_macro::TokenStream; 10 | use proc_macro2::{Literal, TokenTree}; 11 | use quote::{format_ident, quote, ToTokens, TokenStreamExt}; 12 | use std::ops::Deref; 13 | use syn::Result as SynResult; 14 | 15 | /// Allows for the creation of serialised Rust tests 16 | /// ````no_run 17 | /// #[test] 18 | /// #[serial] 19 | /// fn test_serial_one() { 20 | /// // Do things 21 | /// } 22 | /// 23 | /// #[test] 24 | /// #[serial] 25 | /// fn test_serial_another() { 26 | /// // Do things 27 | /// } 28 | /// ```` 29 | /// Multiple tests with the [serial](macro@serial) attribute are guaranteed to be executed in serial. Ordering 30 | /// of the tests is not guaranteed however. If you have other tests that can be run in parallel, but would clash 31 | /// if run at the same time as the [serial](macro@serial) tests, you can use the [parallel](macro@parallel) attribute. 32 | /// 33 | /// If you want different subsets of tests to be serialised with each 34 | /// other, but not depend on other subsets, you can add a key argument to [serial](macro@serial), and all calls 35 | /// with identical arguments will be called in serial. Multiple comma-separated keys will make a test run in serial with all of the sets with any of those keys. 36 | /// 37 | /// ````no_run 38 | /// #[test] 39 | /// #[serial(something)] 40 | /// fn test_serial_one() { 41 | /// // Do things 42 | /// } 43 | /// 44 | /// #[test] 45 | /// #[serial(something)] 46 | /// fn test_serial_another() { 47 | /// // Do things 48 | /// } 49 | /// 50 | /// #[test] 51 | /// #[serial(other)] 52 | /// fn test_serial_third() { 53 | /// // Do things 54 | /// } 55 | /// 56 | /// #[test] 57 | /// #[serial(other)] 58 | /// fn test_serial_fourth() { 59 | /// // Do things 60 | /// } 61 | /// 62 | /// #[test] 63 | /// #[serial(something, other)] 64 | /// fn test_serial_fifth() { 65 | /// // Do things, eventually 66 | /// } 67 | /// ```` 68 | /// `test_serial_one` and `test_serial_another` will be executed in serial, as will `test_serial_third` and `test_serial_fourth` 69 | /// but neither sequence will be blocked by the other. `test_serial_fifth` is blocked by tests in either sequence. 70 | /// 71 | /// Nested serialised tests (i.e. a [serial](macro@serial) tagged test calling another) are supported. 72 | #[proc_macro_attribute] 73 | pub fn serial(attr: TokenStream, input: TokenStream) -> TokenStream { 74 | local_serial_core(attr.into(), input.into()).into() 75 | } 76 | 77 | /// Allows for the creation of parallel Rust tests that won't clash with serial tests 78 | /// ````no_run 79 | /// #[test] 80 | /// #[serial] 81 | /// fn test_serial_one() { 82 | /// // Do things 83 | /// } 84 | /// 85 | /// #[test] 86 | /// #[parallel] 87 | /// fn test_parallel_one() { 88 | /// // Do things 89 | /// } 90 | /// 91 | /// #[test] 92 | /// #[parallel] 93 | /// fn test_parallel_two() { 94 | /// // Do things 95 | /// } 96 | /// ```` 97 | /// Multiple tests with the [parallel](macro@parallel) attribute may run in parallel, but not at the 98 | /// same time as [serial](macro@serial) tests. e.g. in the example code above, `test_parallel_one` 99 | /// and `test_parallel_two` may run at the same time, but `test_serial_one` is guaranteed not to run 100 | /// at the same time as either of them. [parallel](macro@parallel) also takes key arguments for groups 101 | /// of tests as per [serial](macro@serial). 102 | /// 103 | /// Note that this has zero effect on [file_serial](macro@file_serial) tests, as that uses a different 104 | /// serialisation mechanism. For that, you want [file_parallel](macro@file_parallel). 105 | #[proc_macro_attribute] 106 | pub fn parallel(attr: TokenStream, input: TokenStream) -> TokenStream { 107 | local_parallel_core(attr.into(), input.into()).into() 108 | } 109 | 110 | /// Allows for the creation of file-serialised Rust tests 111 | /// ````no_run 112 | /// #[test] 113 | /// #[file_serial] 114 | /// fn test_serial_one() { 115 | /// // Do things 116 | /// } 117 | /// 118 | /// #[test] 119 | /// #[file_serial] 120 | /// fn test_serial_another() { 121 | /// // Do things 122 | /// } 123 | /// ```` 124 | /// 125 | /// Multiple tests with the [file_serial](macro@file_serial) attribute are guaranteed to run in serial, as per the [serial](macro@serial) 126 | /// attribute. Note that there are no guarantees about one test with [serial](macro@serial) and another with [file_serial](macro@file_serial) 127 | /// as they lock using different methods, and [file_serial](macro@file_serial) does not support nested serialised tests, but otherwise acts 128 | /// like [serial](macro@serial). If you have other tests that can be run in parallel, but would clash 129 | /// if run at the same time as the [file_serial](macro@file_serial) tests, you can use the [file_parallel](macro@file_parallel) attribute. 130 | /// 131 | /// It also supports an optional `path` arg as well as key(s) as per [serial](macro@serial), which is the path to the file used for 132 | /// locking purposes. This file is managed by `serial_test` and no assumptions about it's format should be made. The `path` defaults to 133 | /// a file under a reasonable temp directory for the OS if not specified. If the `path` is specified, you can only use one key, as we 134 | /// can't generate per-key paths if you've done that. 135 | /// ````no_run 136 | /// #[test] 137 | /// #[file_serial(key)] 138 | /// fn test_serial_one() { 139 | /// // Do things 140 | /// } 141 | /// 142 | /// #[test] 143 | /// #[file_serial(key, path => "/tmp/foo")] 144 | /// fn test_serial_another() { 145 | /// // Do things 146 | /// } 147 | /// ```` 148 | #[proc_macro_attribute] 149 | #[cfg_attr(docsrs, doc(cfg(feature = "file_locks")))] 150 | pub fn file_serial(attr: TokenStream, input: TokenStream) -> TokenStream { 151 | fs_serial_core(attr.into(), input.into()).into() 152 | } 153 | 154 | /// Allows for the creation of file-serialised parallel Rust tests that won't clash with file-serialised serial tests 155 | /// ````no_run 156 | /// #[test] 157 | /// #[file_serial] 158 | /// fn test_serial_one() { 159 | /// // Do things 160 | /// } 161 | /// 162 | /// #[test] 163 | /// #[file_parallel] 164 | /// fn test_parallel_one() { 165 | /// // Do things 166 | /// } 167 | /// 168 | /// #[test] 169 | /// #[file_parallel] 170 | /// fn test_parallel_two() { 171 | /// // Do things 172 | /// } 173 | /// ```` 174 | /// Effectively, this should behave like [parallel](macro@parallel) but for [file_serial](macro@file_serial). 175 | /// Note that as per [file_serial](macro@file_serial) this doesn't do anything for [serial](macro@serial)/[parallel](macro@parallel) tests. 176 | /// 177 | /// It also supports an optional `path` arg as well as key(s) as per [serial](macro@serial), which is the path to the file used for 178 | /// locking purposes. This file is managed by `serial_test` and no assumptions about it's format should be made. The `path` defaults to 179 | /// a file under a reasonable temp directory for the OS if not specified. If the `path` is specified, you can only use one key, as we 180 | /// can't generate per-key paths if you've done that. 181 | /// ````no_run 182 | /// #[test] 183 | /// #[file_parallel(key, path => "/tmp/foo")] 184 | /// fn test_parallel_one() { 185 | /// // Do things 186 | /// } 187 | /// 188 | /// #[test] 189 | /// #[file_parallel(key, path => "/tmp/foo")] 190 | /// fn test_parallel_another() { 191 | /// // Do things 192 | /// } 193 | /// ```` 194 | #[proc_macro_attribute] 195 | #[cfg_attr(docsrs, doc(cfg(feature = "file_locks")))] 196 | pub fn file_parallel(attr: TokenStream, input: TokenStream) -> TokenStream { 197 | fs_parallel_core(attr.into(), input.into()).into() 198 | } 199 | 200 | // Based off of https://github.com/dtolnay/quote/issues/20#issuecomment-437341743 201 | #[derive(Default, Debug, Clone)] 202 | struct QuoteOption(Option); 203 | 204 | impl ToTokens for QuoteOption { 205 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 206 | tokens.append_all(match self.0 { 207 | Some(ref t) => quote! { ::std::option::Option::Some(#t) }, 208 | None => quote! { ::std::option::Option::None }, 209 | }); 210 | } 211 | } 212 | 213 | #[derive(Default, Debug)] 214 | struct Config { 215 | names: Vec, 216 | path: QuoteOption, 217 | crate_ident: Vec, 218 | } 219 | 220 | fn string_from_literal(literal: Literal) -> String { 221 | let string_literal = literal.to_string(); 222 | if !string_literal.starts_with('\"') || !string_literal.ends_with('\"') { 223 | panic!("Expected a string literal, got '{}'", string_literal); 224 | } 225 | // Hacky way of getting a string without the enclosing quotes 226 | string_literal[1..string_literal.len() - 1].to_string() 227 | } 228 | 229 | fn get_config(attr: proc_macro2::TokenStream) -> Config { 230 | let mut attrs = attr.into_iter().collect::>(); 231 | let mut raw_args: Vec = Vec::new(); 232 | let mut in_path: bool = false; 233 | let mut path: Option = None; 234 | let mut in_crate: bool = false; 235 | let mut crate_ident: Option> = None; 236 | while !attrs.is_empty() { 237 | match attrs.remove(0) { 238 | TokenTree::Ident(id) if id.to_string().eq_ignore_ascii_case("path") => { 239 | in_path = true; 240 | } 241 | TokenTree::Ident(id) if id.to_string().eq_ignore_ascii_case("crate") => { 242 | in_crate = true; 243 | } 244 | TokenTree::Ident(id) => { 245 | let name = id.to_string(); 246 | raw_args.push(name); 247 | } 248 | x => { 249 | panic!( 250 | "Expected literal as key args (or a 'path => '\"foo\"'), not {}", 251 | x 252 | ); 253 | } 254 | } 255 | if in_path { 256 | if attrs.len() < 3 { 257 | panic!("Expected a '=> ' after 'path'"); 258 | } 259 | match attrs.remove(0) { 260 | TokenTree::Punct(p) if p.as_char() == '=' => {} 261 | x => { 262 | panic!("Expected = after path, not {}", x); 263 | } 264 | } 265 | match attrs.remove(0) { 266 | TokenTree::Punct(p) if p.as_char() == '>' => {} 267 | x => { 268 | panic!("Expected > after path, not {}", x); 269 | } 270 | } 271 | match attrs.remove(0) { 272 | TokenTree::Literal(literal) => { 273 | path = Some(string_from_literal(literal)); 274 | } 275 | x => { 276 | panic!("Expected literals as path arg, not {}", x); 277 | } 278 | } 279 | in_path = false; 280 | } 281 | if in_crate { 282 | if attrs.len() < 2 { 283 | panic!("Expected a '= ' after 'crate'"); 284 | } 285 | match attrs.remove(0) { 286 | TokenTree::Punct(p) if p.as_char() == '=' => {} 287 | x => { 288 | panic!("Expected = after crate, not {}", x); 289 | } 290 | } 291 | let ident_items: Vec<_> = attrs 292 | .iter() 293 | .map_while(|t| { 294 | match t { 295 | TokenTree::Ident(_) => {} 296 | TokenTree::Punct(p) if p.as_char() != ',' => {} 297 | _ => { 298 | return None; 299 | } 300 | }; 301 | Some(t.clone()) 302 | }) 303 | .collect(); 304 | for _ in 0..ident_items.len() { 305 | attrs.remove(0); 306 | } 307 | crate_ident = Some(ident_items); 308 | in_crate = false; 309 | } 310 | if !attrs.is_empty() { 311 | match attrs.remove(0) { 312 | TokenTree::Punct(p) if p.as_char() == ',' => {} 313 | x => { 314 | panic!("Expected , between args, not {}", x); 315 | } 316 | } 317 | } 318 | } 319 | if raw_args.is_empty() { 320 | raw_args.push(String::new()); 321 | } 322 | raw_args.sort(); // So the keys are always requested in the same order. Avoids dining philosopher issues. 323 | Config { 324 | names: raw_args, 325 | path: QuoteOption(path), 326 | crate_ident: crate_ident.unwrap_or(vec![TokenTree::Ident(format_ident!("serial_test"))]), 327 | } 328 | } 329 | 330 | fn local_serial_core( 331 | attr: proc_macro2::TokenStream, 332 | input: proc_macro2::TokenStream, 333 | ) -> proc_macro2::TokenStream { 334 | let config = get_config(attr); 335 | serial_setup(input, config, "local") 336 | } 337 | 338 | fn local_parallel_core( 339 | attr: proc_macro2::TokenStream, 340 | input: proc_macro2::TokenStream, 341 | ) -> proc_macro2::TokenStream { 342 | let config = get_config(attr); 343 | parallel_setup(input, config, "local") 344 | } 345 | 346 | fn fs_serial_core( 347 | attr: proc_macro2::TokenStream, 348 | input: proc_macro2::TokenStream, 349 | ) -> proc_macro2::TokenStream { 350 | let config = get_config(attr); 351 | serial_setup(input, config, "fs") 352 | } 353 | 354 | fn fs_parallel_core( 355 | attr: proc_macro2::TokenStream, 356 | input: proc_macro2::TokenStream, 357 | ) -> proc_macro2::TokenStream { 358 | let config = get_config(attr); 359 | parallel_setup(input, config, "fs") 360 | } 361 | 362 | #[allow(clippy::cmp_owned)] 363 | fn core_setup( 364 | input: proc_macro2::TokenStream, 365 | config: &Config, 366 | prefix: &str, 367 | kind: &str, 368 | ) -> proc_macro2::TokenStream { 369 | let fn_ast: SynResult = syn::parse2(input.clone()); 370 | if let Ok(ast) = fn_ast { 371 | return fn_setup(ast, config, prefix, kind); 372 | }; 373 | let mod_ast: SynResult = syn::parse2(input); 374 | match mod_ast { 375 | Ok(mut ast) => { 376 | let new_content = ast.content.clone().map(|(brace, items)| { 377 | let new_items = items 378 | .into_iter() 379 | .map(|item| match item { 380 | syn::Item::Fn(item_fn) 381 | if item_fn.attrs.iter().any(|attr| { 382 | attr.meta 383 | .path() 384 | .segments 385 | .iter() 386 | .map(|s| s.ident.to_string()) 387 | .collect::>() 388 | .join("::") 389 | .contains("test") 390 | }) => 391 | { 392 | let tokens = fn_setup(item_fn, config, prefix, kind); 393 | let token_display = format!("tokens: {tokens}"); 394 | syn::parse2(tokens).expect(&token_display) 395 | } 396 | other => other, 397 | }) 398 | .collect(); 399 | (brace, new_items) 400 | }); 401 | if let Some(nc) = new_content { 402 | ast.content.replace(nc); 403 | } 404 | ast.attrs.retain(|attr| { 405 | attr.meta.path().segments.first().unwrap().ident.to_string() != "serial" 406 | }); 407 | ast.into_token_stream() 408 | } 409 | Err(_) => { 410 | panic!("Attribute applied to something other than mod or fn!"); 411 | } 412 | } 413 | } 414 | 415 | fn fn_setup( 416 | ast: syn::ItemFn, 417 | config: &Config, 418 | prefix: &str, 419 | kind: &str, 420 | ) -> proc_macro2::TokenStream { 421 | let asyncness = ast.sig.asyncness; 422 | if asyncness.is_some() && cfg!(not(feature = "async")) { 423 | panic!("async testing attempted with async feature disabled in serial_test!"); 424 | } 425 | let vis = ast.vis; 426 | let name = ast.sig.ident; 427 | #[cfg(all(feature = "test_logging", not(test)))] 428 | let print_name = { 429 | let print_str = format!("Starting {name}"); 430 | quote! { 431 | println!(#print_str); 432 | } 433 | }; 434 | #[cfg(any(not(feature = "test_logging"), test))] 435 | let print_name = quote! {}; 436 | let return_type = match ast.sig.output { 437 | syn::ReturnType::Default => None, 438 | syn::ReturnType::Type(_rarrow, ref box_type) => Some(box_type.deref()), 439 | }; 440 | let block = ast.block; 441 | let attrs: Vec = ast.attrs.into_iter().collect(); 442 | let names = config.names.clone(); 443 | let path = config.path.clone(); 444 | let crate_ident = config.crate_ident.clone(); 445 | if let Some(ret) = return_type { 446 | match asyncness { 447 | Some(_) => { 448 | let fnname = format_ident!("{}_async_{}_core_with_return", prefix, kind); 449 | let temp_fn = format_ident!("_{}_internal", name); 450 | quote! { 451 | #(#attrs) 452 | * 453 | #vis async fn #name () -> #ret { 454 | async fn #temp_fn () -> #ret 455 | #block 456 | 457 | #print_name 458 | #(#crate_ident)*::#fnname(vec![#(#names ),*], #path, #temp_fn()).await 459 | } 460 | } 461 | } 462 | None => { 463 | let fnname = format_ident!("{}_{}_core_with_return", prefix, kind); 464 | quote! { 465 | #(#attrs) 466 | * 467 | #vis fn #name () -> #ret { 468 | #print_name 469 | #(#crate_ident)*::#fnname(vec![#(#names ),*], #path, || #block ) 470 | } 471 | } 472 | } 473 | } 474 | } else { 475 | match asyncness { 476 | Some(_) => { 477 | let fnname = format_ident!("{}_async_{}_core", prefix, kind); 478 | let temp_fn = format_ident!("_{}_internal", name); 479 | quote! { 480 | #(#attrs) 481 | * 482 | #vis async fn #name () { 483 | async fn #temp_fn () 484 | #block 485 | 486 | #print_name 487 | #(#crate_ident)*::#fnname(vec![#(#names ),*], #path, #temp_fn()).await; 488 | } 489 | } 490 | } 491 | None => { 492 | let fnname = format_ident!("{}_{}_core", prefix, kind); 493 | quote! { 494 | #(#attrs) 495 | * 496 | #vis fn #name () { 497 | #print_name 498 | #(#crate_ident)*::#fnname(vec![#(#names ),*], #path, || #block ); 499 | } 500 | } 501 | } 502 | } 503 | } 504 | } 505 | 506 | fn serial_setup( 507 | input: proc_macro2::TokenStream, 508 | config: Config, 509 | prefix: &str, 510 | ) -> proc_macro2::TokenStream { 511 | core_setup(input, &config, prefix, "serial") 512 | } 513 | 514 | fn parallel_setup( 515 | input: proc_macro2::TokenStream, 516 | config: Config, 517 | prefix: &str, 518 | ) -> proc_macro2::TokenStream { 519 | core_setup(input, &config, prefix, "parallel") 520 | } 521 | 522 | #[cfg(test)] 523 | mod tests { 524 | use super::{fs_serial_core, local_serial_core}; 525 | use proc_macro2::TokenStream; 526 | use quote::quote; 527 | use std::iter::FromIterator; 528 | 529 | fn init() { 530 | let _ = env_logger::builder().is_test(false).try_init(); 531 | } 532 | 533 | fn unparse(input: TokenStream) -> String { 534 | let item = syn::parse2(input).unwrap(); 535 | let file = syn::File { 536 | attrs: vec![], 537 | items: vec![item], 538 | shebang: None, 539 | }; 540 | 541 | prettyplease::unparse(&file) 542 | } 543 | 544 | fn compare_streams(first: TokenStream, second: TokenStream) { 545 | let f = unparse(first); 546 | assert_eq!(f, unparse(second)); 547 | } 548 | 549 | #[test] 550 | fn test_serial() { 551 | init(); 552 | let attrs = proc_macro2::TokenStream::new(); 553 | let input = quote! { 554 | #[test] 555 | fn foo() {} 556 | }; 557 | let stream = local_serial_core(attrs.into(), input); 558 | let compare = quote! { 559 | #[test] 560 | fn foo () { 561 | serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); 562 | } 563 | }; 564 | compare_streams(compare, stream); 565 | } 566 | 567 | #[test] 568 | fn test_serial_with_pub() { 569 | init(); 570 | let attrs = proc_macro2::TokenStream::new(); 571 | let input = quote! { 572 | #[test] 573 | pub fn foo() {} 574 | }; 575 | let stream = local_serial_core(attrs.into(), input); 576 | let compare = quote! { 577 | #[test] 578 | pub fn foo () { 579 | serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); 580 | } 581 | }; 582 | compare_streams(compare, stream); 583 | } 584 | 585 | #[test] 586 | fn test_other_attributes() { 587 | init(); 588 | let attrs = proc_macro2::TokenStream::new(); 589 | let input = quote! { 590 | #[test] 591 | #[ignore] 592 | #[should_panic(expected = "Testing panic")] 593 | #[something_else] 594 | fn foo() {} 595 | }; 596 | let stream = local_serial_core(attrs.into(), input); 597 | let compare = quote! { 598 | #[test] 599 | #[ignore] 600 | #[should_panic(expected = "Testing panic")] 601 | #[something_else] 602 | fn foo () { 603 | serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); 604 | } 605 | }; 606 | compare_streams(compare, stream); 607 | } 608 | 609 | #[test] 610 | #[cfg(feature = "async")] 611 | fn test_serial_async() { 612 | init(); 613 | let attrs = proc_macro2::TokenStream::new(); 614 | let input = quote! { 615 | async fn foo() {} 616 | }; 617 | let stream = local_serial_core(attrs.into(), input); 618 | let compare = quote! { 619 | async fn foo () { 620 | async fn _foo_internal () { } 621 | serial_test::local_async_serial_core(vec![""], ::std::option::Option::None, _foo_internal() ).await; 622 | } 623 | }; 624 | assert_eq!(format!("{}", compare), format!("{}", stream)); 625 | } 626 | 627 | #[test] 628 | #[cfg(feature = "async")] 629 | fn test_serial_async_return() { 630 | init(); 631 | let attrs = proc_macro2::TokenStream::new(); 632 | let input = quote! { 633 | async fn foo() -> Result<(), ()> { Ok(()) } 634 | }; 635 | let stream = local_serial_core(attrs.into(), input); 636 | let compare = quote! { 637 | async fn foo () -> Result<(), ()> { 638 | async fn _foo_internal () -> Result<(), ()> { Ok(()) } 639 | serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _foo_internal() ).await 640 | } 641 | }; 642 | assert_eq!(format!("{}", compare), format!("{}", stream)); 643 | } 644 | 645 | #[test] 646 | fn test_file_serial() { 647 | init(); 648 | let attrs: Vec<_> = quote! { foo }.into_iter().collect(); 649 | let input = quote! { 650 | #[test] 651 | fn foo() {} 652 | }; 653 | let stream = fs_serial_core( 654 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 655 | input, 656 | ); 657 | let compare = quote! { 658 | #[test] 659 | fn foo () { 660 | serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::None, || {} ); 661 | } 662 | }; 663 | compare_streams(compare, stream); 664 | } 665 | 666 | #[test] 667 | fn test_file_serial_no_args() { 668 | init(); 669 | let attrs = proc_macro2::TokenStream::new(); 670 | let input = quote! { 671 | #[test] 672 | fn foo() {} 673 | }; 674 | let stream = fs_serial_core( 675 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 676 | input, 677 | ); 678 | let compare = quote! { 679 | #[test] 680 | fn foo () { 681 | serial_test::fs_serial_core(vec![""], ::std::option::Option::None, || {} ); 682 | } 683 | }; 684 | compare_streams(compare, stream); 685 | } 686 | 687 | #[test] 688 | fn test_file_serial_with_path() { 689 | init(); 690 | let attrs: Vec<_> = quote! { foo, path => "bar_path" }.into_iter().collect(); 691 | let input = quote! { 692 | #[test] 693 | fn foo() {} 694 | }; 695 | let stream = fs_serial_core( 696 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 697 | input, 698 | ); 699 | let compare = quote! { 700 | #[test] 701 | fn foo () { 702 | serial_test::fs_serial_core(vec!["foo"], ::std::option::Option::Some("bar_path"), || {} ); 703 | } 704 | }; 705 | compare_streams(compare, stream); 706 | } 707 | 708 | #[test] 709 | fn test_single_attr() { 710 | init(); 711 | let attrs: Vec<_> = quote! { one}.into_iter().collect(); 712 | let input = quote! { 713 | #[test] 714 | fn single() {} 715 | }; 716 | let stream = local_serial_core( 717 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 718 | input, 719 | ); 720 | let compare = quote! { 721 | #[test] 722 | fn single () { 723 | serial_test::local_serial_core(vec!["one"], ::std::option::Option::None, || {} ); 724 | } 725 | }; 726 | compare_streams(compare, stream); 727 | } 728 | 729 | #[test] 730 | fn test_multiple_attr() { 731 | init(); 732 | let attrs: Vec<_> = quote! { two, one }.into_iter().collect(); 733 | let input = quote! { 734 | #[test] 735 | fn multiple() {} 736 | }; 737 | let stream = local_serial_core( 738 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 739 | input, 740 | ); 741 | let compare = quote! { 742 | #[test] 743 | fn multiple () { 744 | serial_test::local_serial_core(vec!["one", "two"], ::std::option::Option::None, || {} ); 745 | } 746 | }; 747 | compare_streams(compare, stream); 748 | } 749 | 750 | #[test] 751 | fn test_mod() { 752 | init(); 753 | let attrs = proc_macro2::TokenStream::new(); 754 | let input = quote! { 755 | #[cfg(test)] 756 | #[serial] 757 | mod serial_attr_tests { 758 | pub fn foo() { 759 | println!("Nothing"); 760 | } 761 | 762 | #[test] 763 | fn bar() {} 764 | } 765 | }; 766 | let stream = local_serial_core( 767 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 768 | input, 769 | ); 770 | let compare = quote! { 771 | #[cfg(test)] 772 | mod serial_attr_tests { 773 | pub fn foo() { 774 | println!("Nothing"); 775 | } 776 | 777 | #[test] 778 | fn bar() { 779 | serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); 780 | } 781 | } 782 | }; 783 | compare_streams(compare, stream); 784 | } 785 | 786 | #[test] 787 | fn test_later_test_mod() { 788 | init(); 789 | let attrs = proc_macro2::TokenStream::new(); 790 | let input = quote! { 791 | #[cfg(test)] 792 | #[serial] 793 | mod serial_attr_tests { 794 | pub fn foo() { 795 | println!("Nothing"); 796 | } 797 | 798 | #[demo_library::test] 799 | fn bar() {} 800 | } 801 | }; 802 | let stream = local_serial_core( 803 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 804 | input, 805 | ); 806 | let compare = quote! { 807 | #[cfg(test)] 808 | mod serial_attr_tests { 809 | pub fn foo() { 810 | println!("Nothing"); 811 | } 812 | 813 | #[demo_library::test] 814 | fn bar() { 815 | serial_test::local_serial_core(vec![""], ::std::option::Option::None, || {} ); 816 | } 817 | } 818 | }; 819 | compare_streams(compare, stream); 820 | } 821 | 822 | #[test] 823 | #[cfg(feature = "async")] 824 | fn test_mod_with_async() { 825 | init(); 826 | let attrs = proc_macro2::TokenStream::new(); 827 | let input = quote! { 828 | #[cfg(test)] 829 | #[serial] 830 | mod serial_attr_tests { 831 | #[demo_library::test] 832 | async fn foo() -> Result<(), ()> { 833 | Ok(()) 834 | } 835 | 836 | #[demo_library::test] 837 | #[ignore = "bla"] 838 | async fn bar() -> Result<(), ()> { 839 | Ok(()) 840 | } 841 | } 842 | }; 843 | let stream = local_serial_core( 844 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 845 | input, 846 | ); 847 | let compare = quote! { 848 | #[cfg(test)] 849 | mod serial_attr_tests { 850 | #[demo_library::test] 851 | async fn foo() -> Result<(), ()> { 852 | async fn _foo_internal() -> Result<(), ()> { Ok(())} 853 | serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _foo_internal() ).await 854 | } 855 | 856 | #[demo_library::test] 857 | #[ignore = "bla"] 858 | async fn bar() -> Result<(), ()> { 859 | async fn _bar_internal() -> Result<(), ()> { Ok(())} 860 | serial_test::local_async_serial_core_with_return(vec![""], ::std::option::Option::None, _bar_internal() ).await 861 | } 862 | } 863 | }; 864 | compare_streams(compare, stream); 865 | } 866 | 867 | #[test] 868 | fn test_nested_return() { 869 | init(); 870 | let attrs = proc_macro2::TokenStream::new(); 871 | let input = quote! { 872 | #[test] 873 | fn test() -> Result, ()> { 874 | Ok(Ok(())) 875 | } 876 | }; 877 | let stream = local_serial_core( 878 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 879 | input, 880 | ); 881 | let compare = quote! { 882 | #[test] 883 | fn test() -> Result, ()> { 884 | serial_test::local_serial_core_with_return(vec![""], ::std::option::Option::None, || {Ok(Ok(()))} ) 885 | } 886 | }; 887 | compare_streams(compare, stream); 888 | } 889 | 890 | #[test] 891 | fn test_crate_wrapper() { 892 | init(); 893 | let attrs: Vec<_> = quote! { crate = wrapper::__derive_refs::serial } 894 | .into_iter() 895 | .collect(); 896 | let input = quote! { 897 | #[test] 898 | fn foo() {} 899 | }; 900 | let stream = fs_serial_core( 901 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 902 | input, 903 | ); 904 | let compare = quote! { 905 | #[test] 906 | fn foo () { 907 | wrapper::__derive_refs::serial::fs_serial_core(vec![""], ::std::option::Option::None, || {} ); 908 | } 909 | }; 910 | compare_streams(compare, stream); 911 | } 912 | 913 | #[test] 914 | fn test_crate_wrapper_with_path() { 915 | init(); 916 | let attrs: Vec<_> = quote! {crate = wrapper::__derive_refs::serial, path => "/tmp/bar" } 917 | .into_iter() 918 | .collect(); 919 | let input = quote! { 920 | #[test] 921 | fn foo() {} 922 | }; 923 | let stream = fs_serial_core( 924 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 925 | input, 926 | ); 927 | let compare = quote! { 928 | #[test] 929 | fn foo () { 930 | wrapper::__derive_refs::serial::fs_serial_core(vec![""], ::std::option::Option::Some("/tmp/bar"), || {} ); 931 | } 932 | }; 933 | compare_streams(compare, stream); 934 | } 935 | 936 | #[test] 937 | fn test_crate_wrapper_with_path_and_key() { 938 | init(); 939 | let attrs: Vec<_> = 940 | quote! { key1, key2, path => "/tmp/bar", crate = wrapper::__derive_refs::serial } 941 | .into_iter() 942 | .collect(); 943 | let input = quote! { 944 | #[test] 945 | fn foo() {} 946 | }; 947 | let stream = fs_serial_core( 948 | proc_macro2::TokenStream::from_iter(attrs.into_iter()), 949 | input, 950 | ); 951 | let compare = quote! { 952 | #[test] 953 | fn foo () { 954 | wrapper::__derive_refs::serial::fs_serial_core(vec!["key1", "key2"], ::std::option::Option::Some("/tmp/bar"), || {} ); 955 | } 956 | }; 957 | compare_streams(compare, stream); 958 | } 959 | } 960 | --------------------------------------------------------------------------------