tidy/
target_specific_tests.rs

1//! Tidy check to ensure that all target specific tests (those that require a `--target` flag)
2//! also require the pre-requisite LLVM components to run.
3
4use std::collections::BTreeMap;
5use std::path::Path;
6
7use crate::iter_header::{HeaderLine, iter_header};
8use crate::walk::filter_not_rust;
9
10const LLVM_COMPONENTS_HEADER: &str = "needs-llvm-components:";
11const COMPILE_FLAGS_HEADER: &str = "compile-flags:";
12
13#[derive(Default, Debug)]
14struct RevisionInfo<'a> {
15    target_arch: Option<Option<&'a str>>,
16    llvm_components: Option<Vec<&'a str>>,
17}
18
19pub fn check(tests_path: &Path, bad: &mut bool) {
20    crate::walk::walk(tests_path, |path, _is_dir| filter_not_rust(path), &mut |entry, content| {
21        if content.contains("// ignore-tidy-target-specific-tests") {
22            return;
23        }
24
25        let file = entry.path().display();
26        let mut header_map = BTreeMap::new();
27        iter_header(content, &mut |HeaderLine { revision, directive, .. }| {
28            if let Some(value) = directive.strip_prefix(LLVM_COMPONENTS_HEADER) {
29                let info = header_map.entry(revision).or_insert(RevisionInfo::default());
30                let comp_vec = info.llvm_components.get_or_insert(Vec::new());
31                for component in value.split(' ') {
32                    let component = component.trim();
33                    if !component.is_empty() {
34                        comp_vec.push(component);
35                    }
36                }
37            } else if let Some(compile_flags) = directive.strip_prefix(COMPILE_FLAGS_HEADER)
38                && let Some((_, v)) = compile_flags.split_once("--target")
39            {
40                let v = v.trim_start_matches([' ', '=']);
41                let info = header_map.entry(revision).or_insert(RevisionInfo::default());
42                if v.starts_with("{{") {
43                    info.target_arch.replace(None);
44                } else if let Some((arch, _)) = v.split_once("-") {
45                    info.target_arch.replace(Some(arch));
46                } else {
47                    eprintln!("{file}: seems to have a malformed --target value");
48                    *bad = true;
49                }
50            }
51        });
52
53        // Skip run-make tests as revisions are not supported.
54        if entry.path().strip_prefix(tests_path).is_ok_and(|rest| rest.starts_with("run-make")) {
55            return;
56        }
57
58        for (rev, RevisionInfo { target_arch, llvm_components }) in &header_map {
59            let rev = rev.unwrap_or("[unspecified]");
60            match (target_arch, llvm_components) {
61                (None, None) => {}
62                (Some(target_arch), None) => {
63                    let llvm_component =
64                        target_arch.map_or_else(|| "<arch>".to_string(), arch_to_llvm_component);
65                    eprintln!(
66                        "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
67                    );
68                    *bad = true;
69                }
70                (None, Some(_)) => {
71                    eprintln!(
72                        "{file}: revision {rev} should not specify `{LLVM_COMPONENTS_HEADER}` as it doesn't need `--target`"
73                    );
74                    *bad = true;
75                }
76                (Some(target_arch), Some(llvm_components)) => {
77                    if let Some(target_arch) = target_arch {
78                        let llvm_component = arch_to_llvm_component(target_arch);
79                        if !llvm_components.contains(&llvm_component.as_str()) {
80                            eprintln!(
81                                "{file}: revision {rev} should specify `{LLVM_COMPONENTS_HEADER} {llvm_component}` as it has `--target` set"
82                            );
83                            *bad = true;
84                        }
85                    }
86                }
87            }
88        }
89    });
90}
91
92fn arch_to_llvm_component(arch: &str) -> String {
93    // NOTE: This is an *approximate* mapping of Rust's `--target` architecture to LLVM component
94    // names. It is not intended to be an authoritative source, but rather a best-effort that's good
95    // enough for the purpose of this tidy check.
96    match arch {
97        "amdgcn" => "amdgpu".into(),
98        "aarch64_be" | "arm64_32" | "arm64e" | "arm64ec" => "aarch64".into(),
99        "i386" | "i586" | "i686" | "x86" | "x86_64" | "x86_64h" => "x86".into(),
100        "loongarch32" | "loongarch64" => "loongarch".into(),
101        "nvptx64" => "nvptx".into(),
102        "s390x" => "systemz".into(),
103        "sparc64" | "sparcv9" => "sparc".into(),
104        "wasm32" | "wasm32v1" | "wasm64" => "webassembly".into(),
105        _ if arch.starts_with("armeb")
106            || arch.starts_with("armv")
107            || arch.starts_with("thumbv") =>
108        {
109            "arm".into()
110        }
111        _ if arch.starts_with("bpfe") => "bpf".into(),
112        _ if arch.starts_with("mips") => "mips".into(),
113        _ if arch.starts_with("powerpc") => "powerpc".into(),
114        _ if arch.starts_with("riscv") => "riscv".into(),
115        _ => arch.to_ascii_lowercase(),
116    }
117}