trybuild_internals_api/lib.rs
1//! [![github]](https://github.com/dtolnay/trybuild) [![crates-io]](https://crates.io/crates/trybuild) [![docs-rs]](https://docs.rs/trybuild)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! <br>
8//!
9//! ####  A compiler diagnostics testing library in just 3 functions.
10//!
11//! Trybuild is a test harness for invoking rustc on a set of test cases and
12//! asserting that any resulting error messages are the ones intended.
13//!
14//! Such tests are commonly useful for testing error reporting involving
15//! procedural macros. We would write test cases triggering either errors
16//! detected by the macro or errors detected by the Rust compiler in the
17//! resulting expanded code, and compare against the expected errors to ensure
18//! that they remain user-friendly.
19//!
20//! This style of testing is sometimes called *ui tests* because they test
21//! aspects of the user's interaction with a library outside of what would be
22//! covered by ordinary API tests.
23//!
24//! Nothing here is specific to macros; trybuild would work equally well for
25//! testing misuse of non-macro APIs.
26//!
27//! <br>
28//!
29//! # Compile-fail tests
30//!
31//! A minimal trybuild setup looks like this:
32//!
33//! ```
34//! #[test]
35//! fn ui() {
36//! let t = trybuild::TestCases::new();
37//! t.compile_fail("tests/ui/*.rs");
38//! }
39//! ```
40//!
41//! The test can be run with `cargo test`. It will individually compile each of
42//! the source files matching the glob pattern, expect them to fail to compile,
43//! and assert that the compiler's error message matches an adjacently named
44//! _*.stderr_ file containing the expected output (same file name as the test
45//! except with a different extension). If it matches, the test case is
46//! considered to succeed.
47//!
48//! Dependencies listed under `[dev-dependencies]` in the project's Cargo.toml
49//! are accessible from within the test cases.
50//!
51//! <p align="center">
52//! <img src="https://user-images.githubusercontent.com/1940490/57186574-76469e00-6e96-11e9-8cb5-b63b657170c9.png" width="700">
53//! </p>
54//!
55//! Failing tests display the expected vs actual compiler output inline.
56//!
57//! <p align="center">
58//! <img src="https://user-images.githubusercontent.com/1940490/57186575-79418e80-6e96-11e9-9478-c9b3dc10327f.png" width="700">
59//! </p>
60//!
61//! A compile_fail test that fails to fail to compile is also a failure.
62//!
63//! <p align="center">
64//! <img src="https://user-images.githubusercontent.com/1940490/57186576-7b0b5200-6e96-11e9-8bfd-2de705125108.png" width="700">
65//! </p>
66//!
67//! <br>
68//!
69//! # Pass tests
70//!
71//! The same test harness is able to run tests that are expected to pass, too.
72//! Ordinarily you would just have Cargo run such tests directly, but being able
73//! to combine modes like this could be useful for workshops in which
74//! participants work through test cases enabling one at a time. Trybuild was
75//! originally developed for my [procedural macros workshop at Rust
76//! Latam][workshop].
77//!
78//! [workshop]: https://github.com/dtolnay/proc-macro-workshop
79//!
80//! ```
81//! #[test]
82//! fn ui() {
83//! let t = trybuild::TestCases::new();
84//! t.pass("tests/01-parse-header.rs");
85//! t.pass("tests/02-parse-body.rs");
86//! t.compile_fail("tests/03-expand-four-errors.rs");
87//! t.pass("tests/04-paste-ident.rs");
88//! t.pass("tests/05-repeat-section.rs");
89//! //t.pass("tests/06-make-work-in-function.rs");
90//! //t.pass("tests/07-init-array.rs");
91//! //t.compile_fail("tests/08-ident-span.rs");
92//! }
93//! ```
94//!
95//! Pass tests are considered to succeed if they compile successfully and have a
96//! `main` function that does not panic when the compiled binary is executed.
97//!
98//! <p align="center">
99//! <img src="https://user-images.githubusercontent.com/1940490/57186580-7f376f80-6e96-11e9-9cae-8257609269ef.png" width="700">
100//! </p>
101//!
102//! <br>
103//!
104//! # Details
105//!
106//! That's the entire API.
107//!
108//! <br>
109//!
110//! # Workflow
111//!
112//! There are two ways to update the _*.stderr_ files as you iterate on your
113//! test cases or your library; handwriting them is not recommended.
114//!
115//! First, if a test case is being run as compile_fail but a corresponding
116//! _*.stderr_ file does not exist, the test runner will save the actual
117//! compiler output with the right filename into a directory called *wip* within
118//! the directory containing Cargo.toml. So you can update these files by
119//! deleting them, running `cargo test`, and moving all the files from *wip*
120//! into your testcase directory.
121//!
122//! <p align="center">
123//! <img src="https://user-images.githubusercontent.com/1940490/57186579-7cd51580-6e96-11e9-9f19-54dcecc9fbba.png" width="700">
124//! </p>
125//!
126//! Alternatively, run `cargo test` with the environment variable
127//! `TRYBUILD=overwrite` to skip the *wip* directory and write all compiler
128//! output directly in place. You'll want to check `git diff` afterward to be
129//! sure the compiler's output is what you had in mind.
130//!
131//! <br>
132//!
133//! # What to test
134//!
135//! When it comes to compile-fail tests, write tests for anything for which you
136//! care to find out when there are changes in the user-facing compiler output.
137//! As a negative example, please don't write compile-fail tests simply calling
138//! all of your public APIs with arguments of the wrong type; there would be no
139//! benefit.
140//!
141//! A common use would be for testing specific targeted error messages emitted
142//! by a procedural macro. For example the derive macro from the [`ref-cast`]
143//! crate is required to be placed on a type that has either `#[repr(C)]` or
144//! `#[repr(transparent)]` in order for the expansion to be free of undefined
145//! behavior, which it enforces at compile time:
146//!
147//! [`ref-cast`]: https://github.com/dtolnay/ref-cast
148//!
149//! ```console
150//! error: RefCast trait requires #[repr(C)] or #[repr(transparent)]
151//! --> $DIR/missing-repr.rs:3:10
152//! |
153//! 3 | #[derive(RefCast)]
154//! | ^^^^^^^
155//! ```
156//!
157//! Macros that consume helper attributes will want to check that unrecognized
158//! content within those attributes is properly indicated to the caller. Is the
159//! error message correctly placed under the erroneous tokens, not on a useless
160//! call\_site span?
161//!
162//! ```console
163//! error: unknown serde field attribute `qqq`
164//! --> $DIR/unknown-attribute.rs:5:13
165//! |
166//! 5 | #[serde(qqq = "...")]
167//! | ^^^
168//! ```
169//!
170//! Declarative macros can benefit from compile-fail tests too. The [`json!`]
171//! macro from serde\_json is just a great big macro\_rules macro but makes an
172//! effort to have error messages from broken JSON in the input always appear on
173//! the most appropriate token:
174//!
175//! [`json!`]: https://docs.rs/serde_json/1.0/serde_json/macro.json.html
176//!
177//! ```console
178//! error: no rules expected the token `,`
179//! --> $DIR/double-comma.rs:4:38
180//! |
181//! 4 | println!("{}", json!({ "k": null,, }));
182//! | ^ no rules expected this token in macro call
183//! ```
184//!
185//! Sometimes we may have a macro that expands successfully but we count on it
186//! to trigger particular compiler errors at some point beyond macro expansion.
187//! For example the [`readonly`] crate introduces struct fields that are public
188//! but readable only, even if the caller has a &mut reference to the
189//! surrounding struct. If someone writes to a readonly field, we need to be
190//! sure that it wouldn't compile:
191//!
192//! [`readonly`]: https://github.com/dtolnay/readonly
193//!
194//! ```console
195//! error[E0594]: cannot assign to data in a `&` reference
196//! --> $DIR/write-a-readonly.rs:17:26
197//! |
198//! 17 | println!("{}", s.n); s.n += 1;
199//! | ^^^^^^^^ cannot assign
200//! ```
201//!
202//! In all of these cases, the compiler's output can change because our crate or
203//! one of our dependencies broke something, or as a consequence of changes in
204//! the Rust compiler. Both are good reasons to have well conceived compile-fail
205//! tests. If we refactor and mistakenly cause an error that used to be correct
206//! to now no longer be emitted or be emitted in the wrong place, that is
207//! important for a test suite to catch. If the compiler changes something that
208//! makes error messages that we care about substantially worse, it is also
209//! important to catch and report as a compiler issue.
210
211#![doc(html_root_url = "https://docs.rs/trybuild/1.0.99")]
212#![allow(
213 clippy::collapsible_if,
214 clippy::comparison_chain,
215 clippy::default_trait_access,
216 clippy::derive_partial_eq_without_eq,
217 clippy::doc_markdown,
218 clippy::enum_glob_use,
219 clippy::iter_not_returning_iterator, // https://github.com/rust-lang/rust-clippy/issues/8285
220 clippy::let_underscore_untyped, // https://github.com/rust-lang/rust-clippy/issues/10410
221 clippy::manual_assert,
222 clippy::manual_range_contains,
223 clippy::module_inception,
224 clippy::module_name_repetitions,
225 clippy::must_use_candidate,
226 clippy::needless_pass_by_value,
227 clippy::non_ascii_literal,
228 clippy::range_plus_one,
229 clippy::similar_names,
230 clippy::single_match_else,
231 clippy::test_attr_in_doctest,
232 clippy::too_many_lines,
233 clippy::trivially_copy_pass_by_ref,
234 clippy::uninhabited_references,
235 clippy::uninlined_format_args,
236 clippy::unused_self,
237 clippy::while_let_on_iterator,
238)]
239#![deny(clippy::clone_on_ref_ptr)]
240
241#[macro_use]
242mod term;
243
244#[macro_use]
245mod path;
246
247pub mod cargo;
248pub mod dependencies;
249pub mod diff;
250pub mod directory;
251pub mod env;
252pub mod error;
253pub mod expand;
254pub mod features;
255pub mod flock;
256pub mod inherit;
257pub mod manifest;
258pub mod message;
259pub mod normalize;
260pub mod run;
261pub mod rustflags;
262
263use std::cell::RefCell;
264use std::panic::RefUnwindSafe;
265use std::path::{Path, PathBuf};
266use std::thread;
267
268#[derive(Debug)]
269pub struct TestCases {
270 runner: RefCell<Runner>,
271}
272
273#[derive(Debug)]
274pub struct Runner {
275 tests: Vec<Test>,
276}
277
278#[derive(Clone, Debug)]
279pub struct Test {
280 path: PathBuf,
281 expected: Expected,
282}
283
284#[derive(Copy, Clone, Debug)]
285enum Expected {
286 Pass,
287 CompileFail,
288}
289
290impl TestCases {
291 #[allow(clippy::new_without_default)]
292 pub fn new() -> Self {
293 TestCases {
294 runner: RefCell::new(Runner { tests: Vec::new() }),
295 }
296 }
297
298 pub fn pass<P: AsRef<Path>>(&self, path: P) {
299 self.runner.borrow_mut().tests.push(Test {
300 path: path.as_ref().to_owned(),
301 expected: Expected::Pass,
302 });
303 }
304
305 pub fn compile_fail<P: AsRef<Path>>(&self, path: P) {
306 self.runner.borrow_mut().tests.push(Test {
307 path: path.as_ref().to_owned(),
308 expected: Expected::CompileFail,
309 });
310 }
311}
312
313impl RefUnwindSafe for TestCases {}
314
315#[doc(hidden)]
316impl Drop for TestCases {
317 fn drop(&mut self) {
318 if !thread::panicking() {
319 self.runner.borrow_mut().run();
320 }
321 }
322}