1 use std::collections::HashSet;
2 use std::env;
3 use std::fs::File;
4 use std::io::prelude::*;
5 use std::io::BufReader;
6 use std::path::{Path, PathBuf};
7 use std::process::Command;
8
9 use build_helper::ci::CiEnv;
10 use tracing::*;
11
12 use crate::common::{Config, Debugger, FailMode, Mode, PassMode};
13 use crate::header::cfg::parse_cfg_name_directive;
14 use crate::header::cfg::MatchOutcome;
15 use crate::header::needs::CachedNeedsConditions;
16 use crate::{extract_cdb_version, extract_gdb_version};
17
18 mod cfg;
19 mod needs;
20 #[cfg(test)]
21 mod tests;
22
23 pub struct HeadersCache {
24 needs: CachedNeedsConditions,
25 }
26
27 impl HeadersCache {
load(config: &Config) -> Self28 pub fn load(config: &Config) -> Self {
29 Self { needs: CachedNeedsConditions::load(config) }
30 }
31 }
32
33 /// Properties which must be known very early, before actually running
34 /// the test.
35 #[derive(Default)]
36 pub struct EarlyProps {
37 pub aux: Vec<String>,
38 pub aux_crate: Vec<(String, String)>,
39 pub revisions: Vec<String>,
40 }
41
42 impl EarlyProps {
from_file(config: &Config, testfile: &Path) -> Self43 pub fn from_file(config: &Config, testfile: &Path) -> Self {
44 let file = File::open(testfile).expect("open test file to parse earlyprops");
45 Self::from_reader(config, testfile, file)
46 }
47
from_reader<R: Read>(config: &Config, testfile: &Path, rdr: R) -> Self48 pub fn from_reader<R: Read>(config: &Config, testfile: &Path, rdr: R) -> Self {
49 let mut props = EarlyProps::default();
50 iter_header(testfile, rdr, &mut |_, ln, _| {
51 config.push_name_value_directive(ln, directives::AUX_BUILD, &mut props.aux, |r| {
52 r.trim().to_string()
53 });
54 config.push_name_value_directive(
55 ln,
56 directives::AUX_CRATE,
57 &mut props.aux_crate,
58 Config::parse_aux_crate,
59 );
60 config.parse_and_update_revisions(ln, &mut props.revisions);
61 });
62 return props;
63 }
64 }
65
66 #[derive(Clone, Debug)]
67 pub struct TestProps {
68 // Lines that should be expected, in order, on standard out
69 pub error_patterns: Vec<String>,
70 // Regexes that should be expected, in order, on standard out
71 pub regex_error_patterns: Vec<String>,
72 // Extra flags to pass to the compiler
73 pub compile_flags: Vec<String>,
74 // Extra flags to pass when the compiled code is run (such as --bench)
75 pub run_flags: Option<String>,
76 // If present, the name of a file that this test should match when
77 // pretty-printed
78 pub pp_exact: Option<PathBuf>,
79 // Other crates that should be compiled (typically from the same
80 // directory as the test, but for backwards compatibility reasons
81 // we also check the auxiliary directory)
82 pub aux_builds: Vec<String>,
83 // Similar to `aux_builds`, but a list of NAME=somelib.rs of dependencies
84 // to build and pass with the `--extern` flag.
85 pub aux_crates: Vec<(String, String)>,
86 // Environment settings to use for compiling
87 pub rustc_env: Vec<(String, String)>,
88 // Environment variables to unset prior to compiling.
89 // Variables are unset before applying 'rustc_env'.
90 pub unset_rustc_env: Vec<String>,
91 // Environment settings to use during execution
92 pub exec_env: Vec<(String, String)>,
93 // Environment variables to unset prior to execution.
94 // Variables are unset before applying 'exec_env'
95 pub unset_exec_env: Vec<String>,
96 // Build documentation for all specified aux-builds as well
97 pub build_aux_docs: bool,
98 // Flag to force a crate to be built with the host architecture
99 pub force_host: bool,
100 // Check stdout for error-pattern output as well as stderr
101 pub check_stdout: bool,
102 // Check stdout & stderr for output of run-pass test
103 pub check_run_results: bool,
104 // For UI tests, allows compiler to generate arbitrary output to stdout
105 pub dont_check_compiler_stdout: bool,
106 // For UI tests, allows compiler to generate arbitrary output to stderr
107 pub dont_check_compiler_stderr: bool,
108 // When checking the output of stdout or stderr check
109 // that the lines of expected output are a subset of the actual output.
110 pub compare_output_lines_by_subset: bool,
111 // Don't force a --crate-type=dylib flag on the command line
112 //
113 // Set this for example if you have an auxiliary test file that contains
114 // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures
115 // that the aux file is compiled as a `proc-macro` and not as a `dylib`.
116 pub no_prefer_dynamic: bool,
117 // Run -Zunpretty expanded when running pretty printing tests
118 pub pretty_expanded: bool,
119 // Which pretty mode are we testing with, default to 'normal'
120 pub pretty_mode: String,
121 // Only compare pretty output and don't try compiling
122 pub pretty_compare_only: bool,
123 // Patterns which must not appear in the output of a cfail test.
124 pub forbid_output: Vec<String>,
125 // Revisions to test for incremental compilation.
126 pub revisions: Vec<String>,
127 // Directory (if any) to use for incremental compilation. This is
128 // not set by end-users; rather it is set by the incremental
129 // testing harness and used when generating compilation
130 // arguments. (In particular, it propagates to the aux-builds.)
131 pub incremental_dir: Option<PathBuf>,
132 // If `true`, this test will use incremental compilation.
133 //
134 // This can be set manually with the `incremental` header, or implicitly
135 // by being a part of an incremental mode test. Using the `incremental`
136 // header should be avoided if possible; using an incremental mode test is
137 // preferred. Incremental mode tests support multiple passes, which can
138 // verify that the incremental cache can be loaded properly after being
139 // created. Just setting the header will only verify the behavior with
140 // creating an incremental cache, but doesn't check that it is created
141 // correctly.
142 //
143 // Compiletest will create the incremental directory, and ensure it is
144 // empty before the test starts. Incremental mode tests will reuse the
145 // incremental directory between passes in the same test.
146 pub incremental: bool,
147 // If `true`, this test is a known bug.
148 //
149 // When set, some requirements are relaxed. Currently, this only means no
150 // error annotations are needed, but this may be updated in the future to
151 // include other relaxations.
152 pub known_bug: bool,
153 // How far should the test proceed while still passing.
154 pass_mode: Option<PassMode>,
155 // Ignore `--pass` overrides from the command line for this test.
156 ignore_pass: bool,
157 // How far this test should proceed to start failing.
158 pub fail_mode: Option<FailMode>,
159 // rustdoc will test the output of the `--test` option
160 pub check_test_line_numbers_match: bool,
161 // customized normalization rules
162 pub normalize_stdout: Vec<(String, String)>,
163 pub normalize_stderr: Vec<(String, String)>,
164 pub failure_status: Option<i32>,
165 // For UI tests, allows compiler to exit with arbitrary failure status
166 pub dont_check_failure_status: bool,
167 // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the
168 // resulting Rust code.
169 pub run_rustfix: bool,
170 // If true, `rustfix` will only apply `MachineApplicable` suggestions.
171 pub rustfix_only_machine_applicable: bool,
172 pub assembly_output: Option<String>,
173 // If true, the test is expected to ICE
174 pub should_ice: bool,
175 // If true, the stderr is expected to be different across bit-widths.
176 pub stderr_per_bitwidth: bool,
177 // The MIR opt to unit test, if any
178 pub mir_unit_test: Option<String>,
179 // Whether to tell `rustc` to remap the "src base" directory to a fake
180 // directory.
181 pub remap_src_base: bool,
182 }
183
184 mod directives {
185 pub const ERROR_PATTERN: &'static str = "error-pattern";
186 pub const REGEX_ERROR_PATTERN: &'static str = "regex-error-pattern";
187 pub const COMPILE_FLAGS: &'static str = "compile-flags";
188 pub const RUN_FLAGS: &'static str = "run-flags";
189 pub const SHOULD_ICE: &'static str = "should-ice";
190 pub const BUILD_AUX_DOCS: &'static str = "build-aux-docs";
191 pub const FORCE_HOST: &'static str = "force-host";
192 pub const CHECK_STDOUT: &'static str = "check-stdout";
193 pub const CHECK_RUN_RESULTS: &'static str = "check-run-results";
194 pub const DONT_CHECK_COMPILER_STDOUT: &'static str = "dont-check-compiler-stdout";
195 pub const DONT_CHECK_COMPILER_STDERR: &'static str = "dont-check-compiler-stderr";
196 pub const NO_PREFER_DYNAMIC: &'static str = "no-prefer-dynamic";
197 pub const PRETTY_EXPANDED: &'static str = "pretty-expanded";
198 pub const PRETTY_MODE: &'static str = "pretty-mode";
199 pub const PRETTY_COMPARE_ONLY: &'static str = "pretty-compare-only";
200 pub const AUX_BUILD: &'static str = "aux-build";
201 pub const AUX_CRATE: &'static str = "aux-crate";
202 pub const EXEC_ENV: &'static str = "exec-env";
203 pub const RUSTC_ENV: &'static str = "rustc-env";
204 pub const UNSET_EXEC_ENV: &'static str = "unset-exec-env";
205 pub const UNSET_RUSTC_ENV: &'static str = "unset-rustc-env";
206 pub const FORBID_OUTPUT: &'static str = "forbid-output";
207 pub const CHECK_TEST_LINE_NUMBERS_MATCH: &'static str = "check-test-line-numbers-match";
208 pub const IGNORE_PASS: &'static str = "ignore-pass";
209 pub const FAILURE_STATUS: &'static str = "failure-status";
210 pub const DONT_CHECK_FAILURE_STATUS: &'static str = "dont-check-failure-status";
211 pub const RUN_RUSTFIX: &'static str = "run-rustfix";
212 pub const RUSTFIX_ONLY_MACHINE_APPLICABLE: &'static str = "rustfix-only-machine-applicable";
213 pub const ASSEMBLY_OUTPUT: &'static str = "assembly-output";
214 pub const STDERR_PER_BITWIDTH: &'static str = "stderr-per-bitwidth";
215 pub const INCREMENTAL: &'static str = "incremental";
216 pub const KNOWN_BUG: &'static str = "known-bug";
217 pub const MIR_UNIT_TEST: &'static str = "unit-test";
218 pub const REMAP_SRC_BASE: &'static str = "remap-src-base";
219 pub const COMPARE_OUTPUT_LINES_BY_SUBSET: &'static str = "compare-output-lines-by-subset";
220 // This isn't a real directive, just one that is probably mistyped often
221 pub const INCORRECT_COMPILER_FLAGS: &'static str = "compiler-flags";
222 }
223
224 impl TestProps {
new() -> Self225 pub fn new() -> Self {
226 TestProps {
227 error_patterns: vec![],
228 regex_error_patterns: vec![],
229 compile_flags: vec![],
230 run_flags: None,
231 pp_exact: None,
232 aux_builds: vec![],
233 aux_crates: vec![],
234 revisions: vec![],
235 rustc_env: vec![],
236 unset_rustc_env: vec![],
237 exec_env: vec![],
238 unset_exec_env: vec![],
239 build_aux_docs: false,
240 force_host: false,
241 check_stdout: false,
242 check_run_results: false,
243 dont_check_compiler_stdout: false,
244 dont_check_compiler_stderr: false,
245 compare_output_lines_by_subset: false,
246 no_prefer_dynamic: false,
247 pretty_expanded: false,
248 pretty_mode: "normal".to_string(),
249 pretty_compare_only: false,
250 forbid_output: vec![],
251 incremental_dir: None,
252 incremental: false,
253 known_bug: false,
254 pass_mode: None,
255 fail_mode: None,
256 ignore_pass: false,
257 check_test_line_numbers_match: false,
258 normalize_stdout: vec![],
259 normalize_stderr: vec![],
260 failure_status: None,
261 dont_check_failure_status: false,
262 run_rustfix: false,
263 rustfix_only_machine_applicable: false,
264 assembly_output: None,
265 should_ice: false,
266 stderr_per_bitwidth: false,
267 mir_unit_test: None,
268 remap_src_base: false,
269 }
270 }
271
from_aux_file(&self, testfile: &Path, cfg: Option<&str>, config: &Config) -> Self272 pub fn from_aux_file(&self, testfile: &Path, cfg: Option<&str>, config: &Config) -> Self {
273 let mut props = TestProps::new();
274
275 // copy over select properties to the aux build:
276 props.incremental_dir = self.incremental_dir.clone();
277 props.ignore_pass = true;
278 props.load_from(testfile, cfg, config);
279
280 props
281 }
282
from_file(testfile: &Path, cfg: Option<&str>, config: &Config) -> Self283 pub fn from_file(testfile: &Path, cfg: Option<&str>, config: &Config) -> Self {
284 let mut props = TestProps::new();
285 props.load_from(testfile, cfg, config);
286
287 match (props.pass_mode, props.fail_mode) {
288 (None, None) if config.mode == Mode::Ui => props.fail_mode = Some(FailMode::Check),
289 (Some(_), Some(_)) => panic!("cannot use a *-fail and *-pass mode together"),
290 _ => {}
291 }
292
293 props
294 }
295
296 /// Loads properties from `testfile` into `props`. If a property is
297 /// tied to a particular revision `foo` (indicated by writing
298 /// `//[foo]`), then the property is ignored unless `cfg` is
299 /// `Some("foo")`.
load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config)300 fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) {
301 // In CI, we've sometimes encountered non-determinism related to truncating very long paths.
302 // Set a consistent (short) prefix to avoid issues, but only in CI to avoid regressing the
303 // contributor experience.
304 if CiEnv::is_ci() {
305 self.remap_src_base = config.mode == Mode::Ui && !config.suite.contains("rustdoc");
306 }
307
308 let mut has_edition = false;
309 if !testfile.is_dir() {
310 let file = File::open(testfile).unwrap();
311
312 iter_header(testfile, file, &mut |revision, ln, _| {
313 if revision.is_some() && revision != cfg {
314 return;
315 }
316
317 use directives::*;
318
319 config.push_name_value_directive(
320 ln,
321 ERROR_PATTERN,
322 &mut self.error_patterns,
323 |r| r,
324 );
325 config.push_name_value_directive(
326 ln,
327 REGEX_ERROR_PATTERN,
328 &mut self.regex_error_patterns,
329 |r| r,
330 );
331
332 if let Some(flags) = config.parse_name_value_directive(ln, COMPILE_FLAGS) {
333 self.compile_flags.extend(flags.split_whitespace().map(|s| s.to_owned()));
334 }
335 if config.parse_name_value_directive(ln, INCORRECT_COMPILER_FLAGS).is_some() {
336 panic!("`compiler-flags` directive should be spelled `compile-flags`");
337 }
338
339 if let Some(edition) = config.parse_edition(ln) {
340 self.compile_flags.push(format!("--edition={}", edition.trim()));
341 has_edition = true;
342 }
343
344 config.parse_and_update_revisions(ln, &mut self.revisions);
345
346 config.set_name_value_directive(ln, RUN_FLAGS, &mut self.run_flags, |r| r);
347
348 if self.pp_exact.is_none() {
349 self.pp_exact = config.parse_pp_exact(ln, testfile);
350 }
351
352 config.set_name_directive(ln, SHOULD_ICE, &mut self.should_ice);
353 config.set_name_directive(ln, BUILD_AUX_DOCS, &mut self.build_aux_docs);
354 config.set_name_directive(ln, FORCE_HOST, &mut self.force_host);
355 config.set_name_directive(ln, CHECK_STDOUT, &mut self.check_stdout);
356 config.set_name_directive(ln, CHECK_RUN_RESULTS, &mut self.check_run_results);
357 config.set_name_directive(
358 ln,
359 DONT_CHECK_COMPILER_STDOUT,
360 &mut self.dont_check_compiler_stdout,
361 );
362 config.set_name_directive(
363 ln,
364 DONT_CHECK_COMPILER_STDERR,
365 &mut self.dont_check_compiler_stderr,
366 );
367 config.set_name_directive(ln, NO_PREFER_DYNAMIC, &mut self.no_prefer_dynamic);
368 config.set_name_directive(ln, PRETTY_EXPANDED, &mut self.pretty_expanded);
369
370 if let Some(m) = config.parse_name_value_directive(ln, PRETTY_MODE) {
371 self.pretty_mode = m;
372 }
373
374 config.set_name_directive(ln, PRETTY_COMPARE_ONLY, &mut self.pretty_compare_only);
375 config.push_name_value_directive(ln, AUX_BUILD, &mut self.aux_builds, |r| {
376 r.trim().to_string()
377 });
378 config.push_name_value_directive(
379 ln,
380 AUX_CRATE,
381 &mut self.aux_crates,
382 Config::parse_aux_crate,
383 );
384 config.push_name_value_directive(
385 ln,
386 EXEC_ENV,
387 &mut self.exec_env,
388 Config::parse_env,
389 );
390 config.push_name_value_directive(
391 ln,
392 UNSET_EXEC_ENV,
393 &mut self.unset_exec_env,
394 |r| r,
395 );
396 config.push_name_value_directive(
397 ln,
398 RUSTC_ENV,
399 &mut self.rustc_env,
400 Config::parse_env,
401 );
402 config.push_name_value_directive(
403 ln,
404 UNSET_RUSTC_ENV,
405 &mut self.unset_rustc_env,
406 |r| r,
407 );
408 config.push_name_value_directive(ln, FORBID_OUTPUT, &mut self.forbid_output, |r| r);
409 config.set_name_directive(
410 ln,
411 CHECK_TEST_LINE_NUMBERS_MATCH,
412 &mut self.check_test_line_numbers_match,
413 );
414
415 self.update_pass_mode(ln, cfg, config);
416 self.update_fail_mode(ln, config);
417
418 config.set_name_directive(ln, IGNORE_PASS, &mut self.ignore_pass);
419
420 if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stdout") {
421 self.normalize_stdout.push(rule);
422 }
423 if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stderr") {
424 self.normalize_stderr.push(rule);
425 }
426
427 if let Some(code) = config
428 .parse_name_value_directive(ln, FAILURE_STATUS)
429 .and_then(|code| code.trim().parse::<i32>().ok())
430 {
431 self.failure_status = Some(code);
432 }
433
434 config.set_name_directive(
435 ln,
436 DONT_CHECK_FAILURE_STATUS,
437 &mut self.dont_check_failure_status,
438 );
439
440 config.set_name_directive(ln, RUN_RUSTFIX, &mut self.run_rustfix);
441 config.set_name_directive(
442 ln,
443 RUSTFIX_ONLY_MACHINE_APPLICABLE,
444 &mut self.rustfix_only_machine_applicable,
445 );
446 config.set_name_value_directive(
447 ln,
448 ASSEMBLY_OUTPUT,
449 &mut self.assembly_output,
450 |r| r.trim().to_string(),
451 );
452 config.set_name_directive(ln, STDERR_PER_BITWIDTH, &mut self.stderr_per_bitwidth);
453 config.set_name_directive(ln, INCREMENTAL, &mut self.incremental);
454
455 // Unlike the other `name_value_directive`s this needs to be handled manually,
456 // because it sets a `bool` flag.
457 if let Some(known_bug) = config.parse_name_value_directive(ln, KNOWN_BUG) {
458 let known_bug = known_bug.trim();
459 if known_bug == "unknown"
460 || known_bug.split(',').all(|issue_ref| {
461 issue_ref
462 .trim()
463 .split_once('#')
464 .filter(|(_, number)| {
465 number.chars().all(|digit| digit.is_numeric())
466 })
467 .is_some()
468 })
469 {
470 self.known_bug = true;
471 } else {
472 panic!(
473 "Invalid known-bug value: {known_bug}\nIt requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
474 );
475 }
476 } else if config.parse_name_directive(ln, KNOWN_BUG) {
477 panic!(
478 "Invalid known-bug attribute, requires comma-separated issue references (`#000` or `chalk#000`) or `known-bug: unknown`."
479 );
480 }
481
482 config.set_name_value_directive(ln, MIR_UNIT_TEST, &mut self.mir_unit_test, |s| {
483 s.trim().to_string()
484 });
485 config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base);
486 config.set_name_directive(
487 ln,
488 COMPARE_OUTPUT_LINES_BY_SUBSET,
489 &mut self.compare_output_lines_by_subset,
490 );
491 });
492 }
493
494 if self.should_ice {
495 self.failure_status = Some(101);
496 }
497
498 if config.mode == Mode::Incremental {
499 self.incremental = true;
500 }
501
502 for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
503 if let Ok(val) = env::var(key) {
504 if self.exec_env.iter().find(|&&(ref x, _)| x == key).is_none() {
505 self.exec_env.push(((*key).to_owned(), val))
506 }
507 }
508 }
509
510 if let (Some(edition), false) = (&config.edition, has_edition) {
511 self.compile_flags.push(format!("--edition={}", edition));
512 }
513 }
514
update_fail_mode(&mut self, ln: &str, config: &Config)515 fn update_fail_mode(&mut self, ln: &str, config: &Config) {
516 let check_ui = |mode: &str| {
517 if config.mode != Mode::Ui {
518 panic!("`{}-fail` header is only supported in UI tests", mode);
519 }
520 };
521 if config.mode == Mode::Ui && config.parse_name_directive(ln, "compile-fail") {
522 panic!("`compile-fail` header is useless in UI tests");
523 }
524 let fail_mode = if config.parse_name_directive(ln, "check-fail") {
525 check_ui("check");
526 Some(FailMode::Check)
527 } else if config.parse_name_directive(ln, "build-fail") {
528 check_ui("build");
529 Some(FailMode::Build)
530 } else if config.parse_name_directive(ln, "run-fail") {
531 check_ui("run");
532 Some(FailMode::Run)
533 } else {
534 None
535 };
536 match (self.fail_mode, fail_mode) {
537 (None, Some(_)) => self.fail_mode = fail_mode,
538 (Some(_), Some(_)) => panic!("multiple `*-fail` headers in a single test"),
539 (_, None) => {}
540 }
541 }
542
update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config)543 fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) {
544 let check_no_run = |s| {
545 if config.mode != Mode::Ui && config.mode != Mode::Incremental {
546 panic!("`{}` header is only supported in UI and incremental tests", s);
547 }
548 if config.mode == Mode::Incremental
549 && !revision.map_or(false, |r| r.starts_with("cfail"))
550 && !self.revisions.iter().all(|r| r.starts_with("cfail"))
551 {
552 panic!("`{}` header is only supported in `cfail` incremental tests", s);
553 }
554 };
555 let pass_mode = if config.parse_name_directive(ln, "check-pass") {
556 check_no_run("check-pass");
557 Some(PassMode::Check)
558 } else if config.parse_name_directive(ln, "build-pass") {
559 check_no_run("build-pass");
560 Some(PassMode::Build)
561 } else if config.parse_name_directive(ln, "run-pass") {
562 if config.mode != Mode::Ui {
563 panic!("`run-pass` header is only supported in UI tests")
564 }
565 Some(PassMode::Run)
566 } else {
567 None
568 };
569 match (self.pass_mode, pass_mode) {
570 (None, Some(_)) => self.pass_mode = pass_mode,
571 (Some(_), Some(_)) => panic!("multiple `*-pass` headers in a single test"),
572 (_, None) => {}
573 }
574 }
575
pass_mode(&self, config: &Config) -> Option<PassMode>576 pub fn pass_mode(&self, config: &Config) -> Option<PassMode> {
577 if !self.ignore_pass && self.fail_mode.is_none() {
578 if let mode @ Some(_) = config.force_pass_mode {
579 return mode;
580 }
581 }
582 self.pass_mode
583 }
584
585 // does not consider CLI override for pass mode
local_pass_mode(&self) -> Option<PassMode>586 pub fn local_pass_mode(&self) -> Option<PassMode> {
587 self.pass_mode
588 }
589 }
590
line_directive<'line>( comment: &str, ln: &'line str, ) -> Option<(Option<&'line str>, &'line str)>591 pub fn line_directive<'line>(
592 comment: &str,
593 ln: &'line str,
594 ) -> Option<(Option<&'line str>, &'line str)> {
595 if ln.starts_with(comment) {
596 let ln = ln[comment.len()..].trim_start();
597 if ln.starts_with('[') {
598 // A comment like `//[foo]` is specific to revision `foo`
599 if let Some(close_brace) = ln.find(']') {
600 let lncfg = &ln[1..close_brace];
601
602 Some((Some(lncfg), ln[(close_brace + 1)..].trim_start()))
603 } else {
604 panic!("malformed condition directive: expected `{}[foo]`, found `{}`", comment, ln)
605 }
606 } else {
607 Some((None, ln))
608 }
609 } else {
610 None
611 }
612 }
613
iter_header<R: Read>(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str, usize))614 fn iter_header<R: Read>(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str, usize)) {
615 iter_header_extra(testfile, rdr, &[], it)
616 }
617
iter_header_extra( testfile: &Path, rdr: impl Read, extra_directives: &[&str], it: &mut dyn FnMut(Option<&str>, &str, usize), )618 fn iter_header_extra(
619 testfile: &Path,
620 rdr: impl Read,
621 extra_directives: &[&str],
622 it: &mut dyn FnMut(Option<&str>, &str, usize),
623 ) {
624 if testfile.is_dir() {
625 return;
626 }
627
628 // Process any extra directives supplied by the caller (e.g. because they
629 // are implied by the test mode), with a dummy line number of 0.
630 for directive in extra_directives {
631 it(None, directive, 0);
632 }
633
634 let comment = if testfile.extension().map(|e| e == "rs") == Some(true) { "//" } else { "#" };
635
636 let mut rdr = BufReader::new(rdr);
637 let mut ln = String::new();
638 let mut line_number = 0;
639
640 loop {
641 line_number += 1;
642 ln.clear();
643 if rdr.read_line(&mut ln).unwrap() == 0 {
644 break;
645 }
646
647 // Assume that any directives will be found before the first
648 // module or function. This doesn't seem to be an optimization
649 // with a warm page cache. Maybe with a cold one.
650 let ln = ln.trim();
651 if ln.starts_with("fn") || ln.starts_with("mod") {
652 return;
653 } else if let Some((lncfg, ln)) = line_directive(comment, ln) {
654 it(lncfg, ln, line_number);
655 }
656 }
657 }
658
659 impl Config {
parse_aux_crate(r: String) -> (String, String)660 fn parse_aux_crate(r: String) -> (String, String) {
661 let mut parts = r.trim().splitn(2, '=');
662 (
663 parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(),
664 parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(),
665 )
666 }
667
parse_and_update_revisions(&self, line: &str, existing: &mut Vec<String>)668 fn parse_and_update_revisions(&self, line: &str, existing: &mut Vec<String>) {
669 if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
670 let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
671 for revision in raw.split_whitespace().map(|r| r.to_string()) {
672 if !duplicates.insert(revision.clone()) {
673 panic!("Duplicate revision: `{}` in line `{}`", revision, raw);
674 }
675 existing.push(revision);
676 }
677 }
678 }
679
parse_env(nv: String) -> (String, String)680 fn parse_env(nv: String) -> (String, String) {
681 // nv is either FOO or FOO=BAR
682 let mut strs: Vec<String> = nv.splitn(2, '=').map(str::to_owned).collect();
683
684 match strs.len() {
685 1 => (strs.pop().unwrap(), String::new()),
686 2 => {
687 let end = strs.pop().unwrap();
688 (strs.pop().unwrap(), end)
689 }
690 n => panic!("Expected 1 or 2 strings, not {}", n),
691 }
692 }
693
parse_pp_exact(&self, line: &str, testfile: &Path) -> Option<PathBuf>694 fn parse_pp_exact(&self, line: &str, testfile: &Path) -> Option<PathBuf> {
695 if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
696 Some(PathBuf::from(&s))
697 } else if self.parse_name_directive(line, "pp-exact") {
698 testfile.file_name().map(PathBuf::from)
699 } else {
700 None
701 }
702 }
703
parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)>704 fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)> {
705 if parse_cfg_name_directive(self, line, prefix).outcome == MatchOutcome::Match {
706 let from = parse_normalization_string(&mut line)?;
707 let to = parse_normalization_string(&mut line)?;
708 Some((from, to))
709 } else {
710 None
711 }
712 }
713
parse_name_directive(&self, line: &str, directive: &str) -> bool714 fn parse_name_directive(&self, line: &str, directive: &str) -> bool {
715 // Ensure the directive is a whole word. Do not match "ignore-x86" when
716 // the line says "ignore-x86_64".
717 line.starts_with(directive)
718 && matches!(line.as_bytes().get(directive.len()), None | Some(&b' ') | Some(&b':'))
719 }
720
parse_negative_name_directive(&self, line: &str, directive: &str) -> bool721 fn parse_negative_name_directive(&self, line: &str, directive: &str) -> bool {
722 line.starts_with("no-") && self.parse_name_directive(&line[3..], directive)
723 }
724
parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String>725 pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
726 let colon = directive.len();
727 if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
728 let value = line[(colon + 1)..].to_owned();
729 debug!("{}: {}", directive, value);
730 Some(expand_variables(value, self))
731 } else {
732 None
733 }
734 }
735
find_rust_src_root(&self) -> Option<PathBuf>736 pub fn find_rust_src_root(&self) -> Option<PathBuf> {
737 let mut path = self.src_base.clone();
738 let path_postfix = Path::new("src/etc/lldb_batchmode.py");
739
740 while path.pop() {
741 if path.join(&path_postfix).is_file() {
742 return Some(path);
743 }
744 }
745
746 None
747 }
748
parse_edition(&self, line: &str) -> Option<String>749 fn parse_edition(&self, line: &str) -> Option<String> {
750 self.parse_name_value_directive(line, "edition")
751 }
752
set_name_directive(&self, line: &str, directive: &str, value: &mut bool)753 fn set_name_directive(&self, line: &str, directive: &str, value: &mut bool) {
754 match value {
755 true => {
756 if self.parse_negative_name_directive(line, directive) {
757 *value = false;
758 }
759 }
760 false => {
761 if self.parse_name_directive(line, directive) {
762 *value = true;
763 }
764 }
765 }
766 }
767
set_name_value_directive<T>( &self, line: &str, directive: &str, value: &mut Option<T>, parse: impl FnOnce(String) -> T, )768 fn set_name_value_directive<T>(
769 &self,
770 line: &str,
771 directive: &str,
772 value: &mut Option<T>,
773 parse: impl FnOnce(String) -> T,
774 ) {
775 if value.is_none() {
776 *value = self.parse_name_value_directive(line, directive).map(parse);
777 }
778 }
779
push_name_value_directive<T>( &self, line: &str, directive: &str, values: &mut Vec<T>, parse: impl FnOnce(String) -> T, )780 fn push_name_value_directive<T>(
781 &self,
782 line: &str,
783 directive: &str,
784 values: &mut Vec<T>,
785 parse: impl FnOnce(String) -> T,
786 ) {
787 if let Some(value) = self.parse_name_value_directive(line, directive).map(parse) {
788 values.push(value);
789 }
790 }
791 }
792
expand_variables(mut value: String, config: &Config) -> String793 fn expand_variables(mut value: String, config: &Config) -> String {
794 const CWD: &str = "{{cwd}}";
795 const SRC_BASE: &str = "{{src-base}}";
796 const BUILD_BASE: &str = "{{build-base}}";
797
798 if value.contains(CWD) {
799 let cwd = env::current_dir().unwrap();
800 value = value.replace(CWD, &cwd.to_string_lossy());
801 }
802
803 if value.contains(SRC_BASE) {
804 value = value.replace(SRC_BASE, &config.src_base.to_string_lossy());
805 }
806
807 if value.contains(BUILD_BASE) {
808 value = value.replace(BUILD_BASE, &config.build_base.to_string_lossy());
809 }
810
811 value
812 }
813
814 /// Finds the next quoted string `"..."` in `line`, and extract the content from it. Move the `line`
815 /// variable after the end of the quoted string.
816 ///
817 /// # Examples
818 ///
819 /// ```
820 /// let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)\".";
821 /// let first = parse_normalization_string(&mut s);
822 /// assert_eq!(first, Some("something (32 bits)".to_owned()));
823 /// assert_eq!(s, " -> \"something ($WORD bits)\".");
824 /// ```
parse_normalization_string(line: &mut &str) -> Option<String>825 fn parse_normalization_string(line: &mut &str) -> Option<String> {
826 // FIXME support escapes in strings.
827 let begin = line.find('"')? + 1;
828 let end = line[begin..].find('"')? + begin;
829 let result = line[begin..end].to_owned();
830 *line = &line[end + 1..];
831 Some(result)
832 }
833
extract_llvm_version(version: &str) -> Option<u32>834 pub fn extract_llvm_version(version: &str) -> Option<u32> {
835 let pat = |c: char| !c.is_ascii_digit() && c != '.';
836 let version_without_suffix = match version.find(pat) {
837 Some(pos) => &version[..pos],
838 None => version,
839 };
840 let components: Vec<u32> = version_without_suffix
841 .split('.')
842 .map(|s| s.parse().expect("Malformed version component"))
843 .collect();
844 let version = match *components {
845 [a] => a * 10_000,
846 [a, b] => a * 10_000 + b * 100,
847 [a, b, c] => a * 10_000 + b * 100 + c,
848 _ => panic!("Malformed version"),
849 };
850 Some(version)
851 }
852
extract_llvm_version_from_binary(binary_path: &str) -> Option<u32>853 pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option<u32> {
854 let output = Command::new(binary_path).arg("--version").output().ok()?;
855 if !output.status.success() {
856 return None;
857 }
858 let version = String::from_utf8(output.stdout).ok()?;
859 for line in version.lines() {
860 if let Some(version) = line.split("LLVM version ").skip(1).next() {
861 return extract_llvm_version(version);
862 }
863 }
864 None
865 }
866
867 /// Takes a directive of the form "<version1> [- <version2>]",
868 /// returns the numeric representation of <version1> and <version2> as
869 /// tuple: (<version1> as u32, <version2> as u32)
870 ///
871 /// If the <version2> part is omitted, the second component of the tuple
872 /// is the same as <version1>.
extract_version_range<F>(line: &str, parse: F) -> Option<(u32, u32)> where F: Fn(&str) -> Option<u32>,873 fn extract_version_range<F>(line: &str, parse: F) -> Option<(u32, u32)>
874 where
875 F: Fn(&str) -> Option<u32>,
876 {
877 let mut splits = line.splitn(2, "- ").map(str::trim);
878 let min = splits.next().unwrap();
879 if min.ends_with('-') {
880 return None;
881 }
882
883 let max = splits.next();
884
885 if min.is_empty() {
886 return None;
887 }
888
889 let min = parse(min)?;
890 let max = match max {
891 Some(max) if max.is_empty() => return None,
892 Some(max) => parse(max)?,
893 _ => min,
894 };
895
896 Some((min, max))
897 }
898
make_test_description<R: Read>( config: &Config, cache: &HeadersCache, name: test::TestName, path: &Path, src: R, cfg: Option<&str>, poisoned: &mut bool, ) -> test::TestDesc899 pub fn make_test_description<R: Read>(
900 config: &Config,
901 cache: &HeadersCache,
902 name: test::TestName,
903 path: &Path,
904 src: R,
905 cfg: Option<&str>,
906 poisoned: &mut bool,
907 ) -> test::TestDesc {
908 let mut ignore = false;
909 let mut ignore_message = None;
910 let mut should_fail = false;
911
912 let extra_directives: &[&str] = match config.mode {
913 // The run-coverage tests are treated as having these extra directives,
914 // without needing to specify them manually in every test file.
915 // (Some of the comments below have been copied over from
916 // `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
917 Mode::RunCoverage => {
918 &[
919 "needs-profiler-support",
920 // FIXME(mati865): MinGW GCC miscompiles compiler-rt profiling library but with Clang it works
921 // properly. Since we only have GCC on the CI ignore the test for now.
922 "ignore-windows-gnu",
923 // FIXME(pietroalbini): this test currently does not work on cross-compiled
924 // targets because remote-test is not capable of sending back the *.profraw
925 // files generated by the LLVM instrumentation.
926 "ignore-cross-compile",
927 ]
928 }
929 _ => &[],
930 };
931
932 iter_header_extra(path, src, extra_directives, &mut |revision, ln, line_number| {
933 if revision.is_some() && revision != cfg {
934 return;
935 }
936
937 macro_rules! decision {
938 ($e:expr) => {
939 match $e {
940 IgnoreDecision::Ignore { reason } => {
941 ignore = true;
942 // The ignore reason must be a &'static str, so we have to leak memory to
943 // create it. This is fine, as the header is parsed only at the start of
944 // compiletest so it won't grow indefinitely.
945 ignore_message = Some(&*Box::leak(Box::<str>::from(reason)));
946 }
947 IgnoreDecision::Error { message } => {
948 eprintln!("error: {}:{line_number}: {message}", path.display());
949 *poisoned = true;
950 return;
951 }
952 IgnoreDecision::Continue => {}
953 }
954 };
955 }
956
957 decision!(cfg::handle_ignore(config, ln));
958 decision!(cfg::handle_only(config, ln));
959 decision!(needs::handle_needs(&cache.needs, config, ln));
960 decision!(ignore_llvm(config, ln));
961 decision!(ignore_cdb(config, ln));
962 decision!(ignore_gdb(config, ln));
963 decision!(ignore_lldb(config, ln));
964
965 if config.target == "wasm32-unknown-unknown" {
966 if config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) {
967 decision!(IgnoreDecision::Ignore {
968 reason: "ignored when checking the run results on WASM".into(),
969 });
970 }
971 }
972
973 should_fail |= config.parse_name_directive(ln, "should-fail");
974 });
975
976 // The `should-fail` annotation doesn't apply to pretty tests,
977 // since we run the pretty printer across all tests by default.
978 // If desired, we could add a `should-fail-pretty` annotation.
979 let should_panic = match config.mode {
980 crate::common::Pretty => test::ShouldPanic::No,
981 _ if should_fail => test::ShouldPanic::Yes,
982 _ => test::ShouldPanic::No,
983 };
984
985 test::TestDesc {
986 name,
987 ignore,
988 ignore_message,
989 source_file: "",
990 start_line: 0,
991 start_col: 0,
992 end_line: 0,
993 end_col: 0,
994 should_panic,
995 compile_fail: false,
996 no_run: false,
997 test_type: test::TestType::Unknown,
998 }
999 }
1000
ignore_cdb(config: &Config, line: &str) -> IgnoreDecision1001 fn ignore_cdb(config: &Config, line: &str) -> IgnoreDecision {
1002 if config.debugger != Some(Debugger::Cdb) {
1003 return IgnoreDecision::Continue;
1004 }
1005
1006 if let Some(actual_version) = config.cdb_version {
1007 if let Some(rest) = line.strip_prefix("min-cdb-version:").map(str::trim) {
1008 let min_version = extract_cdb_version(rest).unwrap_or_else(|| {
1009 panic!("couldn't parse version range: {:?}", rest);
1010 });
1011
1012 // Ignore if actual version is smaller than the minimum
1013 // required version
1014 if actual_version < min_version {
1015 return IgnoreDecision::Ignore {
1016 reason: format!("ignored when the CDB version is lower than {rest}"),
1017 };
1018 }
1019 }
1020 }
1021 IgnoreDecision::Continue
1022 }
1023
ignore_gdb(config: &Config, line: &str) -> IgnoreDecision1024 fn ignore_gdb(config: &Config, line: &str) -> IgnoreDecision {
1025 if config.debugger != Some(Debugger::Gdb) {
1026 return IgnoreDecision::Continue;
1027 }
1028
1029 if let Some(actual_version) = config.gdb_version {
1030 if let Some(rest) = line.strip_prefix("min-gdb-version:").map(str::trim) {
1031 let (start_ver, end_ver) = extract_version_range(rest, extract_gdb_version)
1032 .unwrap_or_else(|| {
1033 panic!("couldn't parse version range: {:?}", rest);
1034 });
1035
1036 if start_ver != end_ver {
1037 panic!("Expected single GDB version")
1038 }
1039 // Ignore if actual version is smaller than the minimum
1040 // required version
1041 if actual_version < start_ver {
1042 return IgnoreDecision::Ignore {
1043 reason: format!("ignored when the GDB version is lower than {rest}"),
1044 };
1045 }
1046 } else if let Some(rest) = line.strip_prefix("ignore-gdb-version:").map(str::trim) {
1047 let (min_version, max_version) = extract_version_range(rest, extract_gdb_version)
1048 .unwrap_or_else(|| {
1049 panic!("couldn't parse version range: {:?}", rest);
1050 });
1051
1052 if max_version < min_version {
1053 panic!("Malformed GDB version range: max < min")
1054 }
1055
1056 if actual_version >= min_version && actual_version <= max_version {
1057 if min_version == max_version {
1058 return IgnoreDecision::Ignore {
1059 reason: format!("ignored when the GDB version is {rest}"),
1060 };
1061 } else {
1062 return IgnoreDecision::Ignore {
1063 reason: format!("ignored when the GDB version is between {rest}"),
1064 };
1065 }
1066 }
1067 }
1068 }
1069 IgnoreDecision::Continue
1070 }
1071
ignore_lldb(config: &Config, line: &str) -> IgnoreDecision1072 fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
1073 if config.debugger != Some(Debugger::Lldb) {
1074 return IgnoreDecision::Continue;
1075 }
1076
1077 if let Some(actual_version) = config.lldb_version {
1078 if let Some(rest) = line.strip_prefix("min-lldb-version:").map(str::trim) {
1079 let min_version = rest.parse().unwrap_or_else(|e| {
1080 panic!("Unexpected format of LLDB version string: {}\n{:?}", rest, e);
1081 });
1082 // Ignore if actual version is smaller the minimum required
1083 // version
1084 if actual_version < min_version {
1085 return IgnoreDecision::Ignore {
1086 reason: format!("ignored when the LLDB version is {rest}"),
1087 };
1088 }
1089 }
1090 }
1091 IgnoreDecision::Continue
1092 }
1093
ignore_llvm(config: &Config, line: &str) -> IgnoreDecision1094 fn ignore_llvm(config: &Config, line: &str) -> IgnoreDecision {
1095 if config.system_llvm && line.starts_with("no-system-llvm") {
1096 return IgnoreDecision::Ignore { reason: "ignored when the system LLVM is used".into() };
1097 }
1098 if let Some(needed_components) =
1099 config.parse_name_value_directive(line, "needs-llvm-components")
1100 {
1101 let components: HashSet<_> = config.llvm_components.split_whitespace().collect();
1102 if let Some(missing_component) = needed_components
1103 .split_whitespace()
1104 .find(|needed_component| !components.contains(needed_component))
1105 {
1106 if env::var_os("COMPILETEST_NEEDS_ALL_LLVM_COMPONENTS").is_some() {
1107 panic!("missing LLVM component: {}", missing_component);
1108 }
1109 return IgnoreDecision::Ignore {
1110 reason: format!("ignored when the {missing_component} LLVM component is missing"),
1111 };
1112 }
1113 }
1114 if let Some(actual_version) = config.llvm_version {
1115 if let Some(rest) = line.strip_prefix("min-llvm-version:").map(str::trim) {
1116 let min_version = extract_llvm_version(rest).unwrap();
1117 // Ignore if actual version is smaller the minimum required
1118 // version
1119 if actual_version < min_version {
1120 return IgnoreDecision::Ignore {
1121 reason: format!("ignored when the LLVM version is older than {rest}"),
1122 };
1123 }
1124 } else if let Some(rest) = line.strip_prefix("min-system-llvm-version:").map(str::trim) {
1125 let min_version = extract_llvm_version(rest).unwrap();
1126 // Ignore if using system LLVM and actual version
1127 // is smaller the minimum required version
1128 if config.system_llvm && actual_version < min_version {
1129 return IgnoreDecision::Ignore {
1130 reason: format!("ignored when the system LLVM version is older than {rest}"),
1131 };
1132 }
1133 } else if let Some(rest) = line.strip_prefix("ignore-llvm-version:").map(str::trim) {
1134 // Syntax is: "ignore-llvm-version: <version1> [- <version2>]"
1135 let (v_min, v_max) =
1136 extract_version_range(rest, extract_llvm_version).unwrap_or_else(|| {
1137 panic!("couldn't parse version range: {:?}", rest);
1138 });
1139 if v_max < v_min {
1140 panic!("Malformed LLVM version range: max < min")
1141 }
1142 // Ignore if version lies inside of range.
1143 if actual_version >= v_min && actual_version <= v_max {
1144 if v_min == v_max {
1145 return IgnoreDecision::Ignore {
1146 reason: format!("ignored when the LLVM version is {rest}"),
1147 };
1148 } else {
1149 return IgnoreDecision::Ignore {
1150 reason: format!("ignored when the LLVM version is between {rest}"),
1151 };
1152 }
1153 }
1154 }
1155 }
1156 IgnoreDecision::Continue
1157 }
1158
1159 enum IgnoreDecision {
1160 Ignore { reason: String },
1161 Continue,
1162 Error { message: String },
1163 }
1164