Skip to content

Commit

Permalink
Implemented Opt-in crate-name support Fix #258
Browse files Browse the repository at this point in the history
  • Loading branch information
la10736 committed Jun 1, 2024
1 parent 236be92 commit fef4f7b
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 21 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Changed

- Add feature `crate-name` (enable by default to opt-in crate rename
support. See [#258](https://github.com/la10736/rstest/issues/258)

### Add

### Fixed
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

# Fixture-based test framework for Rust



## Introduction

`rstest` uses procedural macros to help you on writing
Expand All @@ -19,6 +17,11 @@ following lines to your `Cargo.toml` file:
rstest = "0.20.0"
```

### Features

- `async-timeout`: `timeout` for `async` tests (Default enabled)
- `crate-name`: Import `rstest` package with different name (Default enabled)

### Fixture

The core idea is that you can inject your test dependencies
Expand Down
3 changes: 2 additions & 1 deletion rstest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ async-timeout = [
"dep:futures-timer",
"rstest_macros/async-timeout",
]
default = ["async-timeout"]
crate-name = ["rstest_macros/crate-name"]
default = ["async-timeout", "crate-name"]

[lib]

Expand Down
4 changes: 4 additions & 0 deletions rstest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
//! assert!(string_processor.output.contains("Alice"));
//! }
//! ```
//! ### Features
//!
//! - `async-timeout`: `timeout` for `async` tests (Default enabled)
//! - `crate-name`: Import `rstest` package with different name (Default enabled)
//!
//! ## Injecting fixtures as function arguments
//!
Expand Down
75 changes: 75 additions & 0 deletions rstest/tests/resources/rstest/convert_string_literal_other_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use other_name::*;
use std::net::SocketAddr;

#[rstest]
#[case(true, "1.2.3.4:42")]
#[case(true, r#"4.3.2.1:24"#)]
#[case(false, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443")]
#[case(false, r#"[2aa1:db8:85a3:8af:1319:8a2e:375:4873]:344"#)]
#[case(false, "this.is.not.a.socket.address")]
#[case(false, r#"this.is.not.a.socket.address"#)]
fn cases(#[case] expected: bool, #[case] addr: SocketAddr) {
assert_eq!(expected, addr.is_ipv4());
}

#[rstest]
fn values(
#[values(
"1.2.3.4:42",
r#"4.3.2.1:24"#,
"this.is.not.a.socket.address",
r#"this.is.not.a.socket.address"#
)]
addr: SocketAddr,
) {
assert!(addr.is_ipv4())
}

#[rstest]
#[case(b"12345")]
fn not_convert_byte_array(#[case] cases: &[u8], #[values(b"abc")] values: &[u8]) {
assert_eq!(5, cases.len());
assert_eq!(3, values.len());
}

trait MyTrait {
fn my_trait(&self) -> u32 {
42
}
}

impl MyTrait for &str {}

#[rstest]
#[case("impl", "nothing")]
fn not_convert_impl(#[case] that_impl: impl MyTrait, #[case] s: &str) {
assert_eq!(42, that_impl.my_trait());
assert_eq!(42, s.my_trait());
}

#[rstest]
#[case("1.2.3.4", "1.2.3.4:42")]
#[case("1.2.3.4".to_owned(), "1.2.3.4:42")]
fn not_convert_generics<S: AsRef<str>>(#[case] ip: S, #[case] addr: SocketAddr) {
assert_eq!(addr.ip().to_string(), ip.as_ref());
}

struct MyType(String);
struct E;
impl core::str::FromStr for MyType {
type Err = E;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"error" => Err(E),
inner => Ok(MyType(inner.to_owned())),
}
}
}

#[rstest]
#[case("hello", "hello")]
#[case("doesn't mater", "error")]
fn convert_without_debug(#[case] expected: &str, #[case] converted: MyType) {
assert_eq!(expected, converted.0);
}
114 changes: 114 additions & 0 deletions rstest/tests/resources/rstest/timeout_other_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use other_name::*;
use std::time::Duration;

fn ms(ms: u32) -> Duration {
Duration::from_millis(ms.into())
}

