nextest_runner/show_config/
test_groups.rs1use crate::{
5 config::{
6 CompiledOverride, CustomTestGroup, EarlyProfile, EvaluatableProfile, FinalConfig,
7 MaybeTargetSpec, OverrideId, SettingSource, TestGroup, TestGroupConfig,
8 },
9 errors::ShowTestGroupsError,
10 helpers::QuotedDisplay,
11 indenter::indented,
12 list::{TestInstance, TestList, TestListDisplayFilter},
13 write_str::WriteStr,
14};
15use indexmap::IndexMap;
16use owo_colors::{OwoColorize, Style};
17use std::{
18 collections::{BTreeMap, BTreeSet},
19 io,
20};
21
22#[derive(Debug)]
24pub struct ShowTestGroups<'a> {
25 test_list: &'a TestList<'a>,
26 indexed_overrides: BTreeMap<TestGroup, IndexMap<OverrideId, ShowTestGroupsData<'a>>>,
27 test_group_config: &'a BTreeMap<CustomTestGroup, TestGroupConfig>,
28 non_overrides: Option<TestListDisplayFilter<'a>>,
30}
31
32impl<'a> ShowTestGroups<'a> {
33 pub fn validate_groups(
35 profile: &EarlyProfile<'_>,
36 groups: impl IntoIterator<Item = TestGroup>,
37 ) -> Result<ValidatedTestGroups, ShowTestGroupsError> {
38 let groups: BTreeSet<_> = groups.into_iter().collect();
39 let known_groups: BTreeSet<_> =
40 TestGroup::make_all_groups(profile.test_group_config().keys().cloned()).collect();
41 let unknown_groups = &groups - &known_groups;
42 if !unknown_groups.is_empty() {
43 return Err(ShowTestGroupsError::UnknownGroups {
44 unknown_groups,
45 known_groups,
46 });
47 }
48 Ok(ValidatedTestGroups(groups))
49 }
50
51 pub fn new(
53 profile: &'a EvaluatableProfile<'a>,
54 test_list: &'a TestList<'a>,
55 settings: &ShowTestGroupSettings,
56 ) -> Self {
57 let mut indexed_overrides: BTreeMap<_, _> =
58 TestGroup::make_all_groups(profile.test_group_config().keys().cloned())
59 .filter_map(|group| {
60 settings
61 .mode
62 .matches_group(&group)
63 .then(|| (group, IndexMap::new()))
64 })
65 .collect();
66 let mut non_overrides = settings.show_default.then(TestListDisplayFilter::new);
67
68 for suite in test_list.iter() {
69 for (test_name, test_case) in suite.status.test_cases() {
70 let test_instance = TestInstance::new(test_name, suite, test_case);
71 let query = test_instance.to_test_query();
72 let test_settings = profile.settings_with_source_for(&query);
73 let (test_group, source) = test_settings.test_group_with_source();
74
75 match source {
76 SettingSource::Override(source) => {
77 let override_map = match indexed_overrides.get_mut(test_group) {
78 Some(override_map) => override_map,
79 None => continue,
80 };
81 let data = override_map
82 .entry(source.id().clone())
83 .or_insert_with(|| ShowTestGroupsData::new(source));
84 data.matching_tests.insert(&suite.binary_id, test_name);
85 }
86 SettingSource::Script(_) => {
87 panic!("show-test-groups is not set via script section");
88 }
89 SettingSource::Profile | SettingSource::Default => {
90 if let Some(non_overrides) = non_overrides.as_mut() {
91 if settings.mode.matches_group(&TestGroup::Global) {
92 non_overrides.insert(&suite.binary_id, test_name);
93 }
94 }
95 }
96 }
97 }
98 }
99
100 Self {
101 test_list,
102 indexed_overrides,
103 test_group_config: profile.test_group_config(),
104 non_overrides,
105 }
106 }
107
108 fn should_show_group(&self, group: &TestGroup) -> bool {
109 match (group, self.non_overrides.is_some()) {
122 (TestGroup::Global, true) => true,
123 (TestGroup::Global, false) => self
124 .indexed_overrides
125 .get(group)
126 .map(|override_map| !override_map.values().all(|data| data.is_empty()))
127 .unwrap_or(false),
128 _ => true,
129 }
130 }
131
132 pub fn write_human(&self, mut writer: &mut dyn WriteStr, colorize: bool) -> io::Result<()> {
134 static INDENT: &str = " ";
135
136 let mut styles = Styles::default();
137 if colorize {
138 styles.colorize();
139 }
140
141 for (test_group, override_map) in &self.indexed_overrides {
142 if !self.should_show_group(test_group) {
143 continue;
144 }
145
146 write!(writer, "group: {}", test_group.style(styles.group))?;
147 if let TestGroup::Custom(group) = test_group {
148 write!(
149 writer,
150 " (max threads = {})",
151 self.test_group_config[group]
152 .max_threads
153 .style(styles.max_threads)
154 )?;
155 }
156 writeln!(writer)?;
157
158 let mut any_printed = false;
159
160 for (override_id, data) in override_map {
161 any_printed = true;
162 write!(
163 writer,
164 " * override for {} profile",
165 override_id.profile_name.style(styles.profile),
166 )?;
167
168 if let Some(expr) = data.override_.filter() {
169 write!(
170 writer,
171 " with filter {}",
172 QuotedDisplay(&expr.parsed).style(styles.filter)
173 )?;
174 }
175 if let MaybeTargetSpec::Provided(target_spec) = data.override_.target_spec() {
176 write!(
177 writer,
178 " on platform {}",
179 QuotedDisplay(target_spec).style(styles.platform)
180 )?;
181 }
182
183 writeln!(writer, ":")?;
184
185 let mut inner_writer = indented(writer).with_str(INDENT);
186 self.test_list.write_human_with_filter(
187 &data.matching_tests,
188 &mut inner_writer,
189 false,
190 colorize,
191 )?;
192 inner_writer.write_str_flush()?;
193 writer = inner_writer.into_inner();
194 }
195
196 if test_group == &TestGroup::Global {
198 if let Some(non_overrides) = &self.non_overrides {
199 any_printed = true;
200 writeln!(writer, " * from default settings:")?;
201 let mut inner_writer = indented(writer).with_str(INDENT);
202 self.test_list.write_human_with_filter(
203 non_overrides,
204 &mut inner_writer,
205 false,
206 colorize,
207 )?;
208 inner_writer.write_str_flush()?;
209 writer = inner_writer.into_inner();
210 }
211 }
212
213 if !any_printed {
214 writeln!(writer, " (no matches)")?;
215 }
216 }
217
218 Ok(())
219 }
220}
221
222#[derive(Clone, Debug)]
224pub struct ShowTestGroupSettings {
225 pub show_default: bool,
227
228 pub mode: ShowTestGroupsMode,
230}
231
232#[derive(Clone, Debug)]
234pub enum ShowTestGroupsMode {
235 All,
237 Only(ValidatedTestGroups),
239}
240
241impl ShowTestGroupsMode {
242 fn matches_group(&self, group: &TestGroup) -> bool {
243 match self {
244 Self::All => true,
245 Self::Only(groups) => groups.0.contains(group),
246 }
247 }
248}
249
250#[derive(Clone, Debug)]
252pub struct ValidatedTestGroups(BTreeSet<TestGroup>);
253
254impl ValidatedTestGroups {
255 pub fn into_inner(self) -> BTreeSet<TestGroup> {
257 self.0
258 }
259}
260
261#[derive(Debug)]
262struct ShowTestGroupsData<'a> {
263 override_: &'a CompiledOverride<FinalConfig>,
264 matching_tests: TestListDisplayFilter<'a>,
265}
266
267impl<'a> ShowTestGroupsData<'a> {
268 fn new(override_: &'a CompiledOverride<FinalConfig>) -> Self {
269 Self {
270 override_,
271 matching_tests: TestListDisplayFilter::new(),
272 }
273 }
274
275 fn is_empty(&self) -> bool {
276 self.matching_tests.test_count() == 0
277 }
278}
279
280#[derive(Clone, Debug, Default)]
281struct Styles {
282 group: Style,
283 max_threads: Style,
284 profile: Style,
285 filter: Style,
286 platform: Style,
287}
288
289impl Styles {
290 fn colorize(&mut self) {
291 self.group = Style::new().bold().underline();
292 self.max_threads = Style::new().bold();
293 self.profile = Style::new().bold();
294 self.filter = Style::new().yellow();
295 self.platform = Style::new().yellow();
296 }
297}