]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/tests/compiletest.rs
:arrow_up: rust-analyzer
[rust.git] / src / tools / miri / tests / compiletest.rs
1 use colored::*;
2 use regex::Regex;
3 use std::path::{Path, PathBuf};
4 use std::{env, process::Command};
5 use ui_test::{color_eyre::Result, Config, Mode, OutputConflictHandling};
6
7 fn miri_path() -> PathBuf {
8     PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri")))
9 }
10
11 fn get_host() -> String {
12     rustc_version::VersionMeta::for_command(std::process::Command::new(miri_path()))
13         .expect("failed to parse rustc version info")
14         .host
15 }
16
17 // Build the shared object file for testing external C function calls.
18 fn build_so_for_c_ffi_tests() -> PathBuf {
19     let cc = option_env!("CC").unwrap_or("cc");
20     // Target directory that we can write to.
21     let so_target_dir = Path::new(&env::var_os("CARGO_TARGET_DIR").unwrap()).join("miri-extern-so");
22     // Create the directory if it does not already exist.
23     std::fs::create_dir_all(&so_target_dir)
24         .expect("Failed to create directory for shared object file");
25     let so_file_path = so_target_dir.join("libtestlib.so");
26     let cc_output = Command::new(cc)
27         .args([
28             "-shared",
29             "-o",
30             so_file_path.to_str().unwrap(),
31             "tests/extern-so/test.c",
32             // Only add the functions specified in libcode.version to the shared object file.
33             // This is to avoid automatically adding `malloc`, etc.
34             // Source: https://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html/
35             "-fPIC",
36             "-Wl,--version-script=tests/extern-so/libcode.version",
37         ])
38         .output()
39         .expect("failed to generate shared object file for testing external C function calls");
40     if !cc_output.status.success() {
41         panic!("error in generating shared object file for testing external C function calls");
42     }
43     so_file_path
44 }
45
46 fn run_tests(mode: Mode, path: &str, target: &str, with_dependencies: bool) -> Result<()> {
47     let mut config = Config {
48         target: Some(target.to_owned()),
49         stderr_filters: STDERR.clone(),
50         stdout_filters: STDOUT.clone(),
51         root_dir: PathBuf::from(path),
52         mode,
53         program: miri_path(),
54         quiet: false,
55         ..Config::default()
56     };
57
58     let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some();
59
60     // Add some flags we always want.
61     config.args.push("--edition".into());
62     config.args.push("2018".into());
63     if in_rustc_test_suite {
64         // Less aggressive warnings to make the rustc toolstate management less painful.
65         // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.)
66         config.args.push("-Astable-features".into());
67         config.args.push("-Aunused".into());
68     } else {
69         config.args.push("-Dwarnings".into());
70         config.args.push("-Dunused".into());
71     }
72     if let Ok(extra_flags) = env::var("MIRIFLAGS") {
73         for flag in extra_flags.split_whitespace() {
74             config.args.push(flag.into());
75         }
76     }
77     config.args.push("-Zui-testing".into());
78     if let Some(target) = &config.target {
79         config.args.push("--target".into());
80         config.args.push(target.into());
81     }
82
83     // If we're on linux, and we're testing the extern-so functionality,
84     // then build the shared object file for testing external C function calls
85     // and push the relevant compiler flag.
86     if cfg!(target_os = "linux") && path.starts_with("tests/extern-so/") {
87         let so_file_path = build_so_for_c_ffi_tests();
88         let mut flag = std::ffi::OsString::from("-Zmiri-extern-so-file=");
89         flag.push(so_file_path.into_os_string());
90         config.args.push(flag);
91     }
92
93     let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some();
94
95     config.output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) {
96         (false, false) => OutputConflictHandling::Error,
97         (true, false) => OutputConflictHandling::Bless,
98         (false, true) => OutputConflictHandling::Ignore,
99         (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"),
100     };
101
102     // Handle command-line arguments.
103     config.path_filter.extend(std::env::args().skip(1).filter(|arg| {
104         match &**arg {
105             "--quiet" => {
106                 config.quiet = true;
107                 false
108             }
109             _ => true,
110         }
111     }));
112
113     let use_std = env::var_os("MIRI_NO_STD").is_none();
114
115     if with_dependencies && use_std {
116         config.dependencies_crate_manifest_path =
117             Some(Path::new("test_dependencies").join("Cargo.toml"));
118         config.dependency_builder.args = vec![
119             "run".into(),
120             "--manifest-path".into(),
121             "cargo-miri/Cargo.toml".into(),
122             "--".into(),
123             "miri".into(),
124             "run".into(), // There is no `cargo miri build` so we just use `cargo miri run`.
125         ];
126     }
127     ui_test::run_tests(config)
128 }
129
130 macro_rules! regexes {
131     ($name:ident: $($regex:expr => $replacement:expr,)*) => {lazy_static::lazy_static! {
132         static ref $name: Vec<(Regex, &'static str)> = vec![
133             $((Regex::new($regex).unwrap(), $replacement),)*
134         ];
135     }};
136 }
137
138 regexes! {
139     STDOUT:
140     // Windows file paths
141     r"\\"                           => "/",
142     // erase Stacked Borrows tags
143     "<[0-9]+>"                      => "<TAG>",
144 }
145
146 regexes! {
147     STDERR:
148     // erase line and column info
149     r"\.rs:[0-9]+:[0-9]+(: [0-9]+:[0-9]+)?" => ".rs:LL:CC",
150     // erase alloc ids
151     "alloc[0-9]+"                    => "ALLOC",
152     // erase Stacked Borrows tags
153     "<[0-9]+>"                       => "<TAG>",
154     // erase whitespace that differs between platforms
155     r" +at (.*\.rs)"                 => " at $1",
156     // erase generics in backtraces
157     "([0-9]+: .*)::<.*>"             => "$1",
158     // erase addresses in backtraces
159     "([0-9]+: ) +0x[0-9a-f]+ - (.*)" => "$1$2",
160     // erase long hexadecimals
161     r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
162     // erase specific alignments
163     "alignment [0-9]+"               => "alignment ALIGN",
164     // erase thread caller ids
165     r"call [0-9]+"                  => "call ID",
166     // erase platform module paths
167     "sys::[a-z]+::"                  => "sys::PLATFORM::",
168     // Windows file paths
169     r"\\"                           => "/",
170     // erase Rust stdlib path
171     "[^ `]*/(rust[^/]*|checkout)/library/" => "RUSTLIB/",
172     // erase platform file paths
173     "sys/[a-z]+/"                    => "sys/PLATFORM/",
174     // erase paths into the crate registry
175     r"[^ ]*/\.?cargo/registry/.*/(.*\.rs)"  => "CARGO_REGISTRY/.../$1",
176 }
177
178 enum Dependencies {
179     WithDependencies,
180     WithoutDependencies,
181 }
182
183 use Dependencies::*;
184
185 fn ui(mode: Mode, path: &str, target: &str, with_dependencies: Dependencies) -> Result<()> {
186     let msg = format!("## Running ui tests in {path} against miri for {target}");
187     eprintln!("{}", msg.green().bold());
188
189     let with_dependencies = match with_dependencies {
190         WithDependencies => true,
191         WithoutDependencies => false,
192     };
193     run_tests(mode, path, target, with_dependencies)
194 }
195
196 fn get_target() -> String {
197     env::var("MIRI_TEST_TARGET").ok().unwrap_or_else(get_host)
198 }
199
200 fn main() -> Result<()> {
201     ui_test::color_eyre::install()?;
202     let target = get_target();
203
204     // Add a test env var to do environment communication tests.
205     env::set_var("MIRI_ENV_VAR_TEST", "0");
206     // Let the tests know where to store temp files (they might run for a different target, which can make this hard to find).
207     env::set_var("MIRI_TEMP", env::temp_dir());
208
209     ui(Mode::Pass, "tests/pass", &target, WithoutDependencies)?;
210     ui(Mode::Pass, "tests/pass-dep", &target, WithDependencies)?;
211     ui(Mode::Panic, "tests/panic", &target, WithDependencies)?;
212     ui(Mode::Fail { require_patterns: true }, "tests/fail", &target, WithDependencies)?;
213     if cfg!(target_os = "linux") {
214         ui(Mode::Pass, "tests/extern-so/pass", &target, WithoutDependencies)?;
215         ui(
216             Mode::Fail { require_patterns: true },
217             "tests/extern-so/fail",
218             &target,
219             WithoutDependencies,
220         )?;
221     }
222
223     Ok(())
224 }