@@ -6,65 +6,69 @@ tests is also encouraged!
66## Testsuite
77
88Cargo has a wide variety of integration tests that execute the ` cargo ` binary
9- and verify its behavior, located in the [ ` testsuite ` ] directory. The
10- [ ` support ` ] crate contains many helpers to make this process easy.
9+ and verify its behavior, located in the [ ` testsuite ` ] directory. The
10+ [ ` support ` ] crate and [ ` snapbox ` ] contain many helpers to make this process easy.
11+
12+ There are two styles of tests that can roughly be categorized as
13+ - functional tests
14+ - The fixture is programmatically defined
15+ - The assertions are regular string comparisons
16+ - Easier to share in an issue as a code block is completely self-contained
17+ - More resilient to insignificant changes though ui tests are easy to update when a change does occur
18+ - ui tests
19+ - The fixture is file-based
20+ - The assertions use file-backed snapshots that can be updated with an env variable
21+ - Easier to review the expected behavior of the command as more details are included
22+ - Easier to get up and running from an existing project
23+ - Easier to reason about as everything is just files in the repo
1124
1225These tests typically work by creating a temporary "project" with a
1326` Cargo.toml ` file, executing the ` cargo ` binary process, and checking the
1427stdout and stderr output against the expected output.
1528
16- ### ` cargo_test ` attribute
17-
18- Cargo's tests use the ` #[cargo_test] ` attribute instead of ` #[test] ` . This
19- attribute injects some code which does some setup before starting the test,
20- creating the little "sandbox" described below.
21-
22- ### Basic test structure
23-
24- The general form of a test involves creating a "project", running ` cargo ` , and
25- checking the result. Projects are created with the [ ` ProjectBuilder ` ] where
26- you specify some files to create. The general form looks like this:
27-
28- ``` rust,ignore
29- let p = project()
30- .file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
31- .build();
32- ```
33-
34- The project creates a mini sandbox under the "cargo integration test"
35- directory with each test getting a separate directory such as
36- ` /path/to/cargo/target/cit/t123/ ` . Each project appears as a separate
37- directory. There is also an empty ` home ` directory created that will be used
38- as a home directory instead of your normal home directory.
39-
40- If you do not specify a ` Cargo.toml ` manifest using ` file() ` , one is
41- automatically created with a project name of ` foo ` using ` basic_manifest() ` .
42-
43- To run Cargo, call the ` cargo ` method and make assertions on the execution:
29+ ### Functional Tests
4430
31+ Generally, a functional test will be placed in ` tests/testsuite/<command>.rs ` and will look roughly like:
4532``` rust,ignore
46- p.cargo("run --bin foo")
47- .with_stderr(
48- "\
49- [COMPILING] foo [..]
50- [FINISHED] [..]
51- [RUNNING] `target/debug/foo`
52- ",
53- )
54- .with_stdout("hi!")
55- .run();
33+ #[cargo_test]
34+ fn <description>() {
35+ let p = project()
36+ .file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
37+ .build();
38+
39+ p.cargo("run --bin foo")
40+ .with_stderr(
41+ "\
42+ [COMPILING] foo [..]
43+ [FINISHED] [..]
44+ [RUNNING] `target/debug/foo`
45+ ",
46+ )
47+ .with_stdout("hi!")
48+ .run();
49+ }
50+ }
5651```
5752
58- This uses the [ ` Execs ` ] struct to build up a command to execute, along with
59- the expected output.
53+ ` #[cargo_test] ` :
54+ - This is used in place of ` #[test] `
55+ - This attribute injects code which does some setup before starting the
56+ test, creating a filesystem "sandbox" under the "cargo integration test"
57+ directory for each test such as
58+ ` /path/to/cargo/target/cit/t123/ `
59+ - The sandbox will contain a ` home ` directory that will be used instead of your normal home directory
6060
61- See [ ` support::compare ` ] for an explanation of the string pattern matching.
62- Patterns are used to make it easier to match against the expected output.
61+ [ ` ProjectBuilder ` ] via ` project() ` :
62+ - Each project is in a separate directory in the sandbox
63+ - If you do not specify a ` Cargo.toml ` manifest using ` file() ` , one is
64+ automatically created with a project name of ` foo ` using ` basic_manifest() ` .
6365
64- Browse the ` pub ` functions and modules in the [ ` support ` ] crate for a variety
65- of other helpful utilities.
66+ [ ` Execs ` ] via ` p.cargo(...) ` :
67+ - This executes the command and evaluates different assertions
68+ - See [ ` support::compare ` ] for an explanation of the string pattern matching.
69+ Patterns are used to make it easier to match against the expected output.
6670
67- ### Testing Nightly Features
71+ #### Testing Nightly Features
6872
6973If you are testing a Cargo feature that only works on "nightly" Cargo, then
7074you need to call ` masquerade_as_nightly_cargo ` on the process builder like
@@ -85,17 +89,7 @@ if !is_nightly() {
8589}
8690```
8791
88- ### Platform-specific Notes
89-
90- When checking output, use ` / ` for paths even on Windows: the actual output
91- of ` \ ` on Windows will be replaced with ` / ` .
92-
93- Be careful when executing binaries on Windows. You should not rename, delete,
94- or overwrite a binary immediately after running it. Under some conditions
95- Windows will fail with errors like "directory not empty" or "failed to remove"
96- or "access is denied".
97-
98- ### Specifying Dependencies
92+ #### Specifying Dependencies
9993
10094You should not write any tests that use the network such as contacting
10195crates.io. Typically, simple path dependencies are the easiest way to add a
@@ -123,6 +117,110 @@ If you need to test with registry dependencies, see
123117If you need to test git dependencies, see [ ` support::git ` ] to create a git
124118dependency.
125119
120+ ### UI Tests
121+
122+ UI Tests are a bit more spread out and generally look like:
123+
124+ ` tests/testsuite/<command>/mod.rs ` :
125+ ``` rust,ignore
126+ mod <case>;
127+ ```
128+
129+ ` tests/testsuite/<command>/<case>/mod.rs ` :
130+ ``` rust,ignore
131+ use cargo_test_support::prelude::*;
132+ use cargo_test_support::compare::assert;
133+ use cargo_test_support::Project;
134+ use cargo_test_support::curr_dir;
135+
136+ #[cargo_test]
137+ fn <name>() {
138+ let project = Project::from_template(curr_dir!().join("in"));
139+ let project_root = project.root();
140+ let cwd = &project_root;
141+
142+ snapbox::cmd::Command::cargo()
143+ .arg("run")
144+ .arg_line("--bin foo")
145+ .current_dir(cwd)
146+ .assert()
147+ .success()
148+ .stdout_matches_path(curr_dir!().join("stdout.log"))
149+ .stderr_matches_path(curr_dir!().join("stderr.log"));
150+
151+ assert().subset_matches(curr_dir!().join("out"), &project_root);
152+ }
153+ ```
154+
155+ Then populate
156+ - ` tests/testsuite/<command>/<case>/in ` with the project's directory structure
157+ - ` tests/testsuite/<command>/<case>/out ` with the files you want verified
158+ - ` tests/testsuite/<command>/<case>/stdout.log ` with nothing
159+ - ` tests/testsuite/<command>/<case>/stderr.log ` with nothing
160+
161+ ` #[cargo_test] ` :
162+ - This is used in place of ` #[test] `
163+ - This attribute injects code which does some setup before starting the
164+ test, creating a filesystem "sandbox" under the "cargo integration test"
165+ directory for each test such as
166+ ` /path/to/cargo/target/cit/t123/ `
167+ - The sandbox will contain a ` home ` directory that will be used instead of your normal home directory
168+
169+ ` Project ` :
170+ - The project is copied from a directory in the repo
171+ - Each project is in a separate directory in the sandbox
172+
173+ [ ` Command ` ] via ` Command::cargo() ` :
174+ - Set up and run a command.
175+
176+ [ ` OutputAssert ` ] via ` Command::assert() ` :
177+ - Perform assertions on the result of the [ ` Command ` ]
178+
179+ [ ` Assert ` ] via ` assert() ` :
180+ - Verify the command modified the file system as expected
181+
182+ #### Updating Snapshots
183+
184+ The project, stdout, and stderr snapshots can be updated by running with the
185+ ` SNAPSHOTS=overwrite ` environment variable, like:
186+ ``` console
187+ $ SNAPSHOTS=overwrite cargo test
188+ ```
189+
190+ Be sure to check the snapshots to make sure they make sense.
191+
192+ #### Testing Nightly Features
193+
194+ If you are testing a Cargo feature that only works on "nightly" Cargo, then
195+ you need to call ` masquerade_as_nightly_cargo ` on the process builder like
196+ this:
197+
198+ ``` rust,ignore
199+ snapbox::cmd::Command::cargo()
200+ .masquerade_as_nightly_cargo()
201+ ```
202+
203+ If you are testing a feature that only works on * nightly rustc* (such as
204+ benchmarks), then you should exit the test if it is not running with nightly
205+ rust, like this:
206+
207+ ``` rust,ignore
208+ if !is_nightly() {
209+ // Add a comment here explaining why this is necessary.
210+ return;
211+ }
212+ ```
213+
214+ ### Platform-specific Notes
215+
216+ When checking output, use ` / ` for paths even on Windows: the actual output
217+ of ` \ ` on Windows will be replaced with ` / ` .
218+
219+ Be careful when executing binaries on Windows. You should not rename, delete,
220+ or overwrite a binary immediately after running it. Under some conditions
221+ Windows will fail with errors like "directory not empty" or "failed to remove"
222+ or "access is denied".
223+
126224## Debugging tests
127225
128226In some cases, you may need to dig into a test that is not working as you
@@ -152,10 +250,14 @@ environment. The general process is:
152250 3 . Run with arguments: ` r check `
153251
154252[ `testsuite` ] : https://github.com/rust-lang/cargo/tree/master/tests/testsuite/
155- [ `ProjectBuilder` ] : https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357 /crates/cargo-test-support/src/lib.rs#L225-L231
156- [ `Execs` ] : https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357 /crates/cargo-test-support/src/lib.rs#L558-L579
253+ [ `ProjectBuilder` ] : https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2 /crates/cargo-test-support/src/lib.rs#L196-L202
254+ [ `Execs` ] : https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2 /crates/cargo-test-support/src/lib.rs#L531-L550
157255[ `support` ] : https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/lib.rs
158256[ `support::compare` ] : https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/compare.rs
159- [ `support::registry::Package` ] : https://github.com/rust-lang/cargo/blob/e4b65bdc80f2a293447f2f6a808fa7c84bf9a357 /crates/cargo-test-support/src/registry.rs#L73-L149
257+ [ `support::registry::Package` ] : https://github.com/rust-lang/cargo/blob/d847468768446168b596f721844193afaaf9d3f2 /crates/cargo-test-support/src/registry.rs#L311-L389
160258[ `support::git` ] : https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/git.rs
161259[ Running Cargo ] : ../process/working-on-cargo.md#running-cargo
260+ [ `snapbox` ] : https://docs.rs/snapbox/latest/snapbox/
261+ [ `Command` ] : https://docs.rs/snapbox/latest/snapbox/cmd/struct.Command.html
262+ [ `OutputAssert` ] : https://docs.rs/snapbox/latest/snapbox/cmd/struct.OutputAssert.html
263+ [ `Assert` ] : https://docs.rs/snapbox/latest/snapbox/struct.Assert.html
0 commit comments