Skip to content

Commit b2e32ae

Browse files
committed
Detect snapshot names from function names
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 and #105
1 parent 9fc10eb commit b2e32ae

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)