Skip to content

Commit 4d26ed8

Browse files
committed
Add requirements to cargo_test.
1 parent 281989f commit 4d26ed8

File tree

27 files changed

+328
-747
lines changed

27 files changed

+328
-747
lines changed

crates/cargo-test-macro/src/lib.rs

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,62 @@
11
extern crate proc_macro;
22

33
use proc_macro::*;
4+
use std::env;
5+
use std::process::Command;
6+
use std::sync::Once;
47

58
#[proc_macro_attribute]
69
pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
10+
let mut build_std = false;
11+
let mut ignore = false;
12+
let mut requires_reason = false;
13+
let mut found_reason = false;
14+
for rule in split_rules(attr) {
15+
match rule.as_str() {
16+
"build_std" => build_std = true,
17+
"nightly" => {
18+
requires_reason = true;
19+
ignore |= !version().1;
20+
}
21+
"disable_git_cli" => {
22+
ignore |= disable_git_cli();
23+
}
24+
s if s.starts_with("requires_") => {
25+
let command = &s[9..];
26+
ignore |= !has_command(command);
27+
}
28+
s if s.starts_with(">=1.") => {
29+
requires_reason = true;
30+
let min_minor = s[4..].parse().unwrap();
31+
ignore |= version().0 < min_minor;
32+
}
33+
s if s.starts_with("reason=") => {
34+
found_reason = true;
35+
}
36+
_ => panic!("unknown rule {:?}", rule),
37+
}
38+
}
39+
if requires_reason && !found_reason {
40+
panic!(
41+
"#[cargo_test] with a rule also requires a reason, \
42+
such as #[cargo_test(nightly, reason = \"needs -Z unstable-thing\")]"
43+
);
44+
}
45+
746
let span = Span::call_site();
847
let mut ret = TokenStream::new();
9-
ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
10-
let test = TokenTree::from(Ident::new("test", span));
11-
ret.extend(Some(TokenTree::from(Group::new(
12-
Delimiter::Bracket,
13-
test.into(),
14-
))));
15-
16-
let build_std = contains_ident(&attr, "build_std");
48+
let add_attr = |ret: &mut TokenStream, attr_name| {
49+
ret.extend(Some(TokenTree::from(Punct::new('#', Spacing::Alone))));
50+
let attr = TokenTree::from(Ident::new(attr_name, span));
51+
ret.extend(Some(TokenTree::from(Group::new(
52+
Delimiter::Bracket,
53+
attr.into(),
54+
))));
55+
};
56+
add_attr(&mut ret, "test");
57+
if ignore {
58+
add_attr(&mut ret, "ignore");
59+
}
1760

