Skip to main content

compiletest/runtest/
codegen_units.rs

1use std::collections::HashSet;
2
3use super::{Emit, TestCx, WillExecute};
4use crate::util::static_regex;
5use crate::{errors, fatal};
6
7impl TestCx<'_> {
8    pub(super) fn run_codegen_units_test(&self) {
9        assert!(self.revision.is_none(), "revisions not relevant here");
10
11        let proc_res = self.compile_test(WillExecute::No, Emit::None);
12
13        if !proc_res.status.success() {
14            self.fatal_proc_rec("compilation failed!", &proc_res);
15        }
16
17        const PREFIX: &str = "MONO_ITEM ";
18        const CGU_MARKER: &str = "@@";
19
20        // Some MonoItems can contain {closure@/path/to/checkout/tests/codgen-units/test.rs}
21        // To prevent the current dir from leaking, we just replace the entire path to the test
22        // file with TEST_PATH.
23        let actual: Vec<MonoItem> = proc_res
24            .stdout
25            .lines()
26            .filter(|line| line.starts_with(PREFIX))
27            .map(|line| line.replace(&self.testpaths.file.as_str(), "TEST_PATH").to_string())
28            .map(|line| str_to_mono_item(&line, true))
29            .collect();
30
31        let expected: Vec<MonoItem> = errors::load_errors(&self.testpaths.file, None)
32            .iter()
33            .map(|e| str_to_mono_item(&e.msg[..], false))
34            .collect();
35
36        let mut missing = Vec::new();
37        let mut wrong_cgus = Vec::new();
38
39        for expected_item in &expected {
40            let actual_item_with_same_name = actual.iter().find(|ti| ti.name == expected_item.name);
41
42            if let Some(actual_item) = actual_item_with_same_name {
43                if !expected_item.codegen_units.is_empty() &&
44                   // Also check for codegen units
45                   expected_item.codegen_units != actual_item.codegen_units
46                {
47                    wrong_cgus.push((expected_item.clone(), actual_item.clone()));
48                }
49            } else {
50                missing.push(expected_item.string.clone());
51            }
52        }
53
54        let unexpected: Vec<_> = actual
55            .iter()
56            .filter(|acgu| !expected.iter().any(|ecgu| acgu.name == ecgu.name))
57            .map(|acgu| acgu.string.clone())
58            .collect();
59
60        if !missing.is_empty() {
61            missing.sort();
62
63            writeln!(self.stdout, "\nThese items should have been contained but were not:\n");
64
65            for item in &missing {
66                writeln!(self.stdout, "{}", item);
67            }
68
69            writeln!(self.stdout, "\n");
70        }
71
72        if !unexpected.is_empty() {
73            let sorted = {
74                let mut sorted = unexpected.clone();
75                sorted.sort();
76                sorted
77            };
78
79            writeln!(self.stdout, "\nThese items were contained but should not have been:\n");
80
81            for item in sorted {
82                writeln!(self.stdout, "{}", item);
83            }
84
85            writeln!(self.stdout, "\n");
86        }
87
88        if !wrong_cgus.is_empty() {
89            wrong_cgus.sort_by_key(|pair| pair.0.name.clone());
90            writeln!(self.stdout, "\nThe following items were assigned to wrong codegen units:\n");
91
92            for &(ref expected_item, ref actual_item) in &wrong_cgus {
93                writeln!(self.stdout, "{}", expected_item.name);
94                writeln!(
95                    self.stdout,
96                    "  expected: {}",
97                    codegen_units_to_str(&expected_item.codegen_units)
98                );
99                writeln!(
100                    self.stdout,
101                    "  actual:   {}",
102                    codegen_units_to_str(&actual_item.codegen_units)
103                );
104                writeln!(self.stdout);
105            }
106        }
107
108        if !(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty()) {
109            fatal!("!(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty())");
110        }
111
112        #[derive(Clone, Eq, PartialEq)]
113        struct MonoItem {
114            name: String,
115            codegen_units: HashSet<String>,
116            string: String,
117        }
118
119        // [MONO_ITEM] name [@@ (cgu)+]
120        fn str_to_mono_item(s: &str, cgu_has_crate_disambiguator: bool) -> MonoItem {
121            let s = if s.starts_with(PREFIX) { (&s[PREFIX.len()..]).trim() } else { s.trim() };
122
123            let full_string = format!("{}{}", PREFIX, s);
124
125            let parts: Vec<&str> =
126                s.split(CGU_MARKER).map(str::trim).filter(|s| !s.is_empty()).collect();
127
128            let name = parts[0].trim();
129
130            let cgus = if parts.len() > 1 {
131                let cgus_str = parts[1];
132
133                cgus_str
134                    .split(' ')
135                    .map(str::trim)
136                    .filter(|s| !s.is_empty())
137                    .map(|s| {
138                        if cgu_has_crate_disambiguator {
139                            remove_crate_disambiguators_from_set_of_cgu_names(s)
140                        } else {
141                            s.to_string()
142                        }
143                    })
144                    .collect()
145            } else {
146                HashSet::new()
147            };
148
149            MonoItem { name: name.to_owned(), codegen_units: cgus, string: full_string }
150        }
151
152        fn codegen_units_to_str(cgus: &HashSet<String>) -> String {
153            let mut cgus: Vec<_> = cgus.iter().collect();
154            cgus.sort();
155
156            let mut string = String::new();
157            for cgu in cgus {
158                string.push_str(&cgu[..]);
159                string.push(' ');
160            }
161
162            string
163        }
164
165        // Given a cgu-name-prefix of the form <crate-name>.<crate-disambiguator> or
166        // the form <crate-name1>.<crate-disambiguator1>-in-<crate-name2>.<crate-disambiguator2>,
167        // remove all crate-disambiguators.
168        fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String {
169            let Some(captures) =
170                static_regex!(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?")
171                    .captures(cgu)
172            else {
173                panic!("invalid cgu name encountered: {cgu}");
174            };
175
176            let mut new_name = cgu.to_owned();
177
178            if let Some(d2) = captures.name("d2") {
179                new_name.replace_range(d2.start()..d2.end(), "");
180            }
181
182            let d1 = captures.name("d1").unwrap();
183            new_name.replace_range(d1.start()..d1.end(), "");
184
185            new_name
186        }
187
188        // The name of merged CGUs is constructed as the names of the original
189        // CGUs joined with "--". This function splits such composite CGU names
190        // and handles each component individually.
191        fn remove_crate_disambiguators_from_set_of_cgu_names(cgus: &str) -> String {
192            cgus.split("--").map(remove_crate_disambiguator_from_cgu).collect::<Vec<_>>().join("--")
193        }
194    }
195}