Skip to content

Commit 758eb6a

Browse files
authored
Detect snapshot names from function names (#213)
We used to detect the snapshot names form the current thread. This has been very unrealiable in a lot of cases. This changes the behavior so that the function name is used instead by using `std::any::type_name`. This does change behavior slightly but most users should not notice this change except if they relied on helper functions. In that case using a macro is a better solution most likely. Refs #127 Fixes #105
1 parent 9fc10eb commit 758eb6a

8 files changed

+42
-63
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to insta and cargo-insta are documented here.
55
## 1.12.0
66

77
- Add support for sorting redactions (`sorted_redaction` and `Settings::sort_selector`). (#212)
8+
- Changed snapshot name detection to no longer use thread names but function names. (#213)
89

910
## 1.11.0
1011

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ colors = ["console"]
3333
# This feature is now just always enabled because we use yaml internally now.
3434
serialization = []
3535

36+
# This feature is no longer used as snapshot name detection was changed.
37+
backtrace = []
38+
3639
[dependencies]
3740
csv = { version = "1.1.4", optional = true }
3841
serde = { version = "1.0.117", features = ["derive"] }
@@ -42,7 +45,6 @@ serde_json = "1.0.59"
4245
pest = { version = "2.1.3", optional = true }
4346
pest_derive = { version = "2.1.0", optional = true }
4447
ron = { version = "0.7.0", optional = true }
45-
backtrace = { version = "0.3.55", optional = true }
4648
toml = { version = "0.5.7", optional = true }
4749
globset = { version = "0.4.6", optional = true }
4850
walkdir = { version = "2.3.1", optional = true }

src/macros.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
/// Utility macro to return the name of the current function.
2+
#[doc(hidden)]
3+
#[macro_export]
4+
macro_rules! _function_name {
5+
() => {{
6+
fn f() {}
7+
fn type_name_of_val<T>(_: T) -> &'static str {
8+
std::any::type_name::<T>()
9+
}
10+
let name = type_name_of_val(f).strip_suffix("::f").unwrap_or("");
11+
name.strip_suffix("::{{closure}}").unwrap_or(name)
12+
}};
13+
}
14+
115
/// Asserts a `Serialize` snapshot in CSV format.
216
///
317
/// **Feature:** `csv` (disabled by default)
@@ -384,6 +398,7 @@ macro_rules! assert_snapshot {
384398
$name.into(),
385399
&$value,
386400
env!("CARGO_MANIFEST_DIR"),
401+
$crate::_function_name!(),
387402
module_path!(),
388403
file!(),
389404
line!(),

src/runtime.rs

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::io::Write;
66
use std::path::{Path, PathBuf};
77
use std::str;
88
use std::sync::{Arc, Mutex};
9-
use std::thread;
109

1110
use once_cell::sync::Lazy;
1211

@@ -76,59 +75,8 @@ pub enum ReferenceValue<'a> {
7675
Inline(&'a str),
7776
}
7877

79-
#[cfg(feature = "backtrace")]
80-
fn test_name_from_backtrace(module_path: &str) -> Result<String, &'static str> {
81-
let backtrace = backtrace::Backtrace::new();
82-
let frames = backtrace.frames();
83-
let mut found_run_wrapper = false;
84-
85-
for symbol in frames
86-
.iter()
87-
.rev()
88-
.flat_map(|x| x.symbols())
89-
.filter_map(|x| x.name())
90-
.map(|x| format!("{}", x))
91-
{
92-
if !found_run_wrapper {
93-
if symbol.starts_with("test::run_test::") {
94-
found_run_wrapper = true;
95-
}
96-
} else if symbol.starts_with(module_path) {
97-
let mut rv = &symbol[..symbol.len() - 19];
98-
if rv.ends_with("::{{closure}}") {
99-
rv = &rv[..rv.len() - 13];
100-
}
101-
return Ok(rv.to_string());
102-
}
103-
}
104-
105-
Err(
106-
"Cannot determine test name from backtrace, no snapshot name \
107-
can be generated. Did you forget to enable debug info?",
108-
)
109-
}
110-
111-
fn generate_snapshot_name_for_thread(module_path: &str) -> Result<String, &'static str> {
112-
let thread = thread::current();
113-
#[allow(unused_mut)]
114-
let mut name = Cow::Borrowed(
115-
thread
116-
.name()
117-
.ok_or("test thread is unnamed, no snapshot name can be generated.")?,
118-
);
119-
if name == "main" {
120-
#[cfg(feature = "backtrace")]
121-
{
122-
name = Cow::Owned(test_name_from_backtrace(module_path)?);
123-
}
124-
#[cfg(not(feature = "backtrace"))]
125-
{
126-
return Err("tests run with disabled concurrency, automatic snapshot \
127-
name generation is not supported. Consider using the \
128-
\"backtrace\" feature of insta which tries to recover test \
129-
names from the call stack.");
130-
}
131-
}
78+
fn detect_snapshot_name(function_name: &str, module_path: &str) -> Result<String, &'static str> {
79+
let name = Cow::Borrowed(function_name);
13280

13381
// clean test name first
13482
let mut name = name.rsplit("::").next().unwrap();
@@ -230,6 +178,7 @@ impl<'a> SnapshotAssertionContext<'a> {
230178
fn prepare(
231179
refval: ReferenceValue<'a>,
232180
manifest_dir: &'a str,
181+
function_name: &'a str,
233182
module_path: &'a str,
234183
assertion_file: &'a str,
235184
assertion_line: u32,
@@ -244,7 +193,7 @@ impl<'a> SnapshotAssertionContext<'a> {
244193
ReferenceValue::Named(name) => {
245194
let name = match name {
246195
Some(name) => add_suffix_to_snapshot_name(name),
247-
None => generate_snapshot_name_for_thread(module_path)
196+
None => detect_snapshot_name(function_name, module_path)
248197
.unwrap()
249198
.into(),
250199
};
@@ -257,7 +206,7 @@ impl<'a> SnapshotAssertionContext<'a> {
257206
snapshot_file = Some(file);
258207
}
259208
ReferenceValue::Inline(contents) => {
260-
snapshot_name = generate_snapshot_name_for_thread(module_path)
209+
snapshot_name = detect_snapshot_name(function_name, module_path)
261210
.ok()
262211
.map(Cow::Owned);
263212
let mut pending_file = cargo_workspace.join(assertion_file);
@@ -460,6 +409,7 @@ pub fn assert_snapshot(
460409
refval: ReferenceValue<'_>,
461410
new_snapshot_value: &str,
462411
manifest_dir: &str,
412+
function_name: &str,
463413
module_path: &str,
464414
assertion_file: &str,
465415
assertion_line: u32,
@@ -468,6 +418,7 @@ pub fn assert_snapshot(
468418
let ctx = SnapshotAssertionContext::prepare(
469419
refval,
470420
manifest_dir,
421+
function_name,
471422
module_path,
472423
assertion_file,
473424
assertion_line,
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
source: tests/test_inline.rs
3+
assertion_line: 41
34
expression: "\"Testing-thread-2\""
5+
46
---
57
Testing-thread-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
source: tests/test_inline.rs
3+
assertion_line: 40
34
expression: "\"Testing-thread\""
5+
46
---
57
Testing-thread

tests/test_clash_detection.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
use std::env;
22
use std::thread;
33

4-
use similar_asserts::assert_eq;
4+
fn test_foo_always_missing() {
5+
insta::assert_debug_snapshot!(42);
6+
}
7+
8+
fn foo_always_missing() {
9+
insta::assert_debug_snapshot!(42);
10+
}
511

612
#[test]
713
fn test_clash_detection() {
@@ -11,17 +17,15 @@ fn test_clash_detection() {
1117
env::set_var("INSTA_FORCE_PASS", "0");
1218

1319
let err1 = thread::Builder::new()
14-
.name("test_foo_always_missing".into())
1520
.spawn(|| {
16-
insta::assert_debug_snapshot!(42);
21+
test_foo_always_missing();
1722
})
1823
.unwrap()
1924
.join()
2025
.unwrap_err();
2126
let err2 = thread::Builder::new()
22-
.name("foo_always_missing".into())
2327
.spawn(|| {
24-
insta::assert_debug_snapshot!(42);
28+
foo_always_missing();
2529
})
2630
.unwrap()
2731
.join()
@@ -44,6 +48,6 @@ fn test_clash_detection() {
4448
values.sort();
4549
assert_eq!(&values[..], &vec![
4650
"Insta snapshot name clash detected between \'foo_always_missing\' and \'test_foo_always_missing\' in \'test_clash_detection\'. Rename one function.",
47-
"snapshot assertion for \'foo_always_missing\' failed in line 16",
51+
"snapshot assertion for \'foo_always_missing\' failed in line 5",
4852
][..]);
4953
}

tests/test_inline.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ fn test_unnamed_single_line() {
3131
assert_snapshot!("Testing-2");
3232
}
3333

34+
// We used to use the thread name for snapshot name detection. This is unreliable
35+
// so this test now basically does exactly the same as `test_unnamed_single_line`.
3436
#[test]
3537
fn test_unnamed_thread_single_line() {
3638
let builder = thread::Builder::new().name("foo::lol::something".into());

0 commit comments

Comments
 (0)