1861
for token in item {
1962
let group = match token {
@@ -59,13 +102,80 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
59102
ret
60103
}
61104

62-
fn contains_ident(t: &TokenStream, ident: &str) -> bool {
63-
t.clone().into_iter().any(|t| match t {
64-
TokenTree::Ident(i) => i.to_string() == ident,
105+
fn split_rules(t: TokenStream) -> Vec<String> {
106+
let tts: Vec<_> = t.into_iter().collect();
107+
tts.split(|tt| match tt {
108+
TokenTree::Punct(p) => p.as_char() == ',',
65109
_ => false,
66110
})
111+
.filter(|parts| !parts.is_empty())
112+
.map(|parts| {
113+
parts
114+
.into_iter()
115+
.map(|part| part.to_string())
116+
.collect::<String>()
117+
})
118+
.collect()
67119
}
68120

69121
fn to_token_stream(code: &str) -> TokenStream {
70122
code.parse().unwrap()
71123
}
124+
125+
static mut VERSION: (u32, bool) = (0, false);
126+
127+
fn version() -> &'static (u32, bool) {
128+
static INIT: Once = Once::new();
129+
INIT.call_once(|| {
130+
let output = Command::new("rustc")
131+
.arg("-V")
132+
.output()
133+
.expect("rustc should run");
134+
let stdout = std::str::from_utf8(&output.stdout).expect("utf8");
135+
let vers = stdout.split_whitespace().skip(1).next().unwrap();
136+
let is_nightly = env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
137+
&& (vers.contains("-nightly") || vers.contains("-dev"));
138+
let minor = vers.split('.').skip(1).next().unwrap().parse().unwrap();
139+
unsafe { VERSION = (minor, is_nightly) }
140+
});
141+
unsafe { &VERSION }
142+
}
143+
144+
fn disable_git_cli() -> bool {
145+
// mingw git on Windows does not support Windows-style file URIs.
146+
// Appveyor in the rust repo has that git up front in the PATH instead
147+
// of Git-for-Windows, which causes this to fail.
148+
env::var("CARGO_TEST_DISABLE_GIT_CLI") == Ok("1".to_string())
149+
}
150+
151+
fn has_command(command: &str) -> bool {
152+
let output = match Command::new(command).arg("--version").output() {
153+
Ok(output) => output,
154+
Err(e) => {
155+
if is_ci() {
156+
panic!(
157+
"expected command `{}` to be somewhere in PATH: {}",
158+
command, e
159+
);
160+
}
161+
return false;
162+
}
163+
};
164+
if !output.status.success() {
165+
panic!(
166+
"expected command `{}` to be runnable, got error {}:\n\
167+
stderr:{}\n\
168+
stdout:{}\n",
169+
command,
170+
output.status,
171+
String::from_utf8_lossy(&output.stderr),
172+
String::from_utf8_lossy(&output.stdout)
173+
);
174+
}
175+
true
176+
}
177+
178+
/// Whether or not this running in a Continuous Integration environment.
179+
fn is_ci() -> bool {
180+
std::env::var("CI").is_ok() || std::env::var("TF_BUILD").is_ok()
181+
}

crates/cargo-test-support/src/lib.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,10 @@ pub fn rustc_host_env() -> String {
11281128

11291129
pub fn is_nightly() -> bool {
11301130
let vv = &RUSTC_INFO.verbose_version;
1131+
// CARGO_TEST_DISABLE_NIGHTLY is set in rust-lang/rust's CI so that all
1132+
// nightly-only tests are disabled there. Otherwise, it could make it
1133+
// difficult to land changes which would need to be made simultaneously in
1134+
// rust-lang/cargo and rust-lan/rust, which isn't possible.
11311135
env::var("CARGO_TEST_DISABLE_NIGHTLY").is_err()
11321136
&& (vv.contains("-nightly") || vv.contains("-dev"))
11331137
}
@@ -1350,16 +1354,6 @@ pub fn slow_cpu_multiplier(main: u64) -> Duration {
13501354
Duration::from_secs(*SLOW_CPU_MULTIPLIER * main)
13511355
}
13521356

1353-
pub fn command_is_available(cmd: &str) -> bool {
1354-
if let Err(e) = process(cmd).arg("-V").exec_with_output() {
1355-
eprintln!("{} not available, skipping tests", cmd);
1356-
eprintln!("{:?}", e);
1357-
false
1358-
} else {
1359-
true
1360-
}
1361-
}
1362-
13631357
#[cfg(windows)]
13641358
pub fn symlink_supported() -> bool {
13651359
if is_ci() {

src/doc/contrib/src/tests/writing.md

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,7 @@ fn <description>() {
5050
}
5151
```
5252

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
53+
The [`#[cargo_test]` attribute](#cargo_test-attribute) is used in place of `#[test]` to inject some setup code.
6054

6155
[`ProjectBuilder`] via `project()`:
6256
- Each project is in a separate directory in the sandbox
@@ -68,6 +62,37 @@ fn <description>() {
6862
- See [`support::compare`] for an explanation of the string pattern matching.
6963
Patterns are used to make it easier to match against the expected output.
7064

65+
#### `#[cargo_test]` attribute
66+
67+
The `#[cargo_test]` attribute injects code which does some setup before starting the test.
68+
It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as `/path/to/cargo/target/tmp/cit/t123/`.
69+
The sandbox will contain a `home` directory that will be used instead of your normal home directory.
70+
71+
The `#[cargo_test`] attribute takes several options that will affect how the test is generated.
72+
They are listed in parentheses separated with commas, such as:
73+
74+
```rust,ignore
75+
#[cargo_test(nightly, reason = "-Zfoo is unstable")]
76+
```
77+
78+
The options it supports are:
79+
80+
* `nightly` — This will cause the test to be ignored if not running on the nightly toolchain.
81+
This is useful for tests that use unstable options in `rustc` or `rustdoc`.
82+
These tests are run in Cargo's CI, but are disabled in rust-lang/rust's CI due to the difficulty of updating both repos simultaneously.
83+
A `reason` field is required to explain why it is nightly-only.
84+
* `build_std` — This is a `-Zbuild-std` test.
85+
This only runs on nightly, and only if the environment variable `CARGO_RUN_BUILD_STD_TESTS` is set.
86+
* `requires_` — This indicates a command that is required to be installed to be run.
87+
For example, `requires_rustmft` means the test will only run if the executable `rustfmt` is installed.
88+
These tests are *always* run on CI.
89+
This is mainly used to avoid requiring contributors from having every dependency installed.
90+
* `>=1.64` — This indicates that the test will only run with the given version of `rustc` or newer.
91+
This can be used when a new `rustc` feature has been stabilized that the test depends on.
92+
If this is specified, a `reason` is required to explain why it is being checked.
93+
* `disable_git_cli` — This is needed for `git-fetch-with-cli` tests.
94+
This disables the test in rust-lang/rust's CI due to a compatibility issue.
95+
7196
#### Testing Nightly Features
7297

7398
If you are testing a Cargo feature that only works on "nightly" Cargo, then
@@ -79,16 +104,15 @@ p.cargo("build").masquerade_as_nightly_cargo(&["print-im-a-teapot"])
79104
```
80105

81106
If you are testing a feature that only works on *nightly rustc* (such as
82-
benchmarks), then you should exit the test if it is not running with nightly
83-
rust, like this:
107+
benchmarks), then you should use the `nightly` option of the `cargo_test`
108+
attribute, like this:
84109

85110
```rust,ignore
86-
if !is_nightly() {
87-
// Add a comment here explaining why this is necessary.
88-
return;
89-
}
111+
#[cargo_test(nightly, reason = "-Zfoo is unstable")]
90112
```
91113

114+
This will cause the test to be ignored if not running on the nightly toolchain.
115+
92116
#### Specifying Dependencies
93117

94118
You should not write any tests that use the network such as contacting
@@ -201,16 +225,15 @@ the name of the feature as the reason, like this:
201225
```
202226

203227
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:
228+
benchmarks), then you should use the `nightly` option of the `cargo_test`
229+
attribute, like this:
206230

207231
```rust,ignore
208-
if !is_nightly() {
209-
// Add a comment here explaining why this is necessary.
210-
return;
211-
}
232+
#[cargo_test(nightly, reason = "-Zfoo is unstable")]
212233
```
213234

235+
This will cause the test to be ignored if not running on the nightly toolchain.
236+
214237
### Platform-specific Notes
215238

216239
When checking output, use `/` for paths even on Windows: the actual output

0 commit comments

Comments
 (0)