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