diff --git a/CHANGELOG.md b/CHANGELOG.md index a5ee07d..fb808ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.4.0 + +* Add non-macro versions of `expect` and `expect_file` ([#32]) + # 1.3.0 * Add `data()` getter to Expect ([#31]) @@ -13,6 +17,7 @@ * No changelog until this point :-( +[#32]: https://github.com/rust-analyzer/expect-test/pull/32 [#31]: https://github.com/rust-analyzer/expect-test/pull/31 [#27]: https://github.com/rust-analyzer/expect-test/pull/27 [#26]: https://github.com/rust-analyzer/expect-test/pull/26 diff --git a/Cargo.toml b/Cargo.toml index 2139c08..12d2bd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "expect-test" -version = "1.3.0" +version = "1.4.0" description = "Minimalistic snapshot testing library" keywords = ["snapshot", "testing", "expect"] categories = ["development-tools::testing"] diff --git a/src/lib.rs b/src/lib.rs index 6d2c29c..733455e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,7 +135,7 @@ //! //! ## Minimal Supported Rust Version //! -//! This crate's minimum supported `rustc` version is `1.45.0`. MSRV is updated +//! This crate's minimum supported `rustc` version is `1.46.0`. MSRV is updated //! conservatively, supporting roughly 10 minor versions of `rustc`. MSRV bump //! is not considered semver breaking, but will require at least minor version //! bump. @@ -163,7 +163,7 @@ fn update_expect() -> bool { env::var("UPDATE_EXPECT").is_ok() } -/// Creates an instance of `Expect` from string literal: +/// Creates an instance of `Expect` from a string literal: /// /// ``` /// # use expect_test::expect; @@ -190,6 +190,61 @@ macro_rules! expect { [[]] => { $crate::expect![[""]] }; } +/// Creates an instance of `Expect` from a string literal: +/// +/// ``` +/// # use expect_test::expect; +/// expect(" +/// Foo { value: 92 } +/// "); +/// expect(r#"{"Foo": 92}"#); +/// ``` +/// +/// Leading indentation is stripped. +/// +/// This uses the `#[track_caller]` attribute to resolve relative file paths. +/// This [may not work as expected][1] if this is called from a function which als uses `#[track_caller]`, +/// or if the toolchain is [configured to strip caller information][2]. +/// In these cases you may use the macro form, [`expect!`]. +/// +/// [1]: https://github.com/rust-analyzer/expect-test/issues/15#issuecomment-939308821 +/// [2]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/location-detail.html +#[track_caller] +pub fn expect(data: impl ExpectedData) -> Expect { + let location = std::panic::Location::caller(); + Expect { + position: Position { + file: location.file(), + line: location.line(), + column: location.column(), + }, + data: data.str(), + indent: true, + } +} + +pub trait ExpectedData { + fn str(&self) -> &'static str; +} + +impl ExpectedData for &'static str { + fn str(&self) -> &'static str { + self + } +} + +impl ExpectedData for [&'static str; 1] { + fn str(&self) -> &'static str { + self[0] + } +} + +impl ExpectedData for [&'static str; 0] { + fn str(&self) -> &'static str { + "" + } +} + /// Creates an instance of `ExpectFile` from relative or absolute path: /// /// ``` @@ -204,6 +259,25 @@ macro_rules! expect_file { }}; } +/// Creates an instance of `ExpectFile` from relative or absolute path: +/// +/// ``` +/// # use expect_test::expect_file; +/// expect_file("./test_data/bar.html"); +/// ``` +/// +/// This uses the `#[track_caller]` attribute to resolve relative file paths. +/// This [may not work as expected][1] if this is called from a function which als uses `#[track_caller]`, +/// or if the toolchain is [configured to strip caller information][2]. +/// In these cases you may use the macro form, [`expect_file!`]. +/// +/// [1]: https://github.com/rust-analyzer/expect-test/issues/15#issuecomment-939308821 +/// [2]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/location-detail.html +#[track_caller] +pub fn expect_file(path: impl Into) -> ExpectFile { + ExpectFile { path: path.into(), position: std::panic::Location::caller().file() } +} + /// Self-updating string literal. #[derive(Debug)] pub struct Expect { @@ -311,22 +385,18 @@ impl Expect { let mut line_start = 0; for (i, line) in lines_with_ends(file).enumerate() { if i == self.position.line as usize - 1 { - // `column` points to the first character of the macro invocation: + // `column` points to the first character of the macro invocation/function call: // - // expect![[r#""#]] expect![""] - // ^ ^ ^ ^ - // column offset offset + // expect![[r#""#]] expect![""] expect("") expect([""]) + // ^ ^ ^ ^ ^ ^ ^ ^ + // column offset // - // Seek past the exclam, then skip any whitespace and - // the macro delimiter to get to our argument. + // we seek until we find the first character of the string literal, if present. let byte_offset = line .char_indices() .skip((self.position.column - 1).try_into().unwrap()) - .skip_while(|&(_, c)| c != '!') - .skip(1) // ! - .skip_while(|&(_, c)| c.is_whitespace()) - .skip(1) // [({ - .skip_while(|&(_, c)| c.is_whitespace()) + .skip_while(|&(_, c)| !matches!(c, '[' | '(' | '{')) + .skip(1) .next() .expect("Failed to parse macro invocation") .0; @@ -346,29 +416,35 @@ impl Expect { let literal_start = literal_start + (lit_to_eof.len() - lit_to_eof_trimmed.len()); let literal_len = - locate_end(lit_to_eof_trimmed).expect("Couldn't find closing delimiter for `expect!`."); + locate_end(lit_to_eof_trimmed).expect("Couldn't find closing delimiter for `expect`."); let literal_range = literal_start..literal_start + literal_len; Location { line_indent, literal_range } } } fn locate_end(arg_start_to_eof: &str) -> Option { - match arg_start_to_eof.chars().next()? { - c if c.is_whitespace() => panic!("skip whitespace before calling `locate_end`"), - - // expect![[]] - '[' => { + let c = arg_start_to_eof.chars().next()?; + if c.is_whitespace() { + panic!("skip whitespace before calling `locate_end`") + } + match c { + // expect![["..."]] | expect!(["..."]) + '[' | '(' => { + let end = if c == '[' { ']' } else { ')' }; let str_start_to_eof = arg_start_to_eof[1..].trim_start(); + if str_start_to_eof.chars().next() == Some(end) { + return Some(2); + } let str_len = find_str_lit_len(str_start_to_eof)?; let str_end_to_eof = &str_start_to_eof[str_len..]; - let closing_brace_offset = str_end_to_eof.find(']')?; + let closing_brace_offset = str_end_to_eof.find(end)?; Some((arg_start_to_eof.len() - str_end_to_eof.len()) + closing_brace_offset + 1) } // expect![] | expect!{} | expect!() ']' | '}' | ')' => Some(0), - // expect!["..."] | expect![r#"..."#] + // expect!["..."] | expect![r#"..."#] | expect("...") _ => find_str_lit_len(arg_start_to_eof), } } @@ -591,11 +667,7 @@ fn lit_kind_for_patch(patch: &str) -> StrLitKind { let has_dquote = patch.chars().any(|c| c == '"'); if !has_dquote { let has_bslash_or_newline = patch.chars().any(|c| matches!(c, '\\' | '\n')); - return if has_bslash_or_newline { - StrLitKind::Raw(1) - } else { - StrLitKind::Normal - }; + return if has_bslash_or_newline { StrLitKind::Raw(1) } else { StrLitKind::Normal }; } // Find the maximum number of hashes that follow a double quote in the string. @@ -731,7 +803,14 @@ mod tests { #[test] fn test_trivial_assert() { - expect!["5"].assert_eq("5"); + expect!["1"].assert_eq("1"); + expect!["2"].assert_eq("2"); + } + + #[test] + fn test_trivial_assert_fn() { + expect("3").assert_eq("3"); + expect("4").assert_eq("4"); } #[test] @@ -751,15 +830,15 @@ mod tests { expect![[r##"[r#"{"foo": 42}"#]"##]].assert_eq(&patch); let patch = format_patch(Some(0), "hello\nworld\n"); - expect![[r##" + expect([r##" [r#" hello world - "#]"##]] + "#]"##]) .assert_eq(&patch); let patch = format_patch(Some(4), "single line"); - expect![[r#""single line""#]].assert_eq(&patch); + expect([r#""single line""#]).assert_eq(&patch); } #[test] @@ -795,6 +874,11 @@ mod tests { expect_file!["./lib.rs"].assert_eq(include_str!("./lib.rs")) } + #[test] + fn test_expect_file_fn() { + expect_file("./lib.rs").assert_eq(include_str!("./lib.rs")) + } + #[test] fn smoke_test_indent() { fn check_indented(input: &str, mut expect: Expect) { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 06c3b12..6a203f3 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -2,7 +2,7 @@ use std::env; use xaction::{cargo_toml, cmd, git, push_rustup_toolchain, section, Result}; -const MSRV: &str = "1.45.0"; +const MSRV: &str = "1.46.0"; fn main() { if let Err(err) = try_main() {