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::panic_hook;
17
18mod deadline;
19mod json;
20
21pub(crate) fn run_tests(config: &Config, tests: Vec<CollectedTest>) -> bool {
22 let tests_len = tests.len();
23 let filtered = filter_tests(config, tests);
24 let mut fresh_tests = (0..).map(TestId).zip(&filtered);
26
27 let concurrency = get_concurrency();
28 assert!(concurrency > 0);
29 let concurrent_capacity = concurrency.min(filtered.len());
30
31 let mut listener = json::Listener::new();
32 let mut running_tests = HashMap::with_capacity_and_hasher(
33 concurrent_capacity,
34 BuildHasherDefault::<DefaultHasher>::new(),
35 );
36 let mut deadline_queue = deadline::DeadlineQueue::with_capacity(concurrent_capacity);
37
38 let num_filtered_out = tests_len - filtered.len();
39 listener.suite_started(filtered.len(), num_filtered_out);
40
41 let (completion_tx, completion_rx) = mpsc::channel::<TestCompletion>();
43
44 loop {
47 while running_tests.len() < concurrency
49 && let Some((id, test)) = fresh_tests.next()
50 {
51 listener.test_started(test);
52 deadline_queue.push(id, test);
53 let join_handle = spawn_test_thread(id, test, completion_tx.clone());
54 running_tests.insert(id, RunningTest { test, join_handle });
55 }
56
57 if running_tests.is_empty() {
60 break;
61 }
62
63 let completion = deadline_queue
64 .read_channel_while_checking_deadlines(
65 &completion_rx,
66 |id| running_tests.contains_key(&id),
67 |_id, test| listener.test_timed_out(test),
68 )
69 .expect("receive channel should never be closed early");
70
71 let RunningTest { test, join_handle } = running_tests.remove(&completion.id).unwrap();
72 if let Some(join_handle) = join_handle {
73 join_handle.join().unwrap_or_else(|_| {
74 panic!("thread for `{}` panicked after reporting completion", test.desc.name)
75 });
76 }
77
78 listener.test_finished(test, &completion);
79
80 if completion.outcome.is_failed() && config.fail_fast {
81 mem::forget(completion_rx);
84 break;
85 }
86 }
87
88 let suite_passed = listener.suite_finished();
89 suite_passed
90}
91
92fn spawn_test_thread(
96 id: TestId,
97 test: &CollectedTest,
98 completion_tx: mpsc::Sender<TestCompletion>,
99) -> Option<thread::JoinHandle<()>> {
100 if test.desc.ignore && !test.config.run_ignored {
101 completion_tx
102 .send(TestCompletion { id, outcome: TestOutcome::Ignored, stdout: None })
103 .unwrap();
104 return None;
105 }
106
107 let runnable_test = RunnableTest::new(test);
108 let should_panic = test.desc.should_panic;
109 let run_test = move || run_test_inner(id, should_panic, runnable_test, completion_tx);
110
111 let thread_builder = thread::Builder::new().name(test.desc.name.clone());
112 let join_handle = thread_builder.spawn(run_test).unwrap();
113 Some(join_handle)
114}
115
116fn run_test_inner(
118 id: TestId,
119 should_panic: ShouldPanic,
120 runnable_test: RunnableTest,
121 completion_sender: mpsc::Sender<TestCompletion>,
122) {
123 let is_capture = !runnable_test.config.nocapture;
124
125 if is_capture {
127 panic_hook::set_capture_buf(Default::default());
128 }
129 let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![])));
130
131 if let Some(capture_buf) = &capture_buf {
132 io::set_output_capture(Some(Arc::clone(capture_buf)));
133 }
134
135 let panic_payload = panic::catch_unwind(move || runnable_test.run()).err();
136
137 if let Some(panic_buf) = panic_hook::take_capture_buf() {
138 let panic_buf = panic_buf.lock().unwrap_or_else(|e| e.into_inner());
139 eprint!("{panic_buf}");
143 }
144 if is_capture {
145 io::set_output_capture(None);
146 }
147
148 let outcome = match (should_panic, panic_payload) {
149 (ShouldPanic::No, None) | (ShouldPanic::Yes, Some(_)) => TestOutcome::Succeeded,
150 (ShouldPanic::No, Some(_)) => TestOutcome::Failed { message: None },
151 (ShouldPanic::Yes, None) => {
152 TestOutcome::Failed { message: Some("test did not panic as expected") }
153 }
154 };
155 let stdout = capture_buf.map(|mutex| mutex.lock().unwrap_or_else(|e| e.into_inner()).to_vec());
156
157 completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap();
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161struct TestId(usize);
162
163struct RunnableTest {
164 config: Arc<Config>,
165 testpaths: TestPaths,
166 revision: Option<String>,
167}
168
169impl RunnableTest {
170 fn new(test: &CollectedTest) -> Self {
171 let config = Arc::clone(&test.config);
172 let testpaths = test.testpaths.clone();
173 let revision = test.revision.clone();
174 Self { config, testpaths, revision }
175 }
176
177 fn run(&self) {
178 __rust_begin_short_backtrace(|| {
179 crate::runtest::run(
180 Arc::clone(&self.config),
181 &self.testpaths,
182 self.revision.as_deref(),
183 );
184 });
185 }
186}
187
188#[inline(never)]
190fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
191 let result = f();
192
193 hint::black_box(result)
195}
196
197struct RunningTest<'a> {
198 test: &'a CollectedTest,
199 join_handle: Option<thread::JoinHandle<()>>,
200}
201
202struct TestCompletion {
205 id: TestId,
206 outcome: TestOutcome,
207 stdout: Option<Vec<u8>>,
208}
209
210#[derive(Clone, Debug, PartialEq, Eq)]
211enum TestOutcome {
212 Succeeded,
213 Failed { message: Option<&'static str> },
214 Ignored,
215}
216
217impl TestOutcome {
218 fn is_failed(&self) -> bool {
219 matches!(self, Self::Failed { .. })
220 }
221}
222
223fn filter_tests(opts: &Config, tests: Vec<CollectedTest>) -> Vec<CollectedTest> {
231 let mut filtered = tests;
232
233 let matches_filter = |test: &CollectedTest, filter_str: &str| {
234 let test_name = &test.desc.name;
235 if opts.filter_exact { test_name == filter_str } else { test_name.contains(filter_str) }
236 };
237
238 if !opts.filters.is_empty() {
240 filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
241 }
242
243 if !opts.skip.is_empty() {
245 filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
246 }
247
248 filtered
249}
250
251fn get_concurrency() -> usize {
259 if let Ok(value) = env::var("RUST_TEST_THREADS") {
260 match value.parse::<NonZero<usize>>().ok() {
261 Some(n) => n.get(),
262 _ => panic!("RUST_TEST_THREADS is `{value}`, should be a positive integer."),
263 }
264 } else {
265 thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
266 }
267}
268
269pub(crate) struct CollectedTest {
271 pub(crate) desc: CollectedTestDesc,
272 pub(crate) config: Arc<Config>,
273 pub(crate) testpaths: TestPaths,
274 pub(crate) revision: Option<String>,
275}
276
277pub(crate) struct CollectedTestDesc {
279 pub(crate) name: String,
280 pub(crate) ignore: bool,
281 pub(crate) ignore_message: Option<Cow<'static, str>>,
282 pub(crate) should_panic: ShouldPanic,
283}
284
285#[derive(Copy, Clone, Default, Debug)]
287pub enum ColorConfig {
288 #[default]
289 AutoColor,
290 AlwaysColor,
291 NeverColor,
292}
293
294#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
296pub(crate) enum ShouldPanic {
297 No,
298 Yes,
299}