1use std::borrow::Cow;
9use std::collections::HashMap;
10use std::hash::{BuildHasherDefault, DefaultHasher};
11use std::num::NonZero;
12use std::sync::{Arc, mpsc};
13use std::{env, hint, mem, panic, thread};
14
15use camino::Utf8PathBuf;
16
17use crate::common::{Config, TestPaths};
18use crate::output_capture::{self, ConsoleOut};
19use crate::panic_hook;
20
21mod deadline;
22mod json;
23
24pub(crate) fn run_tests(config: &Config, tests: Vec<CollectedTest>) -> bool {
25 let tests_len = tests.len();
26 let filtered = filter_tests(config, tests);
27 let mut fresh_tests = (0..).map(TestId).zip(&filtered);
29
30 let concurrency = get_concurrency();
31 assert!(concurrency > 0);
32 let concurrent_capacity = concurrency.min(filtered.len());
33
34 let mut listener = json::Listener::new();
35 let mut running_tests = HashMap::with_capacity_and_hasher(
36 concurrent_capacity,
37 BuildHasherDefault::<DefaultHasher>::new(),
38 );
39 let mut deadline_queue = deadline::DeadlineQueue::with_capacity(concurrent_capacity);
40
41 let num_filtered_out = tests_len - filtered.len();
42 listener.suite_started(filtered.len(), num_filtered_out);
43
44 let (completion_tx, completion_rx) = mpsc::channel::<TestCompletion>();
46
47 loop {
50 while running_tests.len() < concurrency
52 && let Some((id, test)) = fresh_tests.next()
53 {
54 listener.test_started(test);
55 deadline_queue.push(id, test);
56 let join_handle = spawn_test_thread(id, test, completion_tx.clone());
57 running_tests.insert(id, RunningTest { test, join_handle });
58 }
59
60 if running_tests.is_empty() {
63 break;
64 }
65
66 let completion = deadline_queue
67 .read_channel_while_checking_deadlines(
68 &completion_rx,
69 |id| running_tests.contains_key(&id),
70 |_id, test| listener.test_timed_out(test),
71 )
72 .expect("receive channel should never be closed early");
73
74 let RunningTest { test, join_handle } = running_tests.remove(&completion.id).unwrap();
75 if let Some(join_handle) = join_handle {
76 join_handle.join().unwrap_or_else(|_| {
77 panic!("thread for `{}` panicked after reporting completion", test.desc.name)
78 });
79 }
80
81 listener.test_finished(test, &completion);
82
83 if completion.outcome.is_failed() && config.fail_fast {
84 mem::forget(completion_rx);
87 break;
88 }
89 }
90
91 let suite_passed = listener.suite_finished();
92 suite_passed
93}
94
95fn spawn_test_thread(
99 id: TestId,
100 test: &CollectedTest,
101 completion_tx: mpsc::Sender<TestCompletion>,
102) -> Option<thread::JoinHandle<()>> {
103 if test.desc.ignore && !test.config.run_ignored {
104 completion_tx
105 .send(TestCompletion { id, outcome: TestOutcome::Ignored, stdout: None })
106 .unwrap();
107 return None;
108 }
109
110 let runnable_test = RunnableTest::new(test);
111 let should_panic = test.desc.should_panic;
112 let run_test = move || run_test_inner(id, should_panic, runnable_test, completion_tx);
113
114 let thread_builder = thread::Builder::new().name(test.desc.name.clone());
115 let join_handle = thread_builder.spawn(run_test).unwrap();
116 Some(join_handle)
117}
118
119fn run_test_inner(
121 id: TestId,
122 should_panic: ShouldPanic,
123 runnable_test: RunnableTest,
124 completion_sender: mpsc::Sender<TestCompletion>,
125) {
126 let capture = CaptureKind::for_config(&runnable_test.config);
127
128 if capture.should_set_panic_hook() {
130 panic_hook::set_capture_buf(Default::default());
131 }
132
133 let stdout = capture.stdout();
134 let stderr = capture.stderr();
135
136 let panic_payload = panic::catch_unwind(move || runnable_test.run(stdout, stderr)).err();
137
138 if let Some(panic_buf) = panic_hook::take_capture_buf() {
139 let panic_buf = panic_buf.lock().unwrap_or_else(|e| e.into_inner());
140 write!(stderr, "{panic_buf}");
142 }
143
144 let outcome = match (should_panic, panic_payload) {
145 (ShouldPanic::No, None) | (ShouldPanic::Yes, Some(_)) => TestOutcome::Succeeded,
146 (ShouldPanic::No, Some(_)) => TestOutcome::Failed { message: None },
147 (ShouldPanic::Yes, None) => {
148 TestOutcome::Failed { message: Some("test did not panic as expected") }
149 }
150 };
151
152 let stdout = capture.into_inner();
153 completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap();
154}
155
156enum CaptureKind {
157 None,
162
163 Capture { buf: output_capture::CaptureBuf },
166}
167
168impl CaptureKind {
169 fn for_config(config: &Config) -> Self {
170 if config.nocapture {
171 Self::None
172 } else {
173 Self::Capture { buf: output_capture::CaptureBuf::new() }
174 }
175 }
176
177 fn should_set_panic_hook(&self) -> bool {
178 match self {
179 Self::None => false,
180 Self::Capture { .. } => true,
181 }
182 }
183
184 fn stdout(&self) -> &dyn ConsoleOut {
185 self.capture_buf_or(&output_capture::Stdout)
186 }
187
188 fn stderr(&self) -> &dyn ConsoleOut {
189 self.capture_buf_or(&output_capture::Stderr)
190 }
191
192 fn capture_buf_or<'a>(&'a self, fallback: &'a dyn ConsoleOut) -> &'a dyn ConsoleOut {
193 match self {
194 Self::None => fallback,
195 Self::Capture { buf } => buf,
196 }
197 }
198
199 fn into_inner(self) -> Option<Vec<u8>> {
200 match self {
201 Self::None => None,
202 Self::Capture { buf } => Some(buf.into_inner().into()),
203 }
204 }
205}
206
207#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
208struct TestId(usize);
209
210struct RunnableTest {
211 config: Arc<Config>,
212 testpaths: TestPaths,
213 revision: Option<String>,
214}
215
216impl RunnableTest {
217 fn new(test: &CollectedTest) -> Self {
218 let config = Arc::clone(&test.config);
219 let testpaths = test.testpaths.clone();
220 let revision = test.revision.clone();
221 Self { config, testpaths, revision }
222 }
223
224 fn run(&self, stdout: &dyn ConsoleOut, stderr: &dyn ConsoleOut) {
225 __rust_begin_short_backtrace(|| {
226 crate::runtest::run(
227 Arc::clone(&self.config),
228 stdout,
229 stderr,
230 &self.testpaths,
231 self.revision.as_deref(),
232 );
233 });
234 }
235}
236
237#[inline(never)]
239fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
240 let result = f();
241
242 hint::black_box(result)
244}
245
246struct RunningTest<'a> {
247 test: &'a CollectedTest,
248 join_handle: Option<thread::JoinHandle<()>>,
249}
250
251struct TestCompletion {
254 id: TestId,
255 outcome: TestOutcome,
256 stdout: Option<Vec<u8>>,
257}
258
259#[derive(Clone, Debug, PartialEq, Eq)]
260enum TestOutcome {
261 Succeeded,
262 Failed { message: Option<&'static str> },
263 Ignored,
264}
265
266impl TestOutcome {
267 fn is_failed(&self) -> bool {
268 matches!(self, Self::Failed { .. })
269 }
270}
271
272fn filter_tests(opts: &Config, tests: Vec<CollectedTest>) -> Vec<CollectedTest> {
280 let mut filtered = tests;
281
282 let matches_filter = |test: &CollectedTest, filter_str: &str| {
283 if opts.filter_exact {
284 test.desc.filterable_path.as_str() == filter_str
287 } else {
288 test.desc.name.contains(filter_str)
291 }
292 };
293
294 if !opts.filters.is_empty() {
296 filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
297 }
298
299 if !opts.skip.is_empty() {
301 filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
302 }
303
304 filtered
305}
306
307fn get_concurrency() -> usize {
315 if let Ok(value) = env::var("RUST_TEST_THREADS") {
316 match value.parse::<NonZero<usize>>().ok() {
317 Some(n) => n.get(),
318 _ => panic!("RUST_TEST_THREADS is `{value}`, should be a positive integer."),
319 }
320 } else {
321 thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
322 }
323}
324
325pub(crate) struct CollectedTest {
327 pub(crate) desc: CollectedTestDesc,
328 pub(crate) config: Arc<Config>,
329 pub(crate) testpaths: TestPaths,
330 pub(crate) revision: Option<String>,
331}
332
333pub(crate) struct CollectedTestDesc {
335 pub(crate) name: String,
336 pub(crate) filterable_path: Utf8PathBuf,
337 pub(crate) ignore: bool,
338 pub(crate) ignore_message: Option<Cow<'static, str>>,
339 pub(crate) should_panic: ShouldPanic,
340}
341
342#[derive(Copy, Clone, Default, Debug)]
344pub enum ColorConfig {
345 #[default]
346 AutoColor,
347 AlwaysColor,
348 NeverColor,
349}
350
351#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
353pub(crate) enum ShouldPanic {
354 No,
355 Yes,
356}