Skip to content

Commit 7fb3917

Browse files
authored
Adds expect function mirroring expect! macro.
resolves #15
1 parent 6f5cb8c commit 7fb3917

File tree

3 files changed

+97
-27
lines changed

3 files changed

+97
-27
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 1.4.0
2+
3+
* Add non-macro versions of `expect` and `expect_file` ([#32])
4+
15
# 1.3.0
26

37
* Add `data()` getter to Expect ([#31])
@@ -13,6 +17,7 @@
1317

1418
* No changelog until this point :-(
1519

20+
[#32]: https://github.com/rust-analyzer/expect-test/pull/32
1621
[#31]: https://github.com/rust-analyzer/expect-test/pull/31
1722
[#27]: https://github.com/rust-analyzer/expect-test/pull/27
1823
[#26]: https://github.com/rust-analyzer/expect-test/pull/26

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "expect-test"
3-
version = "1.3.0"
3+
version = "1.4.0"
44
description = "Minimalistic snapshot testing library"
55
keywords = ["snapshot", "testing", "expect"]
66
categories = ["development-tools::testing"]

src/lib.rs

Lines changed: 91 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ fn update_expect() -> bool {
163163
env::var("UPDATE_EXPECT").is_ok()
164164
}
165165

166-
/// Creates an instance of `Expect` from string literal:
166+
/// Creates an instance of `Expect` from a string literal:
167167
///
168168
/// ```
169169
/// # use expect_test::expect;
@@ -190,6 +190,61 @@ macro_rules! expect {
190190
[[]] => { $crate::expect![[""]] };
191191
}
192192

193+
/// Creates an instance of `Expect` from a string literal:
194+
///
195+
/// ```
196+
/// # use expect_test::expect;
197+
/// expect!("
198+
/// Foo { value: 92 }
199+
/// ");
200+
/// expect!(r#"{"Foo": 92}"#);
201+
/// ```
202+
///
203+
/// Leading indentation is stripped.
204+
///
205+
/// This uses the `#[track_caller]` attribute to resolve relative file paths.
206+
/// This [may not work as expected][1] if this is called from a function which als uses `#[track_caller]`,
207+
/// or if the toolchain is [configured to strip caller information][2].
208+
/// In these cases you may use the macro form, [`expect!`].
209+
///
210+
/// [1]: https://github.com/rust-analyzer/expect-test/issues/15#issuecomment-939308821
211+
/// [2]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/location-detail.html
212+
#[track_caller]
213+
pub fn expect(data: impl ExpectedData) -> Expect {
214+
let location = std::panic::Location::caller();
215+
Expect {
216+
position: Position {
217+
file: location.file(),
218+
line: location.line(),
219+
column: location.column(),
220+
},
221+
data: data.str(),
222+
indent: true,
223+
}
224+
}
225+
226+
pub trait ExpectedData {
227+
fn str(&self) -> &'static str;
228+
}
229+
230+
impl ExpectedData for &'static str {
231+
fn str(&self) -> &'static str {
232+
self
233+
}
234+
}
235+
236+
impl ExpectedData for [&'static str; 1] {
237+
fn str(&self) -> &'static str {
238+
self[0]
239+
}
240+
}
241+
242+
impl ExpectedData for [&'static str; 0] {
243+
fn str(&self) -> &'static str {
244+
""
245+
}
246+
}
247+
193248
/// Creates an instance of `ExpectFile` from relative or absolute path:
194249
///
195250
/// ```
@@ -212,9 +267,9 @@ macro_rules! expect_file {
212267
/// ```
213268
///
214269
/// This uses the `#[track_caller]` attribute to resolve relative file paths.
215-
/// They [won't work correctly][1] if this is called from a function which also uses `#[track_caller]`,
270+
/// This [may not work as expected][1] if this is called from a function which als uses `#[track_caller]`,
216271
/// or if the toolchain is [configured to strip caller information][2].
217-
/// In these cases you must use the macro form, [`expect_file!`].
272+
/// In these cases you may use the macro form, [`expect_file!`].
218273
///
219274
/// [1]: https://github.com/rust-analyzer/expect-test/issues/15#issuecomment-939308821
220275
/// [2]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/location-detail.html
@@ -330,22 +385,19 @@ impl Expect {
330385
let mut line_start = 0;
331386
for (i, line) in lines_with_ends(file).enumerate() {
332387
if i == self.position.line as usize - 1 {
333-
// `column` points to the first character of the macro invocation:
388+
// `column` points to the first character of the macro invocation/function call:
334389
//
335-
// expect![[r#""#]] expect![""]
336-
// ^ ^ ^ ^
337-
// column offset offset
390+
// expect![[r#""#]] expect![""] expect("") expect([""])
391+
// ^ ^ ^ ^ ^ ^ ^ ^
392+
// column offset
338393
//
339-
// Seek past the exclam, then skip any whitespace and
340-
// the macro delimiter to get to our argument.
394+
// we seek until we find the first character of the string literal, if present.
341395
let byte_offset = line
342396
.char_indices()
343397
.skip((self.position.column - 1).try_into().unwrap())
344-
.skip_while(|&(_, c)| c != '!')
345-
.skip(1) // !
346-
.skip_while(|&(_, c)| c.is_whitespace())
347-
.skip(1) // [({
348-
.skip_while(|&(_, c)| c.is_whitespace())
398+
.skip_while(|&(_, c)| !matches!(c, '[' | '(' | '{'))
399+
// .skip_while(|&(_, c)| matches!(c, '[' | '(' | '{') || c.is_whitespace())
400+
.skip(1)
349401
.next()
350402
.expect("Failed to parse macro invocation")
351403
.0;
@@ -365,29 +417,35 @@ impl Expect {
365417
let literal_start = literal_start + (lit_to_eof.len() - lit_to_eof_trimmed.len());
366418

367419
let literal_len =
368-
locate_end(lit_to_eof_trimmed).expect("Couldn't find closing delimiter for `expect!`.");
420+
locate_end(lit_to_eof_trimmed).expect("Couldn't find closing delimiter for `expect`.");
369421
let literal_range = literal_start..literal_start + literal_len;
370422
Location { line_indent, literal_range }
371423
}
372424
}
373425

374426
fn locate_end(arg_start_to_eof: &str) -> Option<usize> {
375-
match arg_start_to_eof.chars().next()? {
376-
c if c.is_whitespace() => panic!("skip whitespace before calling `locate_end`"),
377-
378-
// expect![[]]
379-
'[' => {
427+
let c = arg_start_to_eof.chars().next()?;
428+
if c.is_whitespace() {
429+
panic!("skip whitespace before calling `locate_end`")
430+
}
431+
match c {
432+
// expect![["..."]] | expect!(["..."])
433+
'[' | '(' => {
434+
let end = if c == '[' { ']' } else { ')' };
380435
let str_start_to_eof = arg_start_to_eof[1..].trim_start();
436+
if str_start_to_eof.chars().next() == Some(end) {
437+
return Some(2);
438+
}
381439
let str_len = find_str_lit_len(str_start_to_eof)?;
382440
let str_end_to_eof = &str_start_to_eof[str_len..];
383-
let closing_brace_offset = str_end_to_eof.find(']')?;
441+
let closing_brace_offset = str_end_to_eof.find(end)?;
384442
Some((arg_start_to_eof.len() - str_end_to_eof.len()) + closing_brace_offset + 1)
385443
}
386444

387445
// expect![] | expect!{} | expect!()
388446
']' | '}' | ')' => Some(0),
389447

390-
// expect!["..."] | expect![r#"..."#]
448+
// expect!["..."] | expect![r#"..."#] | expect("...")
391449
_ => find_str_lit_len(arg_start_to_eof),
392450
}
393451
}
@@ -746,7 +804,14 @@ mod tests {
746804

747805
#[test]
748806
fn test_trivial_assert() {
749-
expect!["5"].assert_eq("5");
807+
expect!["1"].assert_eq("1");
808+
expect!["2"].assert_eq("2");
809+
}
810+
811+
#[test]
812+
fn test_trivial_assert_fn() {
813+
expect("3").assert_eq("3");
814+
expect("4").assert_eq("4");
750815
}
751816

752817
#[test]
@@ -766,15 +831,15 @@ mod tests {
766831
expect![[r##"[r#"{"foo": 42}"#]"##]].assert_eq(&patch);
767832

768833
let patch = format_patch(Some(0), "hello\nworld\n");
769-
expect![[r##"
834+
expect([r##"
770835
[r#"
771836
hello
772837
world
773-
"#]"##]]
838+
"#]"##])
774839
.assert_eq(&patch);
775840

776841
let patch = format_patch(Some(4), "single line");
777-
expect![[r#""single line""#]].assert_eq(&patch);
842+
expect([r#""single line""#]).assert_eq(&patch);
778843
}
779844

780845
#[test]

0 commit comments

Comments
 (0)