1
1
extern crate proc_macro;
2
2
3
3
use proc_macro:: * ;
4
+ use std:: process:: Command ;
5
+ use std:: sync:: Once ;
4
6
5
7
#[ proc_macro_attribute]
6
8
pub fn cargo_test ( attr : TokenStream , item : TokenStream ) -> TokenStream {
9
+ // Ideally these options would be embedded in the test itself. However, I
10
+ // find it very helpful to have the test clearly state whether or not it
11
+ // is ignored. It would be nice to have some kind of runtime ignore
12
+ // support (such as
13
+ // https://internals.rust-lang.org/t/pre-rfc-skippable-tests/14611).
14
+ //
15
+ // Unfortunately a big drawback here is that if the environment changes
16
+ // (such as the existence of the `git` CLI), this will not trigger a
17
+ // rebuild and the test will still be ignored. In theory, something like
18
+ // `tracked_env` or `tracked_path`
19
+ // (https://github.com/rust-lang/rust/issues/99515) could help with this,
20
+ // but they don't really handle the absence of files well.
21
+ let mut ignore = false ;
22
+ let mut requires_reason = false ;
23
+ let mut found_reason = false ;
24
+ let is_not_nightly = || !version ( ) . 1 ;
25
+ for rule in split_rules ( attr) {
26
+ match rule. as_str ( ) {
27
+ "build_std_real" => {
28
+ // Only run the "real" build-std tests on nightly and with an
29
+ // explicit opt-in (these generally only work on linux, and
30
+ // have some extra requirements, and are slow, and can pollute
31
+ // the environment since it downloads dependencies).
32
+ ignore |= is_not_nightly ( ) ;
33
+ ignore |= option_env ! ( "CARGO_RUN_BUILD_STD_TESTS" ) . is_none ( ) ;
34
+ }
35
+ "build_std_mock" => {
36
+ // Only run the "mock" build-std tests on nightly and disable
37
+ // for windows-gnu which is missing object files (see
38
+ // https://github.com/rust-lang/wg-cargo-std-aware/issues/46).
39
+ ignore |= is_not_nightly ( ) ;
40
+ ignore |= cfg ! ( all( target_os = "windows" , target_env = "gnu" ) ) ;
41
+ }
42
+ "nightly" => {
43
+ requires_reason = true ;
44
+ ignore |= is_not_nightly ( ) ;
45
+ }
46
+ "disable_git_cli" => {
47
+ ignore |= disable_git_cli ( ) ;
48
+ }
49
+ s if s. starts_with ( "requires_" ) => {
50
+ let command = & s[ 9 ..] ;
51
+ ignore |= !has_command ( command) ;
52
+ }
53
+ s if s. starts_with ( ">=1." ) => {
54
+ requires_reason = true ;
55
+ let min_minor = s[ 4 ..] . parse ( ) . unwrap ( ) ;
56
+ ignore |= version ( ) . 0 < min_minor;
57
+ }
58
+ s if s. starts_with ( "reason=" ) => {
59
+ found_reason = true ;
60
+ }
61
+ _ => panic ! ( "unknown rule {:?}" , rule) ,
62
+ }
63
+ }
64
+ if requires_reason && !found_reason {
65
+ panic ! (
66
+ "#[cargo_test] with a rule also requires a reason, \
67
+ such as #[cargo_test(nightly, reason = \" needs -Z unstable-thing\" )]"
68
+ ) ;
69
+ }
70
+
7
71
let span = Span :: call_site ( ) ;
8
72
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" ) ;
73
+ let add_attr = |ret : & mut TokenStream , attr_name| {
74
+ ret. extend ( Some ( TokenTree :: from ( Punct :: new ( '#' , Spacing :: Alone ) ) ) ) ;
75
+ let attr = TokenTree :: from ( Ident :: new ( attr_name, span) ) ;
76
+ ret. extend ( Some ( TokenTree :: from ( Group :: new (
77
+ Delimiter :: Bracket ,
78
+ attr. into ( ) ,
79
+ ) ) ) ) ;
80
+ } ;
81
+ add_attr ( & mut ret, "test" ) ;
82
+ if ignore {
83
+ add_attr ( & mut ret, "ignore" ) ;
84
+ }
17
85
18
86
for token in item {
19
87
let group = match token {
@@ -38,17 +106,6 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
38
106
};"# ,
39
107
) ;
40
108
41
- // If this is a `build_std` test (aka `tests/build-std/*.rs`) then they
42
- // only run on nightly and they only run when specifically instructed to
43
- // on CI.
44
- if build_std {
45
- let ts = to_token_stream ( "if !cargo_test_support::is_nightly() { return }" ) ;
46
- new_body. extend ( ts) ;
47
- let ts = to_token_stream (
48
- "if std::env::var(\" CARGO_RUN_BUILD_STD_TESTS\" ).is_err() { return }" ,
49
- ) ;
50
- new_body. extend ( ts) ;
51
- }
52
109
new_body. extend ( group. stream ( ) ) ;
53
110
ret. extend ( Some ( TokenTree :: from ( Group :: new (
54
111
group. delimiter ( ) ,
@@ -59,13 +116,86 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
59
116
ret
60
117
}
61
118
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,
119
+ fn split_rules ( t : TokenStream ) -> Vec < String > {
120
+ let tts: Vec < _ > = t. into_iter ( ) . collect ( ) ;
121
+ tts. split ( |tt| match tt {
122
+ TokenTree :: Punct ( p) => p. as_char ( ) == ',' ,
65
123
_ => false ,
66
124
} )
125
+ . filter ( |parts| !parts. is_empty ( ) )
126
+ . map ( |parts| {
127
+ parts
128
+ . into_iter ( )
129
+ . map ( |part| part. to_string ( ) )
130
+ . collect :: < String > ( )
131
+ } )
132
+ . collect ( )
67
133
}
68
134
69
135
fn to_token_stream ( code : & str ) -> TokenStream {
70
136
code. parse ( ) . unwrap ( )
71
137
}
138
+
139
+ static mut VERSION : ( u32 , bool ) = ( 0 , false ) ;
140
+
141
+ fn version ( ) -> & ' static ( u32 , bool ) {
142
+ static INIT : Once = Once :: new ( ) ;
143
+ INIT . call_once ( || {
144
+ let output = Command :: new ( "rustc" )
145
+ . arg ( "-V" )
146
+ . output ( )
147
+ . expect ( "rustc should run" ) ;
148
+ let stdout = std:: str:: from_utf8 ( & output. stdout ) . expect ( "utf8" ) ;
149
+ let vers = stdout. split_whitespace ( ) . skip ( 1 ) . next ( ) . unwrap ( ) ;
150
+ let is_nightly = option_env ! ( "CARGO_TEST_DISABLE_NIGHTLY" ) . is_none ( )
151
+ && ( vers. contains ( "-nightly" ) || vers. contains ( "-dev" ) ) ;
152
+ let minor = vers. split ( '.' ) . skip ( 1 ) . next ( ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
153
+ unsafe { VERSION = ( minor, is_nightly) }
154
+ } ) ;
155
+ unsafe { & VERSION }
156
+ }
157
+
158
+ fn disable_git_cli ( ) -> bool {
159
+ // mingw git on Windows does not support Windows-style file URIs.
160
+ // Appveyor in the rust repo has that git up front in the PATH instead
161
+ // of Git-for-Windows, which causes this to fail.
162
+ matches ! ( option_env!( "CARGO_TEST_DISABLE_GIT_CLI" ) , Some ( "1" ) )
163
+ }
164
+
165
+ fn has_command ( command : & str ) -> bool {
166
+ let output = match Command :: new ( command) . arg ( "--version" ) . output ( ) {
167
+ Ok ( output) => output,
168
+ Err ( e) => {
169
+ // hg is not installed on GitHub macos.
170
+ // Consider installing it if Cargo gains more hg support, but
171
+ // otherwise it isn't critical.
172
+ if is_ci ( ) && !( cfg ! ( target_os = "macos" ) && command == "hg" ) {
173
+ panic ! (
174
+ "expected command `{}` to be somewhere in PATH: {}" ,
175
+ command, e
176
+ ) ;
177
+ }
178
+ return false ;
179
+ }
180
+ } ;
181
+ if !output. status . success ( ) {
182
+ panic ! (
183
+ "expected command `{}` to be runnable, got error {}:\n \
184
+ stderr:{}\n \
185
+ stdout:{}\n ",
186
+ command,
187
+ output. status,
188
+ String :: from_utf8_lossy( & output. stderr) ,
189
+ String :: from_utf8_lossy( & output. stdout)
190
+ ) ;
191
+ }
192
+ true
193
+ }
194
+
195
+ /// Whether or not this running in a Continuous Integration environment.
196
+ fn is_ci ( ) -> bool {
197
+ // Consider using `tracked_env` instead of option_env! when it is stabilized.
198
+ // `tracked_env` will handle changes, but not require rebuilding the macro
199
+ // itself like option_env does.
200
+ option_env ! ( "CI" ) . is_some ( ) || option_env ! ( "TF_BUILD" ) . is_some ( )
201
+ }
0 commit comments