nextest_runner/config/elements/
global_timeout.rs1use serde::{Deserialize, Deserializer};
5use std::time::Duration;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct GlobalTimeout {
10 pub(crate) period: Duration,
11}
12
13impl<'de> Deserialize<'de> for GlobalTimeout {
14 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
15 where
16 D: Deserializer<'de>,
17 {
18 Ok(GlobalTimeout {
19 period: humantime_serde::deserialize(deserializer)?,
20 })
21 }
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27 use crate::{
28 config::{core::NextestConfig, utils::test_helpers::*},
29 run_mode::NextestRunMode,
30 };
31 use camino_tempfile::tempdir;
32 use indoc::indoc;
33 use nextest_filtering::ParseContext;
34 use test_case::test_case;
35
36 #[test_case(
37 "",
38 Ok(GlobalTimeout { period: Duration::from_secs(946728000) }),
39 None
40
41 ; "empty config is expected to use the hardcoded values"
42 )]
43 #[test_case(
44 indoc! {r#"
45 [profile.default]
46 global-timeout = "30s"
47 "#},
48 Ok(GlobalTimeout { period: Duration::from_secs(30) }),
49 None
50
51 ; "overrides the default profile"
52 )]
53 #[test_case(
54 indoc! {r#"
55 [profile.default]
56 global-timeout = "30s"
57
58 [profile.ci]
59 global-timeout = "60s"
60 "#},
61 Ok(GlobalTimeout { period: Duration::from_secs(30) }),
62 Some(GlobalTimeout { period: Duration::from_secs(60) })
63
64 ; "adds a custom profile 'ci'"
65 )]
66 fn globaltimeout_adheres_to_hierarchy(
67 config_contents: &str,
68 expected_default: Result<GlobalTimeout, &str>,
69 maybe_expected_ci: Option<GlobalTimeout>,
70 ) {
71 let workspace_dir = tempdir().unwrap();
72
73 let graph = temp_workspace(&workspace_dir, config_contents);
74
75 let pcx = ParseContext::new(&graph);
76
77 let nextest_config_result = NextestConfig::from_sources(
78 graph.workspace().root(),
79 &pcx,
80 None,
81 &[][..],
82 &Default::default(),
83 );
84
85 match expected_default {
86 Ok(expected_default) => {
87 let nextest_config = nextest_config_result.expect("config file should parse");
88
89 assert_eq!(
90 nextest_config
91 .profile("default")
92 .expect("default profile should exist")
93 .apply_build_platforms(&build_platforms())
94 .global_timeout(NextestRunMode::Test),
95 expected_default,
96 );
97
98 if let Some(expected_ci) = maybe_expected_ci {
99 assert_eq!(
100 nextest_config
101 .profile("ci")
102 .expect("ci profile should exist")
103 .apply_build_platforms(&build_platforms())
104 .global_timeout(NextestRunMode::Test),
105 expected_ci,
106 );
107 }
108 }
109
110 Err(expected_err_str) => {
111 let err_str = format!("{:?}", nextest_config_result.unwrap_err());
112
113 assert!(
114 err_str.contains(expected_err_str),
115 "expected error string not found: {err_str}",
116 )
117 }
118 }
119 }
120
121 const DEFAULT_GLOBAL_TIMEOUT: GlobalTimeout = GlobalTimeout {
123 period: Duration::from_secs(946728000),
124 };
125
126 #[derive(Debug)]
129 enum ExpectedBenchGlobalTimeout {
130 Exact(GlobalTimeout),
132 VeryLarge,
135 }
136
137 #[test_case(
138 "",
139 DEFAULT_GLOBAL_TIMEOUT,
140 ExpectedBenchGlobalTimeout::VeryLarge
141 ; "empty config uses defaults for both modes"
142 )]
143 #[test_case(
144 indoc! {r#"
145 [profile.default]
146 global-timeout = "10s"
147 "#},
148 GlobalTimeout { period: Duration::from_secs(10) },
149 ExpectedBenchGlobalTimeout::VeryLarge
151 ; "global-timeout does not affect bench.global-timeout"
152 )]
153 #[test_case(
154 indoc! {r#"
155 [profile.default]
156 bench.global-timeout = "20s"
157 "#},
158 DEFAULT_GLOBAL_TIMEOUT,
160 ExpectedBenchGlobalTimeout::Exact(GlobalTimeout {
161 period: Duration::from_secs(20),
162 })
163 ; "bench.global-timeout does not affect global-timeout"
164 )]
165 #[test_case(
166 indoc! {r#"
167 [profile.default]
168 global-timeout = "10s"
169 bench.global-timeout = "20s"
170 "#},
171 GlobalTimeout { period: Duration::from_secs(10) },
172 ExpectedBenchGlobalTimeout::Exact(GlobalTimeout {
173 period: Duration::from_secs(20),
174 })
175 ; "both global-timeout and bench.global-timeout can be set independently"
176 )]
177 fn bench_globaltimeout_is_independent(
178 config_contents: &str,
179 expected_test_timeout: GlobalTimeout,
180 expected_bench_timeout: ExpectedBenchGlobalTimeout,
181 ) {
182 let workspace_dir = tempdir().unwrap();
183
184 let graph = temp_workspace(&workspace_dir, config_contents);
185
186 let pcx = ParseContext::new(&graph);
187
188 let nextest_config = NextestConfig::from_sources(
189 graph.workspace().root(),
190 &pcx,
191 None,
192 &[][..],
193 &Default::default(),
194 )
195 .expect("config file should parse");
196
197 let profile = nextest_config
198 .profile("default")
199 .expect("default profile should exist")
200 .apply_build_platforms(&build_platforms());
201
202 assert_eq!(
203 profile.global_timeout(NextestRunMode::Test),
204 expected_test_timeout,
205 "Test mode global-timeout mismatch"
206 );
207
208 let actual_bench_timeout = profile.global_timeout(NextestRunMode::Benchmark);
209 match expected_bench_timeout {
210 ExpectedBenchGlobalTimeout::Exact(expected) => {
211 assert_eq!(
212 actual_bench_timeout, expected,
213 "Benchmark mode global-timeout mismatch"
214 );
215 }
216 ExpectedBenchGlobalTimeout::VeryLarge => {
217 assert!(
220 actual_bench_timeout.period >= DEFAULT_GLOBAL_TIMEOUT.period,
221 "Benchmark mode global-timeout should be >= default, got {:?}",
222 actual_bench_timeout.period
223 );
224 }
225 }
226 }
227}