@@ -6,65 +6,69 @@ tests is also encouraged!
6
6
## Testsuite
7
7
8
8
Cargo 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
11
24
12
25
These tests typically work by creating a temporary "project" with a
13
26
` Cargo.toml ` file, executing the ` cargo ` binary process, and checking the
14
27
stdout and stderr output against the expected output.
15
28
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
44
30
31
+ Generally, a functional test will be placed in ` tests/testsuite/<command>.rs ` and will look roughly like:
45
32
``` 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
+ }
56
51
```
57
52
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
60
60
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() ` .
63
65
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.
66
70
67
- ### Testing Nightly Features
71
+ #### Testing Nightly Features
68
72
69
73
If you are testing a Cargo feature that only works on "nightly" Cargo, then
70
74
you need to call ` masquerade_as_nightly_cargo ` on the process builder like
@@ -85,17 +89,7 @@ if !is_nightly() {
85
89
}
86
90
```
87
91
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
99
93
100
94
You should not write any tests that use the network such as contacting
101
95
crates.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
123
117
If you need to test git dependencies, see [ ` support::git ` ] to create a git
124
118
dependency.
125
119
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
+
126
224
## Debugging tests
127
225
128
226
In 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:
152
250
3 . Run with arguments: ` r check `
153
251
154
252
[ `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
157
255
[ `support` ] : https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/lib.rs
158
256
[ `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
160
258
[ `support::git` ] : https://github.com/rust-lang/cargo/blob/master/crates/cargo-test-support/src/git.rs
161
259
[ 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