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
+ s if s. starts_with ( "requires_" ) => {
47
+ let command = & s[ 9 ..] ;
48
+ ignore |= !has_command ( command) ;
49
+ }
50
+ s if s. starts_with ( ">=1." ) => {
51
+ requires_reason = true ;
52
+ let min_minor = s[ 4 ..] . parse ( ) . unwrap ( ) ;
53
+ ignore |= version ( ) . 0 < min_minor;
54
+ }
55
+ s if s. starts_with ( "reason=" ) => {
56
+ found_reason = true ;
57
+ }
58
+ _ => panic ! ( "unknown rule {:?}" , rule) ,
59
+ }
60
+ }
61
+ if requires_reason && !found_reason {
62
+ panic ! (
63
+ "#[cargo_test] with a rule also requires a reason, \
64
+ such as #[cargo_test(nightly, reason = \" needs -Z unstable-thing\" )]"
65
+ ) ;
66
+ }
67
+
7
68
let span = Span :: call_site ( ) ;
8
69
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" ) ;
70
+ let add_attr = |ret : & mut TokenStream , attr_name| {
71
+ ret. extend ( Some ( TokenTree :: from ( Punct :: new ( '#' , Spacing :: Alone ) ) ) ) ;
72
+ let attr = TokenTree :: from ( Ident :: new ( attr_name, span) ) ;
73
+ ret. extend ( Some ( TokenTree :: from ( Group :: new (
74
+ Delimiter :: Bracket ,
75
+ attr. into ( ) ,
76
+ ) ) ) ) ;
77
+ } ;
78
+ add_attr ( & mut ret, "test" ) ;
79
+ if ignore {
80
+ add_attr ( & mut ret, "ignore" ) ;
81
+ }
17
82
18
83
for token in item {
19
84
let group = match token {
@@ -38,17 +103,6 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
38
103
};"# ,
39
104
) ;
40
105
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
106
new_body. extend ( group. stream ( ) ) ;
53
107
ret. extend ( Some ( TokenTree :: from ( Group :: new (
54
108
group. delimiter ( ) ,
@@ -59,13 +113,79 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
59
113
ret
60
114
}
61
115
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,
116
+ fn split_rules ( t : TokenStream ) -> Vec < String > {
117
+ let tts: Vec < _ > = t. into_iter ( ) . collect ( ) ;
118
+ tts. split ( |tt| match tt {
119
+ TokenTree :: Punct ( p) => p. as_char ( ) == ',' ,
65
120
_ => false ,
66
121
} )
122
+ . filter ( |parts| !parts. is_empty ( ) )
123
+ . map ( |parts| {
124
+ parts
125
+ . into_iter ( )
126
+ . map ( |part| part. to_string ( ) )
127
+ . collect :: < String > ( )
128
+ } )
129
+ . collect ( )
67
130
}
68
131
69
132
fn to_token_stream ( code : & str ) -> TokenStream {
70
133
code. parse ( ) . unwrap ( )
71
134
}
135
+
136
+ static mut VERSION : ( u32 , bool ) = ( 0 , false ) ;
137
+
138
+ fn version ( ) -> & ' static ( u32 , bool ) {
139
+ static INIT : Once = Once :: new ( ) ;
140
+ INIT . call_once ( || {
141
+ let output = Command :: new ( "rustc" )
142
+ . arg ( "-V" )
143
+ . output ( )
144
+ . expect ( "rustc should run" ) ;
145
+ let stdout = std:: str:: from_utf8 ( & output. stdout ) . expect ( "utf8" ) ;
146
+ let vers = stdout. split_whitespace ( ) . skip ( 1 ) . next ( ) . unwrap ( ) ;
147
+ let is_nightly = option_env ! ( "CARGO_TEST_DISABLE_NIGHTLY" ) . is_none ( )
148
+ && ( vers. contains ( "-nightly" ) || vers. contains ( "-dev" ) ) ;
149
+ let minor = vers. split ( '.' ) . skip ( 1 ) . next ( ) . unwrap ( ) . parse ( ) . unwrap ( ) ;
150
+ unsafe { VERSION = ( minor, is_nightly) }
151
+ } ) ;
152
+ unsafe { & VERSION }
153
+ }
154
+
155
+ fn has_command ( command : & str ) -> bool {
156
+ let output = match Command :: new ( command) . arg ( "--version" ) . output ( ) {
157
+ Ok ( output) => output,
158
+ Err ( e) => {
159
+ // hg is not installed on GitHub macos.
160
+ // Consider installing it if Cargo gains more hg support, but
161
+ // otherwise it isn't critical.
162
+ if is_ci ( ) && !( cfg ! ( target_os = "macos" ) && command == "hg" ) {
163
+ panic ! (
164
+ "expected command `{}` to be somewhere in PATH: {}" ,
165
+ command, e
166
+ ) ;
167
+ }
168
+ return false ;
169
+ }
170
+ } ;
171
+ if !output. status . success ( ) {
172
+ panic ! (
173
+ "expected command `{}` to be runnable, got error {}:\n \
174
+ stderr:{}\n \
175
+ stdout:{}\n ",
176
+ command,
177
+ output. status,
178
+ String :: from_utf8_lossy( & output. stderr) ,
179
+ String :: from_utf8_lossy( & output. stdout)
180
+ ) ;
181
+ }
182
+ true
183
+ }
184
+
185
+ /// Whether or not this running in a Continuous Integration environment.
186
+ fn is_ci ( ) -> bool {
187
+ // Consider using `tracked_env` instead of option_env! when it is stabilized.
188
+ // `tracked_env` will handle changes, but not require rebuilding the macro
189
+ // itself like option_env does.
190
+ option_env ! ( "CI" ) . is_some ( ) || option_env ! ( "TF_BUILD" ) . is_some ( )
191
+ }
0 commit comments