Skip to main content

snapbox/
macros.rs

1/// Check if a value is the same as an expected value
2///
3/// By default [`filters`][crate::filter] are applied, including:
4/// - `...` is a line-wildcard when on a line by itself
5/// - `[..]` is a character-wildcard when inside a line
6/// - `[EXE]` matches `.exe` on Windows
7/// - `"{...}"` is a JSON value wildcard
8/// - `"...": "{...}"` is a JSON key-value wildcard
9/// - `\` to `/`
10/// - Newlines
11///
12/// To limit this to newline normalization for text, call [`Data::raw`][crate::Data] on `expected`.
13///
14/// # Effective signature
15///
16/// ```rust
17/// # use snapbox::IntoData;
18/// fn assert_data_eq(actual: impl IntoData, expected: impl IntoData) {
19///     // ...
20/// }
21/// ```
22///
23/// # Examples
24///
25/// ```rust
26/// # use snapbox::assert_data_eq;
27/// let output = "something";
28/// let expected = "so[..]g";
29/// assert_data_eq!(output, expected);
30/// ```
31///
32/// Can combine this with [`file!`]
33/// ```rust,no_run
34/// # use snapbox::assert_data_eq;
35/// # use snapbox::file;
36/// let actual = "something";
37/// assert_data_eq!(actual, file!["output.txt"]);
38/// ```
39#[macro_export]
40macro_rules! assert_data_eq {
41    ($actual: expr, $expected: expr $(,)?) => {{
42        let actual = $crate::IntoData::into_data($actual);
43        let expected = $crate::IntoData::into_data($expected);
44        $crate::Assert::new()
45            .action_env($crate::assert::DEFAULT_ACTION_ENV)
46            .eq(actual, expected);
47    }};
48}
49
50/// Find the directory for your source file
51///
52/// By default, a heuristic is used.
53///
54/// To override the heuristic, add to `$WORKSPACE_ROOT/.cargo/config.toml`:
55/// ```toml
56/// [env]
57/// CARGO_RUSTC_CURRENT_DIR = { value = "", relative = true }
58/// ```
59#[doc(hidden)] // forced to be visible in intended location
60#[macro_export]
61macro_rules! current_dir {
62    () => {{
63        let root = $crate::utils::cargo_rustc_current_dir!();
64        let file = ::std::file!();
65        let rel_path = ::std::path::Path::new(file).parent().unwrap();
66        root.join(rel_path)
67    }};
68}
69
70/// Find the directory for your source file
71///
72/// By default, a heuristic is used.
73///
74/// To override the heuristic, add to `$WORKSPACE_ROOT/.cargo/config.toml`:
75/// ```toml
76/// [env]
77/// CARGO_RUSTC_CURRENT_DIR = { value = "", relative = true }
78/// ```
79#[doc(hidden)] // forced to be visible in intended location
80#[macro_export]
81macro_rules! current_rs {
82    () => {{
83        let root = $crate::utils::cargo_rustc_current_dir!();
84        let file = ::std::file!();
85        let rel_path = ::std::path::Path::new(file);
86        root.join(rel_path)
87    }};
88}
89
90/// Find the base directory for [`std::file!`]
91///
92/// By default, a heuristic is used.
93///
94/// To override the heuristic, add to `$WORKSPACE_ROOT/.cargo/config.toml`:
95/// ```toml
96/// [env]
97/// CARGO_RUSTC_CURRENT_DIR = { value = "", relative = true }
98/// ```
99#[doc(hidden)] // forced to be visible in intended location
100#[macro_export]
101macro_rules! cargo_rustc_current_dir {
102    () => {{
103        if let Some(rustc_root) = ::std::option_env!("CARGO_RUSTC_CURRENT_DIR") {
104            ::std::path::Path::new(rustc_root)
105        } else {
106            let manifest_dir = ::std::path::Path::new(::std::env!("CARGO_MANIFEST_DIR"));
107            manifest_dir
108                .ancestors()
109                .filter(|it| it.join("Cargo.toml").exists())
110                .last()
111                .unwrap()
112        }
113    }};
114}
115
116/// Path to the current function
117///
118/// Closures are ignored
119#[doc(hidden)]
120#[macro_export]
121macro_rules! fn_path {
122    () => {{
123        fn f() {}
124        fn type_name_of_val<T>(_: T) -> &'static str {
125            std::any::type_name::<T>()
126        }
127        let mut name = type_name_of_val(f).strip_suffix("::f").unwrap_or("");
128        while let Some(rest) = name.strip_suffix("::{{closure}}") {
129            name = rest;
130        }
131        name
132    }};
133}
134
135/// The absolute path to a binary target's executable.
136///
137/// The `bin_target_name` is the name of the binary
138/// target, exactly as-is.
139///
140/// **NOTE:** This is only set when building an integration test or benchmark.
141///
142/// ## Example
143///
144/// ```rust,no_run
145/// #[test]
146/// fn cli_tests() {
147///     trycmd::TestCases::new()
148///         .default_bin_path(trycmd::cargo_bin!("bin-fixture"))
149///         .case("tests/cmd/*.trycmd");
150/// }
151/// ```
152#[macro_export]
153#[doc(hidden)]
154macro_rules! cargo_bin {
155    () => {
156        $crate::cmd::cargo_bin!(env!("CARGO_PKG_NAME"))
157    };
158    ($bin_target_name:expr) => {
159        ::std::path::Path::new(env!(concat!("CARGO_BIN_EXE_", $bin_target_name)))
160    };
161}
162
163#[cfg(test)]
164mod test {
165    #[test]
166    fn direct_fn_path() {
167        assert_eq!(fn_path!(), "snapbox::macros::test::direct_fn_path");
168    }
169
170    #[test]
171    #[allow(clippy::redundant_closure_call)]
172    fn closure_fn_path() {
173        (|| {
174            assert_eq!(fn_path!(), "snapbox::macros::test::closure_fn_path");
175        })();
176    }
177
178    #[test]
179    fn nested_fn_path() {
180        fn nested() {
181            assert_eq!(fn_path!(), "snapbox::macros::test::nested_fn_path::nested");
182        }
183        nested();
184    }
185}