Skip to content

Commit

Permalink
Add #test_with::lock
Browse files Browse the repository at this point in the history
  • Loading branch information
yanganto committed Jul 11, 2024
1 parent 0b87e9f commit e5d982c
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 284 deletions.
473 changes: 240 additions & 233 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "test-with"
version = "0.12.6"
version = "0.13.0"
authors = ["Antonio Yang <[email protected]>"]
edition = "2021"
license = "MIT"
description = "A lib help you run test with condition"
repository = "https://github.com/yanganto/test-with"
keywords = [ "testing", "condition", "toggle", "integration", "ignore" ]
categories = [ "development-tools", "testing" ]
rust-version = "1.77.0" # Due to std::fs::create_new

[lib]
proc-macro = true
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,38 @@ mod test_with_mock {

Please check out examples uder the [example/runner](https://github.com/yanganto/test-with/tree/main/examples/runner) project.

## Lock
`#[test_with::lock(LOCK_NAME)]` is way to make sure your test casess can run one by one by using file locks.
The first parameter is the name of the file lock, the second parameter to specific the waiting seconds,
default will be 60 seconds.

```rust
// `LOCK` is file based lock to prevent test1 an test2 run at the same time
#[test_with::lock(LOCK)]
fn test_1() {
assert!(true);
}

// `LOCK` is file based lock to prevent test1 an test2 run at the same time
#[test_with::lock(LOCK)]
fn test_2() {
assert!(true);
}

// `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
// waiting time.
#[test_with::lock(ANOTHER_LOCK, 3)]
fn test_3() {
assert!(true);
}

// `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
// waiting time.
#[test_with::lock(ANOTHER_LOCK, 3)]
fn test_4() {
assert!(true);
}
```

## Relating issues
* [Solve this in runtime][original-issue]
Expand Down
30 changes: 30 additions & 0 deletions examples/lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
fn main() {}

#[cfg(test)]
mod tests {
// `LOCK` is file based lock to prevent test1 an test2 run at the same time
#[test_with::lock(LOCK)]
fn test_1() {
assert!(true);
}

// `LOCK` is file based lock to prevent test1 an test2 run at the same time
#[test_with::lock(LOCK)]
fn test_2() {
assert!(true);
}

// `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
// waiting time.
#[test_with::lock(ANOTHER_LOCK, 3)]
fn test_3() {
assert!(true);
}

// `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
// waiting time.
#[test_with::lock(ANOTHER_LOCK, 3)]
fn test_4() {
assert!(true);
}
}
58 changes: 12 additions & 46 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
pkgs = import nixpkgs {
inherit system overlays;
};
rust = pkgs.rust-bin.stable."1.74.1".default;
rust = pkgs.rust-bin.stable."1.77.0".default;
dr = dependency-refresh.defaultPackage.${system};

publishScript = pkgs.writeShellScriptBin "crate-publish" ''
Expand Down
50 changes: 48 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ use std::net::IpAddr;
use std::net::TcpStream;

use proc_macro::TokenStream;
#[cfg(any(feature = "resource", feature = "icmp"))]
use proc_macro_error::abort_call_site;
use proc_macro_error::proc_macro_error;
use syn::{parse_macro_input, ItemFn, ItemMod};
Expand All @@ -68,7 +67,7 @@ use syn::{Item, ItemStruct, ItemType};
#[cfg(feature = "executable")]
use which::which;

use crate::utils::{fn_macro, is_module, mod_macro, sanitize_env_vars_attr};
use crate::utils::{fn_macro, is_module, lock_macro, mod_macro, sanitize_env_vars_attr};

mod utils;

Expand Down Expand Up @@ -2821,3 +2820,50 @@ mod tests {
}
}
}

