compiletest_rs/
lib.rs

1// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11#![crate_type = "lib"]
12#![cfg_attr(feature = "rustc", feature(rustc_private))]
13#![cfg_attr(feature = "rustc", feature(test))]
14#![deny(unused_imports)]
15
16#[cfg(feature = "rustc")]
17extern crate rustc_driver;
18#[cfg(feature = "rustc")]
19extern crate rustc_session;
20
21#[cfg(unix)]
22extern crate libc;
23#[cfg(feature = "rustc")]
24extern crate test;
25#[cfg(not(feature = "rustc"))]
26extern crate tester as test;
27
28#[cfg(feature = "tmp")]
29extern crate tempfile;
30
31#[macro_use]
32extern crate log;
33extern crate diff;
34extern crate filetime;
35extern crate regex;
36extern crate serde_json;
37#[macro_use]
38extern crate serde_derive;
39extern crate rustfix;
40
41use crate::common::{DebugInfoGdb, DebugInfoLldb, Pretty};
42use crate::common::{Mode, TestPaths};
43use std::env;
44use std::ffi::OsString;
45use std::fs;
46use std::io;
47use std::path::{Path, PathBuf};
48
49use self::header::EarlyProps;
50
51pub mod common;
52pub mod errors;
53pub mod header;
54mod json;
55mod read2;
56pub mod runtest;
57pub mod uidiff;
58pub mod util;
59
60pub use crate::common::Config;
61
62pub fn run_tests(config: &Config) {
63    if config.target.contains("android") {
64        if let DebugInfoGdb = config.mode {
65            println!(
66                "{} debug-info test uses tcp 5039 port.\
67                     please reserve it",
68                config.target
69            );
70        }
71
72        // android debug-info test uses remote debugger
73        // so, we test 1 thread at once.
74        // also trying to isolate problems with adb_run_wrapper.sh ilooping
75        env::set_var("RUST_TEST_THREADS", "1");
76    }
77
78    if let DebugInfoLldb = config.mode {
79        // Some older versions of LLDB seem to have problems with multiple
80        // instances running in parallel, so only run one test task at a
81        // time.
82        env::set_var("RUST_TEST_TASKS", "1");
83    }
84
85    // If we want to collect rustfix coverage information,
86    // we first make sure that the coverage file does not exist.
87    // It will be created later on.
88    if config.rustfix_coverage {
89        let mut coverage_file_path = config.build_base.clone();
90        coverage_file_path.push("rustfix_missing_coverage.txt");
91        if coverage_file_path.exists() {
92            if let Err(e) = fs::remove_file(&coverage_file_path) {
93                panic!(
94                    "Could not delete {} due to {}",
95                    coverage_file_path.display(),
96                    e
97                )
98            }
99        }
100    }
101    let opts = test_opts(config);
102    let tests = make_tests(config);
103    // sadly osx needs some file descriptor limits raised for running tests in
104    // parallel (especially when we have lots and lots of child processes).
105    // For context, see #8904
106    // unsafe { raise_fd_limit::raise_fd_limit(); }
107    // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
108    // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
109    env::set_var("__COMPAT_LAYER", "RunAsInvoker");
110    let res = test::run_tests_console(&opts, tests.into_iter().collect());
111    match res {
112        Ok(true) => {}
113        Ok(false) => panic!("Some tests failed"),
114        Err(e) => {
115            println!("I/O failure during tests: {:?}", e);
116        }
117    }
118}
119
120pub fn test_opts(config: &Config) -> test::TestOpts {
121    test::TestOpts {
122        filters: config.filters.clone(),
123        filter_exact: config.filter_exact,
124        exclude_should_panic: false,
125        force_run_in_process: false,
126        run_ignored: if config.run_ignored {
127            test::RunIgnored::Yes
128        } else {
129            test::RunIgnored::No
130        },
131        format: if config.quiet {
132            test::OutputFormat::Terse
133        } else {
134            test::OutputFormat::Pretty
135        },
136        logfile: config.logfile.clone(),
137        run_tests: true,
138        bench_benchmarks: true,
139        nocapture: match env::var("RUST_TEST_NOCAPTURE") {
140            Ok(val) => &val != "0",
141            Err(_) => false,
142        },
143        color: test::AutoColor,
144        test_threads: None,
145        skip: vec![],
146        list: false,
147        options: test::Options::new(),
148        time_options: None,
149        #[cfg(feature = "rustc")]
150        shuffle: false,
151        #[cfg(feature = "rustc")]
152        shuffle_seed: None,
153        #[cfg(feature = "rustc")]
154        fail_fast: false,
155    }
156}
157
158pub fn make_tests(config: &Config) -> Vec<test::TestDescAndFn> {
159    debug!("making tests from {:?}", config.src_base.display());
160    let mut tests = Vec::new();
161    collect_tests_from_dir(
162        config,
163        &config.src_base,
164        &config.src_base,
165        &PathBuf::new(),
166        &mut tests,
167    )
168    .unwrap();
169    tests
170}
171
172fn collect_tests_from_dir(
173    config: &Config,
174    base: &Path,
175    dir: &Path,
176    relative_dir_path: &Path,
177    tests: &mut Vec<test::TestDescAndFn>,
178) -> io::Result<()> {
179    // Ignore directories that contain a file
180    // `compiletest-ignore-dir`.
181    for file in fs::read_dir(dir)? {
182        let file = file?;
183        let name = file.file_name();
184        if name == *"compiletest-ignore-dir" {
185            return Ok(());
186        }
187        if name == *"Makefile" && config.mode == Mode::RunMake {
188            let paths = TestPaths {
189                file: dir.to_path_buf(),
190                base: base.to_path_buf(),
191                relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
192            };
193            tests.push(make_test(config, &paths));
194            return Ok(());
195        }
196    }
197
198    // If we find a test foo/bar.rs, we have to build the
199    // output directory `$build/foo` so we can write
200    // `$build/foo/bar` into it. We do this *now* in this
201    // sequential loop because otherwise, if we do it in the
202    // tests themselves, they race for the privilege of
203    // creating the directories and sometimes fail randomly.
204    let build_dir = config.build_base.join(&relative_dir_path);
205    fs::create_dir_all(&build_dir).unwrap();
206
207    // Add each `.rs` file as a test, and recurse further on any
208    // subdirectories we find, except for `aux` directories.
209    let dirs = fs::read_dir(dir)?;
210    for file in dirs {
211        let file = file?;
212        let file_path = file.path();
213        let file_name = file.file_name();
214        if is_test(&file_name) {
215            debug!("found test file: {:?}", file_path.display());
216            // output directory `$build/foo` so we can write
217            // `$build/foo/bar` into it. We do this *now* in this
218            // sequential loop because otherwise, if we do it in the
219            // tests themselves, they race for the privilege of
220            // creating the directories and sometimes fail randomly.
221            let build_dir = config.build_base.join(&relative_dir_path);
222            fs::create_dir_all(&build_dir).unwrap();
223
224            let paths = TestPaths {
225                file: file_path,
226                base: base.to_path_buf(),
227                relative_dir: relative_dir_path.to_path_buf(),
228            };
229            tests.push(make_test(config, &paths))
230        } else if file_path.is_dir() {
231            let relative_file_path = relative_dir_path.join(file.file_name());
232            if &file_name == "auxiliary" {
233                // `aux` directories contain other crates used for
234                // cross-crate tests. Don't search them for tests, but
235                // do create a directory in the build dir for them,
236                // since we will dump intermediate output in there
237                // sometimes.
238                let build_dir = config.build_base.join(&relative_file_path);
239                fs::create_dir_all(&build_dir).unwrap();
240            } else {
241                debug!("found directory: {:?}", file_path.display());
242                collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?;
243            }
244        } else {
245            debug!("found other file/directory: {:?}", file_path.display());
246        }
247    }
248    Ok(())
249}
250
251pub fn is_test(file_name: &OsString) -> bool {
252    let file_name = file_name.to_str().unwrap();
253
254    if !file_name.ends_with(".rs") {
255        return false;
256    }
257
258    // `.`, `#`, and `~` are common temp-file prefixes.
259    let invalid_prefixes = &[".", "#", "~"];
260    !invalid_prefixes.iter().any(|p| file_name.starts_with(p))
261}
262
263pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn {
264    let early_props = EarlyProps::from_file(config, &testpaths.file);
265
266    // The `should-fail` annotation doesn't apply to pretty tests,
267    // since we run the pretty printer across all tests by default.
268    // If desired, we could add a `should-fail-pretty` annotation.
269    let should_panic = match config.mode {
270        Pretty => test::ShouldPanic::No,
271        _ => {
272            if early_props.should_fail {
273                test::ShouldPanic::Yes
274            } else {
275                test::ShouldPanic::No
276            }
277        }
278    };
279
280    test::TestDescAndFn {
281        desc: test::TestDesc {
282            name: make_test_name(config, testpaths),
283            ignore: early_props.ignore,
284            should_panic: should_panic,
285            #[cfg(not(feature = "rustc"))]
286            allow_fail: false,
287            #[cfg(feature = "rustc")]
288            compile_fail: false,
289            #[cfg(feature = "rustc")]
290            no_run: false,
291            test_type: test::TestType::IntegrationTest,
292            #[cfg(feature = "rustc")]
293            ignore_message: None,
294            #[cfg(feature = "rustc")]
295            source_file: "",
296            #[cfg(feature = "rustc")]
297            start_line: 0,
298            #[cfg(feature = "rustc")]
299            start_col: 0,
300            #[cfg(feature = "rustc")]
301            end_line: 0,
302            #[cfg(feature = "rustc")]
303            end_col: 0,
304        },
305        testfn: make_test_closure(config, testpaths),
306    }
307}
308
309fn stamp(config: &Config, testpaths: &TestPaths) -> PathBuf {
310    let stamp_name = format!(
311        "{}-{}.stamp",
312        testpaths.file.file_name().unwrap().to_str().unwrap(),
313        config.stage_id
314    );
315    config
316        .build_base
317        .canonicalize()
318        .unwrap_or_else(|_| config.build_base.clone())
319        .join(stamp_name)
320}
321
322pub fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName {
323    // Convert a complete path to something like
324    //
325    //    run-pass/foo/bar/baz.rs
326    let path = PathBuf::from(config.src_base.file_name().unwrap())
327        .join(&testpaths.relative_dir)
328        .join(&testpaths.file.file_name().unwrap());
329    test::DynTestName(format!("[{}] {}", config.mode, path.display()))
330}
331
332pub fn make_test_closure(config: &Config, testpaths: &TestPaths) -> test::TestFn {
333    let config = config.clone();
334    let testpaths = testpaths.clone();
335    test::DynTestFn(Box::new(move || {
336        let result = runtest::run(config, &testpaths);
337        #[cfg(feature = "rustc")]
338        let result = Ok(result);
339        result
340    }))
341}
342
343fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
344    let full_version_line = full_version_line.trim();
345
346    // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
347    // of the ? sections being optional
348
349    // We will parse up to 3 digits for minor and patch, ignoring the date
350    // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version
351
352    // don't start parsing in the middle of a number
353    let mut prev_was_digit = false;
354    for (pos, c) in full_version_line.char_indices() {
355        if prev_was_digit || !c.is_digit(10) {
356            prev_was_digit = c.is_digit(10);
357            continue;
358        }
359
360        prev_was_digit = true;
361
362        let line = &full_version_line[pos..];
363
364        let next_split = match line.find(|c: char| !c.is_digit(10)) {
365            Some(idx) => idx,
366            None => continue, // no minor version
367        };
368
369        if line.as_bytes()[next_split] != b'.' {
370            continue; // no minor version
371        }
372
373        let major = &line[..next_split];
374        let line = &line[next_split + 1..];
375
376        let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) {
377            Some(idx) => {
378                if line.as_bytes()[idx] == b'.' {
379                    let patch = &line[idx + 1..];
380
381                    let patch_len = patch
382                        .find(|c: char| !c.is_digit(10))
383                        .unwrap_or_else(|| patch.len());
384                    let patch = &patch[..patch_len];
385                    let patch = if patch_len > 3 || patch_len == 0 {
386                        None
387                    } else {
388                        Some(patch)
389                    };
390
391                    (&line[..idx], patch)
392                } else {
393                    (&line[..idx], None)
394                }
395            }
396            None => (line, None),
397        };
398
399        if major.len() != 1 || minor.is_empty() {
400            continue;
401        }
402
403        let major: u32 = major.parse().unwrap();
404        let minor: u32 = minor.parse().unwrap();
405        let patch: u32 = patch.unwrap_or("0").parse().unwrap();
406
407        return Some(((major * 1000) + minor) * 1000 + patch);
408    }
409
410    None
411}
412
413#[allow(dead_code)]
414fn extract_lldb_version(full_version_line: Option<String>) -> Option<String> {
415    // Extract the major LLDB version from the given version string.
416    // LLDB version strings are different for Apple and non-Apple platforms.
417    // At the moment, this function only supports the Apple variant, which looks
418    // like this:
419    //
420    // LLDB-179.5 (older versions)
421    // lldb-300.2.51 (new versions)
422    //
423    // We are only interested in the major version number, so this function
424    // will return `Some("179")` and `Some("300")` respectively.
425
426    if let Some(ref full_version_line) = full_version_line {
427        if !full_version_line.trim().is_empty() {
428            let full_version_line = full_version_line.trim();
429
430            for (pos, l) in full_version_line.char_indices() {
431                if l != 'l' && l != 'L' {
432                    continue;
433                }
434                if pos + 5 >= full_version_line.len() {
435                    continue;
436                }
437                let l = full_version_line[pos + 1..].chars().next().unwrap();
438                if l != 'l' && l != 'L' {
439                    continue;
440                }
441                let d = full_version_line[pos + 2..].chars().next().unwrap();
442                if d != 'd' && d != 'D' {
443                    continue;
444                }
445                let b = full_version_line[pos + 3..].chars().next().unwrap();
446                if b != 'b' && b != 'B' {
447                    continue;
448                }
449                let dash = full_version_line[pos + 4..].chars().next().unwrap();
450                if dash != '-' {
451                    continue;
452                }
453
454                let vers = full_version_line[pos + 5..]
455                    .chars()
456                    .take_while(|c| c.is_digit(10))
457                    .collect::<String>();
458                if !vers.is_empty() {
459                    return Some(vers);
460                }
461            }
462            println!(
463                "Could not extract LLDB version from line '{}'",
464                full_version_line
465            );
466        }
467    }
468    None
469}
470
471#[allow(dead_code)]
472fn is_blacklisted_lldb_version(version: &str) -> bool {
473    version == "350"
474}