nextest_runner/config/core/
tool_config.rs1use crate::errors::ToolConfigFileParseError;
5use camino::{Utf8Path, Utf8PathBuf};
6use std::str::FromStr;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct ToolConfigFile {
14 pub tool: String,
16
17 pub config_file: Utf8PathBuf,
19}
20
21impl FromStr for ToolConfigFile {
22 type Err = ToolConfigFileParseError;
23
24 fn from_str(input: &str) -> Result<Self, Self::Err> {
25 match input.split_once(':') {
26 Some((tool, config_file)) => {
27 if tool.is_empty() {
28 Err(ToolConfigFileParseError::EmptyToolName {
29 input: input.to_owned(),
30 })
31 } else if config_file.is_empty() {
32 Err(ToolConfigFileParseError::EmptyConfigFile {
33 input: input.to_owned(),
34 })
35 } else {
36 let config_file = Utf8Path::new(config_file);
37 if config_file.is_absolute() {
38 Ok(Self {
39 tool: tool.to_owned(),
40 config_file: Utf8PathBuf::from(config_file),
41 })
42 } else {
43 Err(ToolConfigFileParseError::ConfigFileNotAbsolute {
44 config_file: config_file.to_owned(),
45 })
46 }
47 }
48 }
49 None => Err(ToolConfigFileParseError::InvalidFormat {
50 input: input.to_owned(),
51 }),
52 }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::config::{
60 core::{NextestConfig, NextestVersionConfig, NextestVersionReq, VersionOnlyConfig},
61 elements::{RetryPolicy, TestGroup},
62 utils::test_helpers::*,
63 };
64 use camino_tempfile::tempdir;
65 use camino_tempfile_ext::prelude::*;
66 use guppy::graph::cargo::BuildPlatform;
67 use nextest_filtering::{ParseContext, TestQuery};
68
69 #[test]
70 fn parse_tool_config_file() {
71 cfg_if::cfg_if! {
72 if #[cfg(windows)] {
73 let valid = ["tool:C:\\foo\\bar", "tool:\\\\?\\C:\\foo\\bar"];
74 let invalid = ["C:\\foo\\bar", "tool:\\foo\\bar", "tool:", ":/foo/bar"];
75 } else {
76 let valid = ["tool:/foo/bar"];
77 let invalid = ["/foo/bar", "tool:", ":/foo/bar", "tool:foo/bar"];
78 }
79 }
80
81 for valid_input in valid {
82 valid_input.parse::<ToolConfigFile>().unwrap_or_else(|err| {
83 panic!("valid input {valid_input} should parse correctly: {err}")
84 });
85 }
86
87 for invalid_input in invalid {
88 invalid_input
89 .parse::<ToolConfigFile>()
90 .expect_err(&format!("invalid input {invalid_input} should error out"));
91 }
92 }
93
94 #[test]
95 fn tool_config_basic() {
96 let config_contents = r#"
97 nextest-version = "0.9.50"
98
99 [profile.default]
100 retries = 3
101
102 [[profile.default.overrides]]
103 filter = 'test(test_foo)'
104 retries = 20
105 test-group = 'foo'
106
107 [[profile.default.overrides]]
108 filter = 'test(test_quux)'
109 test-group = '@tool:tool1:group1'
110
111 [test-groups.foo]
112 max-threads = 2
113 "#;
114
115 let tool1_config_contents = r#"
116 nextest-version = { required = "0.9.51", recommended = "0.9.52" }
117
118 [profile.default]
119 retries = 4
120
121 [[profile.default.overrides]]
122 filter = 'test(test_bar)'
123 retries = 21
124
125 [profile.tool]
126 retries = 12
127
128 [[profile.tool.overrides]]
129 filter = 'test(test_baz)'
130 retries = 22
131 test-group = '@tool:tool1:group1'
132
133 [[profile.tool.overrides]]
134 filter = 'test(test_quux)'
135 retries = 22
136 test-group = '@tool:tool2:group2'
137
138 [test-groups.'@tool:tool1:group1']
139 max-threads = 2
140 "#;
141
142 let tool2_config_contents = r#"
143 nextest-version = { recommended = "0.9.49" }
144
145 [profile.default]
146 retries = 5
147
148 [[profile.default.overrides]]
149 filter = 'test(test_)'
150 retries = 23
151
152 [profile.tool]
153 retries = 16
154
155 [[profile.tool.overrides]]
156 filter = 'test(test_ba)'
157 retries = 24
158 test-group = '@tool:tool2:group2'
159
160 [[profile.tool.overrides]]
161 filter = 'test(test_)'
162 retries = 25
163 test-group = '@global'
164
165 [profile.tool2]
166 retries = 18
167
168 [[profile.tool2.overrides]]
169 filter = 'all()'
170 retries = 26
171
172 [test-groups.'@tool:tool2:group2']
173 max-threads = 4
174 "#;
175
176 let workspace_dir = tempdir().unwrap();
177
178 let graph = temp_workspace(&workspace_dir, config_contents);
179 let tool1_path = workspace_dir.child(".config/tool1.toml");
180 let tool2_path = workspace_dir.child(".config/tool2.toml");
181 tool1_path.write_str(tool1_config_contents).unwrap();
182 tool2_path.write_str(tool2_config_contents).unwrap();
183
184 let workspace_root = graph.workspace().root();
185
186 let tool_config_files = [
187 ToolConfigFile {
188 tool: "tool1".to_owned(),
189 config_file: tool1_path.to_path_buf(),
190 },
191 ToolConfigFile {
192 tool: "tool2".to_owned(),
193 config_file: tool2_path.to_path_buf(),
194 },
195 ];
196
197 let version_only_config =
198 VersionOnlyConfig::from_sources(workspace_root, None, &tool_config_files).unwrap();
199 let nextest_version = version_only_config.nextest_version();
200 assert_eq!(
201 nextest_version,
202 &NextestVersionConfig {
203 required: NextestVersionReq::Version {
204 version: "0.9.51".parse().unwrap(),
205 tool: Some("tool1".to_owned())
206 },
207 recommended: NextestVersionReq::Version {
208 version: "0.9.52".parse().unwrap(),
209 tool: Some("tool1".to_owned())
210 }
211 },
212 );
213
214 let pcx = ParseContext::new(&graph);
215 let config = NextestConfig::from_sources(
216 workspace_root,
217 &pcx,
218 None,
219 &tool_config_files,
220 &Default::default(),
221 )
222 .expect("config is valid");
223
224 let default_profile = config
225 .profile(NextestConfig::DEFAULT_PROFILE)
226 .expect("default profile is present")
227 .apply_build_platforms(&build_platforms());
228 assert_eq!(default_profile.retries(), RetryPolicy::new_without_delay(3));
230
231 let package_id = graph.workspace().iter().next().unwrap().id();
232
233 let binary_query = binary_query(
234 &graph,
235 package_id,
236 "lib",
237 "my-binary",
238 BuildPlatform::Target,
239 );
240 let test_foo_query = TestQuery {
241 binary_query: binary_query.to_query(),
242 test_name: "test_foo",
243 };
244 let test_bar_query = TestQuery {
245 binary_query: binary_query.to_query(),
246 test_name: "test_bar",
247 };
248 let test_baz_query = TestQuery {
249 binary_query: binary_query.to_query(),
250 test_name: "test_baz",
251 };
252 let test_quux_query = TestQuery {
253 binary_query: binary_query.to_query(),
254 test_name: "test_quux",
255 };
256
257 assert_eq!(
258 default_profile.settings_for(&test_foo_query).retries(),
259 RetryPolicy::new_without_delay(20),
260 "retries for test_foo/default profile"
261 );
262 assert_eq!(
263 default_profile.settings_for(&test_foo_query).test_group(),
264 &test_group("foo"),
265 "test_group for test_foo/default profile"
266 );
267 assert_eq!(
268 default_profile.settings_for(&test_bar_query).retries(),
269 RetryPolicy::new_without_delay(21),
270 "retries for test_bar/default profile"
271 );
272 assert_eq!(
273 default_profile.settings_for(&test_bar_query).test_group(),
274 &TestGroup::Global,
275 "test_group for test_bar/default profile"
276 );
277 assert_eq!(
278 default_profile.settings_for(&test_baz_query).retries(),
279 RetryPolicy::new_without_delay(23),
280 "retries for test_baz/default profile"
281 );
282 assert_eq!(
283 default_profile.settings_for(&test_quux_query).test_group(),
284 &test_group("@tool:tool1:group1"),
285 "test group for test_quux/default profile"
286 );
287
288 let tool_profile = config
289 .profile("tool")
290 .expect("tool profile is present")
291 .apply_build_platforms(&build_platforms());
292 assert_eq!(tool_profile.retries(), RetryPolicy::new_without_delay(12));
293 assert_eq!(
294 tool_profile.settings_for(&test_foo_query).retries(),
295 RetryPolicy::new_without_delay(25),
296 "retries for test_foo/default profile"
297 );
298 assert_eq!(
299 tool_profile.settings_for(&test_bar_query).retries(),
300 RetryPolicy::new_without_delay(24),
301 "retries for test_bar/default profile"
302 );
303 assert_eq!(
304 tool_profile.settings_for(&test_baz_query).retries(),
305 RetryPolicy::new_without_delay(22),
306 "retries for test_baz/default profile"
307 );
308
309 let tool2_profile = config
310 .profile("tool2")
311 .expect("tool2 profile is present")
312 .apply_build_platforms(&build_platforms());
313 assert_eq!(tool2_profile.retries(), RetryPolicy::new_without_delay(18));
314 assert_eq!(
315 tool2_profile.settings_for(&test_foo_query).retries(),
316 RetryPolicy::new_without_delay(26),
317 "retries for test_foo/default profile"
318 );
319 assert_eq!(
320 tool2_profile.settings_for(&test_bar_query).retries(),
321 RetryPolicy::new_without_delay(26),
322 "retries for test_bar/default profile"
323 );
324 assert_eq!(
325 tool2_profile.settings_for(&test_baz_query).retries(),
326 RetryPolicy::new_without_delay(26),
327 "retries for test_baz/default profile"
328 );
329 }
330}