/// Run test case one by one when the lock is acquired
/// It will automatically implement a file lock for the test case to prevent it run in the same
/// time. Also, you can pass the second parameter to specific the waiting seconds, default will be
/// 60 seconds.
/// ```
/// #[cfg(test)]
/// mod tests {
///
/// // `LOCK` is file based lock to prevent test1 an test2 run at the same time
/// #[test_with::lock(LOCK)]
/// #[test]
/// fn test_1() {
/// assert!(true);
/// }
///
/// // `LOCK` is file based lock to prevent test1 an test2 run at the same time
/// #[test_with::lock(LOCK)]
/// #[test]
/// fn test_2() {
/// assert!(true);
/// }
///
/// // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
/// // waiting time.
/// #[test_with::lock(ANOTHER_LOCK, 3)]
/// fn test_3() {
/// assert!(true);
/// }
///
/// // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
/// // waiting time.
/// #[test_with::lock(ANOTHER_LOCK, 3)]
/// fn test_4() {
/// assert!(true);
/// }
///
/// }
#[proc_macro_attribute]
#[proc_macro_error]
pub fn lock(attr: TokenStream, stream: TokenStream) -> TokenStream {
if is_module(&stream) {
abort_call_site!("#[test_with::lock] only works with fn")
} else {
lock_macro(attr, parse_macro_input!(stream as ItemFn))
}
}
77 changes: 76 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use quote::quote;
#[cfg(feature = "ign-msg")]
use syn::Signature;
use syn::{Attribute, Meta};
use syn::{Ident, Item, ItemFn, ItemMod};
use syn::{Block, Ident, Item, ItemFn, ItemMod};

// check for `#[test]`, `#[tokio::test]`, `#[async_std::test]`
pub(crate) fn has_test_attr(attrs: &[Attribute]) -> bool {
Expand Down Expand Up @@ -258,6 +258,81 @@ pub(crate) fn mod_macro(
}
}

pub(crate) fn lock_macro(attr: TokenStream, input: ItemFn) -> TokenStream {
let ItemFn {
attrs,
vis,
sig,
block,
} = input;
let Block { stmts, .. } = *block;
let attr_str = attr.to_string().replace(' ', "");
let lock_attrs: Vec<&str> = attr_str.split(',').collect();
let (lock_name, wait_time) = match (lock_attrs.first(), lock_attrs.get(1)) {
(Some(name), None) => (name, 60),
(Some(name), Some(sec)) => {
if let Ok(wait_time) = sec.parse::<usize>() {
(name, wait_time)
} else {
abort_call_site!("`The second parameter of #[test_with::lock]` should be a number for waiting time");
}
}
(None, _) => {
abort_call_site!("`#[test_with::lock]` need a name for the file lock");
}
};
let lock_file = std::env::temp_dir().join(lock_name).display().to_string();

check_before_attrs(&attrs);

if has_test_attr(&attrs) {
quote! {
#(#attrs)*
#vis #sig {
let mut _test_with_lock = None;
for i in 0..#wait_time {
if let Ok(file) = std::fs::File::create_new(#lock_file) {
_test_with_lock = Some(file);
break;
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
if _test_with_lock.is_none() {
panic!("Fail to acquire the lock for the testcase")
}
#(#stmts)*
if std::fs::remove_file(#lock_file).is_err() {
panic!("Fail to unlock the testcase")
}
}
}
.into()
} else {
quote! {
#(#attrs)*
#[test]
#vis #sig {
let mut _test_with_lock = None;
for i in 0..#wait_time {
if let Ok(file) = std::fs::File::create_new(#lock_file) {
_test_with_lock = Some(file);
break;
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
if _test_with_lock.is_none() {
panic!("Fail to acquire the lock for the testcase")
}
#(#stmts)*
if std::fs::remove_file(#lock_file).is_err() {
panic!("Fail to unlock the testcase")
}
}
}
.into()
}
}

/// Sanitize the attribute string to remove any leading or trailing whitespace
/// and split the string into an iterator of individual environment variable names.
pub fn sanitize_env_vars_attr(attr_str: &str) -> impl Iterator<Item = &str> {
Expand Down

0 comments on commit e5d982c

Please sign in to comment.