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