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