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