nextest_runner/config/elements/
threads_required.rs1use crate::config::core::get_num_cpus;
5use serde::Deserialize;
6use std::{cmp::Ordering, fmt};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ThreadsRequired {
11 Count(usize),
13
14 NumCpus,
16
17 NumTestThreads,
19}
20
21impl ThreadsRequired {
22 pub fn compute(self, test_threads: usize) -> usize {
24 match self {
25 Self::Count(threads) => threads,
26 Self::NumCpus => get_num_cpus(),
27 Self::NumTestThreads => test_threads,
28 }
29 }
30}
31
32impl<'de> Deserialize<'de> for ThreadsRequired {
33 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34 where
35 D: serde::Deserializer<'de>,
36 {
37 struct V;
38
39 impl serde::de::Visitor<'_> for V {
40 type Value = ThreadsRequired;
41
42 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
43 write!(
44 formatter,
45 "an integer, the string \"num-cpus\" or the string \"num-test-threads\""
46 )
47 }
48
49 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
50 where
51 E: serde::de::Error,
52 {
53 if v == "num-cpus" {
54 Ok(ThreadsRequired::NumCpus)
55 } else if v == "num-test-threads" {
56 Ok(ThreadsRequired::NumTestThreads)
57 } else {
58 Err(serde::de::Error::invalid_value(
59 serde::de::Unexpected::Str(v),
60 &self,
61 ))
62 }
63 }
64
65 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
67 where
68 E: serde::de::Error,
69 {
70 match v.cmp(&0) {
71 Ordering::Greater => Ok(ThreadsRequired::Count(v as usize)),
72 Ordering::Equal | Ordering::Less => Err(serde::de::Error::invalid_value(
81 serde::de::Unexpected::Signed(v),
82 &self,
83 )),
84 }
85 }
86 }
87
88 deserializer.deserialize_any(V)
89 }
90}
91
92#[cfg(feature = "config-schema")]
93impl schemars::JsonSchema for ThreadsRequired {
94 fn schema_name() -> std::borrow::Cow<'static, str> {
95 "ThreadsRequired".into()
96 }
97
98 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
99 schemars::json_schema!({
100 "oneOf": [
101 { "type": "integer", "minimum": 1 },
102 { "type": "string", "enum": ["num-cpus", "num-test-threads"] }
103 ]
104 })
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::config::{core::NextestConfig, utils::test_helpers::*};
112 use camino_tempfile::tempdir;
113 use indoc::indoc;
114 use nextest_filtering::ParseContext;
115 use test_case::test_case;
116
117 #[test_case(
118 indoc! {r#"
119 [profile.custom]
120 threads-required = 2
121 "#},
122 Some(2)
123
124 ; "positive"
125 )]
126 #[test_case(
127 indoc! {r#"
128 [profile.custom]
129 threads-required = 0
130 "#},
131 None
132
133 ; "zero"
134 )]
135 #[test_case(
136 indoc! {r#"
137 [profile.custom]
138 threads-required = -1
139 "#},
140 None
141
142 ; "negative"
143 )]
144 #[test_case(
145 indoc! {r#"
146 [profile.custom]
147 threads-required = "num-cpus"
148 "#},
149 Some(get_num_cpus())
150
151 ; "num-cpus"
152 )]
153 #[test_case(
154 indoc! {r#"
155 [profile.custom]
156 test-threads = 1
157 threads-required = "num-cpus"
158 "#},
159 Some(get_num_cpus())
160
161 ; "num-cpus-with-custom-test-threads"
162 )]
163 #[test_case(
164 indoc! {r#"
165 [profile.custom]
166 threads-required = "num-test-threads"
167 "#},
168 Some(get_num_cpus())
169
170 ; "num-test-threads"
171 )]
172 #[test_case(
173 indoc! {r#"
174 [profile.custom]
175 test-threads = 1
176 threads-required = "num-test-threads"
177 "#},
178 Some(1)
179
180 ; "num-test-threads-with-custom-test-threads"
181 )]
182 fn parse_threads_required(config_contents: &str, threads_required: Option<usize>) {
183 let workspace_dir = tempdir().unwrap();
184
185 let graph = temp_workspace(&workspace_dir, config_contents);
186
187 let pcx = ParseContext::new(&graph);
188 let config = NextestConfig::from_sources(
189 graph.workspace().root(),
190 &pcx,
191 None,
192 [],
193 &Default::default(),
194 );
195 match threads_required {
196 None => assert!(config.is_err()),
197 Some(t) => {
198 let config = config.unwrap();
199 let profile = config
200 .profile("custom")
201 .unwrap()
202 .apply_build_platforms(&build_platforms());
203
204 let test_threads = profile.test_threads().compute();
205 let threads_required = profile.threads_required().compute(test_threads);
206 assert_eq!(threads_required, t)
207 }
208 }
209 }
210}