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