mod thread {
use super::*;

fn delayed_sum(a: u32, b: u32, delay: Duration) -> u32 {
std::thread::sleep(delay);
a + b
}

#[rstest]
#[timeout(ms(80))]
fn single_pass() {
assert_eq!(4, delayed_sum(2, 2, ms(10)));
}

#[rstest]
#[timeout(ms(100))]
fn single_fail_value() {
assert_eq!(5, delayed_sum(2, 2, ms(1)));
}

#[rstest]
#[timeout(ms(1000))]
#[should_panic = "user message"]
fn fail_with_user_message() {
panic!("user message");
}

#[rstest]
#[timeout(ms(10))]
fn single_fail_timeout() {
assert_eq!(4, delayed_sum(2, 2, ms(80)));
}

#[rstest]
#[timeout(ms(80))]
#[case(ms(10))]
fn one_pass(#[case] delay: Duration) {
assert_eq!(4, delayed_sum(2, 2, delay));
}

#[rstest]
#[timeout(ms(10))]
#[case(ms(80))]
fn one_fail_timeout(#[case] delay: Duration) {
assert_eq!(4, delayed_sum(2, 2, delay));
}

#[rstest]
#[timeout(ms(100))]
#[case(ms(1))]
fn one_fail_value(#[case] delay: Duration) {
assert_eq!(5, delayed_sum(2, 2, delay));
}

#[rstest]
#[case::pass(ms(1), 4)]
#[case::fail_timeout(ms(80), 4)]
#[case::fail_value(ms(1), 5)]
#[timeout(ms(40))]
fn group_same_timeout(#[case] delay: Duration, #[case] expected: u32) {
assert_eq!(expected, delayed_sum(2, 2, delay));
}

#[rstest]
#[timeout(ms(100))]
#[case::pass(ms(1), 4)]
#[timeout(ms(30))]
#[case::fail_timeout(ms(70), 4)]
#[timeout(ms(100))]
#[case::fail_value(ms(1), 5)]
fn group_single_timeout(#[case] delay: Duration, #[case] expected: u32) {
assert_eq!(expected, delayed_sum(2, 2, delay));
}

#[rstest]
#[case::pass(ms(1), 4)]
#[timeout(ms(10))]
#[case::fail_timeout(ms(60), 4)]
#[case::fail_value(ms(1), 5)]
#[timeout(ms(100))]
fn group_one_timeout_override(#[case] delay: Duration, #[case] expected: u32) {
assert_eq!(expected, delayed_sum(2, 2, delay));
}

struct S {}

#[rstest]
#[case(S{})]
fn compile_with_no_copy_arg(#[case] _s: S) {
assert!(true);
}

#[fixture]
fn no_copy() -> S {
S {}
}

#[rstest]
fn compile_with_no_copy_fixture(no_copy: S) {
assert!(true);
}

#[rstest]
fn default_timeout_failure() {
assert_eq!(4, delayed_sum(2, 2, ms(1100)));
}
}
54 changes: 45 additions & 9 deletions rstest/tests/rstest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,29 +1057,65 @@ fn timeout() {
mod import_crate_with_other_name {
use super::*;

fn prj(res: &str) -> Project {
fn prj(res: &str, features: Option<&[&str]>) -> Project {
let prj = crate::base_prj();
let default_features = features.is_none();
let features = features
.map(|features| {
features
.iter()
.map(|f| format!(r#""{}""#, f))
.collect::<Vec<_>>()
.join(",")
})
.unwrap_or_else(|| "".to_string());
prj.add_dependency(
"other_name",
&format!(
r#"{{path="{}", package = "rstest"}}"#,
r#"{{path="{}", package = "rstest", default-features = {}, features = [{}]}}"#,
prj.exec_dir_str().as_str(),
default_features,
features
),
);
prj.set_code_file(resources(res))
}

#[test]
fn timeout_should_compile_and_run() {
let mut prj = prj("timeout.rs");
prj.set_default_timeout(1);
assert!(prj.run_tests().is_ok());
fn should_fails_to_compile_if_crate_name_feature_is_not_enabled() {
let prj = prj("timeout_other_name.rs", Some(&[]));
assert!(!prj.compile().unwrap().status.success());
}

#[test]
fn convert_string_literal_should_compile_and_run() {
let prj = prj("convert_string_literal.rs");
assert!(prj.run_tests().is_ok());
fn should_always_compile_project_that_use_default_name() {
let prj = crate::base_prj();
prj.add_dependency(
"rstest",
&format!(
r#"{{path="{}", default-features = false}}"#,
prj.exec_dir_str().as_str(),
),
);
let prj = prj.set_code_file(resources("convert_string_literal.rs"));

assert!(prj.compile().unwrap().status.success());
}

#[rstest]
#[case::default_features(None)]
#[case::with_crate_name_feature(Some(["crate-name"].as_slice()))]
fn timeout_should_compile_and_run(#[case] features: Option<&[&str]>) {
let prj = prj("timeout_other_name.rs", features);
assert!(prj.compile().unwrap().status.success());
}

#[rstest]
#[case::default(None)]
#[case::with_crate_name_feature(Some(["crate-name"].as_slice()))]
fn convert_string_literal_should_compile_and_run(#[case] features: Option<&[&str]>) {
let prj = prj("convert_string_literal_other_name.rs", features);
assert!(prj.compile().unwrap().status.success());
}
}

Expand Down
5 changes: 3 additions & 2 deletions rstest_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ proc-macro = true

[features]
async-timeout = []
default = ["async-timeout"]
default = ["async-timeout", "crate-name"]
crate-name = ["dep:proc-macro-crate"]

[dependencies]
cfg-if = "1.0.0"
Expand All @@ -36,7 +37,7 @@ syn = { version = "2.0.2", features = [
"visit-mut",
] }
unicode-ident = "1.0.5"
proc-macro-crate = "3.1.0"
proc-macro-crate = { version = "3.1.0", optional = true }

[dev-dependencies]
actix-rt = "2.7.0"
Expand Down
21 changes: 14 additions & 7 deletions rstest_macros/src/render/crate_resolver.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
use quote::format_ident;
use syn::parse_quote;

pub fn crate_name() -> syn::Path {
use proc_macro_crate::FoundCrate;
cfg_if::cfg_if! {
if #[cfg(feature = "crate-name")] {
use proc_macro_crate::FoundCrate;
use quote::format_ident;

match proc_macro_crate::crate_name("rstest").expect("rstest is present in `Cargo.toml` qed") {
FoundCrate::Itself => parse_quote! { rstest },
FoundCrate::Name(name) => {
let myself = format_ident!("{name}");
parse_quote! { #myself }
match proc_macro_crate::crate_name("rstest").expect("rstest is present in `Cargo.toml` qed")
{
FoundCrate::Itself => parse_quote! { rstest },
FoundCrate::Name(name) => {
let myself = format_ident!("{name}");
parse_quote! { #myself }
}
}
} else {
parse_quote! { rstest }
}
}
}

0 comments on commit fef4f7b

Please sign in to comment.