1use crate::args::{Arguments, TimeThreshold};
2use crate::bench::AsyncBencher;
3use crate::execution::{DepWireBytes, TestExecution, TestSuiteExecution};
4use crate::internal;
5use crate::internal::{
6 generate_tests, get_ensure_time, CapturedOutput, CloneableCodec, FailureCause,
7 FlakinessControl, HostedRpcChannel, HostedRpcError, HostedRpcOwnerCell, HostedRpcTransport,
8 InProcessHostedRpcTransport, RegisteredTest, RpcFactory, SuiteResult, TestFunction, TestResult,
9 WorkerReconstructor,
10};
11use crate::ipc::{
12 ipc_name, read_frame_async, write_frame_async, HostedRpcReplyBody, IpcCommand, IpcResponse,
13};
14use crate::output::{test_runner_output, TestRunnerOutput};
15use desert_rust::{deserialize, serialize_to_byte_vec};
16use futures::FutureExt;
17use interprocess::local_socket::tokio::prelude::*;
18use interprocess::local_socket::tokio::{Listener, Stream};
19use interprocess::local_socket::{GenericNamespaced, ListenerOptions};
20use std::any::Any;
21use std::collections::HashMap;
22use std::collections::VecDeque;
23use std::future::Future;
24use std::panic::AssertUnwindSafe;
25use std::pin::Pin;
26use std::process::{ExitCode, Stdio};
27use std::sync::atomic::{AtomicU64, Ordering};
28use std::sync::Arc;
29use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
30use tokio::process::{Child, Command};
31use tokio::spawn;
32use tokio::sync::Mutex;
33use tokio::task::{spawn_blocking, JoinHandle, JoinSet};
34use tokio::time::Instant;
35use uuid::Uuid;
36
37pub fn test_runner() -> ExitCode {
38 tokio::runtime::Builder::new_multi_thread()
39 .enable_all()
40 .build()
41 .unwrap()
42 .block_on(async_test_runner())
43}
44
45#[allow(clippy::await_holding_lock)]
46async fn async_test_runner() -> ExitCode {
47 crate::panic_hook::install_panic_hook();
48 let mut args = Arguments::from_args();
49 if let Some(idx) = args.worker_index {
53 crate::worker::set_worker_index(idx);
54 }
55 let output = test_runner_output(&args);
59
60 let registered_tests = internal::REGISTERED_TESTS.lock().unwrap();
61 let registered_dependency_constructors =
62 internal::REGISTERED_DEPENDENCY_CONSTRUCTORS.lock().unwrap();
63 let registered_testsuite_props = internal::REGISTERED_TESTSUITE_PROPS.lock().unwrap();
64 let registered_test_generators = internal::REGISTERED_TEST_GENERATORS.lock().unwrap();
65
66 let generated_tests = generate_tests(®istered_test_generators).await;
67
68 let all_tests: Vec<RegisteredTest> = registered_tests
69 .iter()
70 .cloned()
71 .chain(generated_tests)
72 .collect();
73
74 if args.list {
75 let tests_with_props =
79 internal::apply_suite_props_to_tests(&all_tests, ®istered_testsuite_props);
80 output.test_list(&tests_with_props);
81 ExitCode::SUCCESS
82 } else {
83 let mut remaining_retries = args.flaky_run.unwrap_or(1);
84
85 let mut exit_code = ExitCode::from(101);
86 while remaining_retries > 0 {
87 let (mut execution, filtered_tests) = TestSuiteExecution::construct(
88 &args,
89 registered_dependency_constructors.as_slice(),
90 &all_tests,
91 registered_testsuite_props.as_slice(),
92 );
93 args.finalize_for_execution(&execution, output.clone());
94 let mut host_capture = crate::host_capture::install_if_needed(&args);
98 let host_capture_epoch: Option<std::time::Instant> =
99 host_capture.as_ref().map(|hc| hc.epoch());
100 let host_capture_epoch_wall: Option<std::time::SystemTime> =
101 host_capture.as_ref().map(|hc| hc.epoch_wall());
102 let is_top_level_parent = args.is_top_level_parent();
103 let has_selected_tests = execution.remaining() > 0;
104 let needs_parent_shared = execution.has_cloneable_dependencies()
109 || execution.has_hosted_dependencies()
110 || execution.has_hosted_rpc_dependencies();
111 let parent_shared = if is_top_level_parent && has_selected_tests && needs_parent_shared
112 {
113 execution.collect_parent_shared_dependencies_async().await
114 } else {
115 crate::execution::ParentSharedDependencies {
116 cloneable_wire_bytes: Vec::new(),
117 cloneable_local_values: Vec::new(),
118 hosted_descriptor_bytes: Vec::new(),
119 hosted_owners: Vec::new(),
120 hosted_rpc_owner_cells: Vec::new(),
121 parent_constructed_shared_values: Vec::new(),
122 }
123 };
124 let cloneable_wire_bytes = parent_shared.cloneable_wire_bytes;
125 let cloneable_local_values = parent_shared.cloneable_local_values;
126 let hosted_descriptor_bytes = parent_shared.hosted_descriptor_bytes;
127 let _hosted_owners = parent_shared.hosted_owners;
128 let hosted_rpc_owner_cells: HashMap<String, Arc<HostedRpcOwnerCell>> =
129 parent_shared.hosted_rpc_owner_cells.into_iter().collect();
130 let parent_constructed_shared_values = parent_shared.parent_constructed_shared_values;
131 let rpc_factories: HashMap<String, RpcFactory> = registered_dependency_constructors
135 .iter()
136 .filter_map(|d| {
137 if d.scope == crate::internal::DepScope::HostedRpc {
138 d.rpc_factory
139 .as_ref()
140 .map(|f| (d.qualified_id(), f.clone()))
141 } else {
142 None
143 }
144 })
145 .collect();
146 let cloneable_codecs: HashMap<String, (CloneableCodec, WorkerReconstructor)> =
154 registered_dependency_constructors
155 .iter()
156 .filter_map(|d| {
157 let codec_opt = match d.scope {
158 crate::internal::DepScope::Cloneable => d.cloneable_codec.as_ref(),
159 crate::internal::DepScope::Hosted => d.hosted_codec.as_ref(),
160 _ => None,
161 };
162 match (codec_opt, &d.worker_fn) {
163 (Some(codec), Some(worker_fn)) => {
164 Some((d.qualified_id(), (codec.clone(), worker_fn.clone())))
165 }
166 _ => None,
167 }
168 })
169 .collect();
170 if is_top_level_parent && !args.spawn_workers && !cloneable_local_values.is_empty() {
180 apply_cloneable_values_locally(&mut execution, &cloneable_local_values);
181 }
182 if is_top_level_parent && !args.spawn_workers && !hosted_descriptor_bytes.is_empty() {
190 apply_hosted_descriptors_locally(
191 &mut execution,
192 &cloneable_codecs,
193 &hosted_descriptor_bytes,
194 )
195 .await;
196 }
197 if is_top_level_parent && !args.spawn_workers && !hosted_rpc_owner_cells.is_empty() {
202 install_local_hosted_rpc_stubs(
203 &mut execution,
204 &rpc_factories,
205 &hosted_rpc_owner_cells,
206 );
207 }
208 if is_top_level_parent
215 && !args.spawn_workers
216 && !parent_constructed_shared_values.is_empty()
217 {
218 apply_parent_constructed_shared_values_locally(
219 &mut execution,
220 &parent_constructed_shared_values,
221 );
222 }
223 if args.spawn_workers {
224 execution.skip_creating_dependencies();
225 }
226
227 let count = execution.remaining();
232 let results = Arc::new(Mutex::new(Vec::with_capacity(count)));
233 let host_windows: Arc<Mutex<Vec<crate::host_capture::HostWindow>>> =
238 Arc::new(Mutex::new(Vec::with_capacity(count)));
239
240 let start = Instant::now();
241 output.start_suite(&filtered_tests);
242
243 let execution = Arc::new(Mutex::new(execution));
244 let cloneable_wire_bytes = Arc::new(cloneable_wire_bytes);
245 let hosted_descriptor_bytes = Arc::new(hosted_descriptor_bytes);
246 let cloneable_codecs = Arc::new(cloneable_codecs);
247 let rpc_factories = Arc::new(rpc_factories);
248 let hosted_rpc_owner_cells = Arc::new(hosted_rpc_owner_cells);
249 let mut join_set = JoinSet::new();
250 let threads = args.test_threads().get();
251
252 for worker_idx in 0..threads {
253 let execution_clone = execution.clone();
254 let output_clone = output.clone();
255 let mut args_clone = args.clone();
258 if args_clone.spawn_workers {
259 args_clone.worker_index = Some(worker_idx);
260 }
261 let results_clone = results.clone();
262 let host_windows_clone = host_windows.clone();
263 let wire_bytes_clone = cloneable_wire_bytes.clone();
264 let hosted_bytes_clone = hosted_descriptor_bytes.clone();
265 let codecs_clone = cloneable_codecs.clone();
266 let rpc_factories_clone = rpc_factories.clone();
267 let hosted_rpc_owner_cells_clone = hosted_rpc_owner_cells.clone();
268 let handle = tokio::runtime::Handle::current();
269 join_set.spawn_blocking(move || {
270 handle.block_on(test_thread(
271 args_clone,
272 execution_clone,
273 output_clone,
274 count,
275 results_clone,
276 host_windows_clone,
277 wire_bytes_clone,
278 hosted_bytes_clone,
279 codecs_clone,
280 rpc_factories_clone,
281 hosted_rpc_owner_cells_clone,
282 host_capture_epoch,
283 ))
284 });
285 }
286
287 while let Some(res) = join_set.join_next().await {
288 res.expect("Failed to join task");
289 }
290
291 drop(execution);
292
293 let mut results = results.lock().await;
294 if is_top_level_parent {
295 assert_eq!(
296 results.len(),
297 count,
298 "IPC worker exhaustion stranded tests in the parent execution plan"
299 );
300 }
301 drop(hosted_rpc_owner_cells);
309 drop(_hosted_owners);
310
311 if let Some(hc) = host_capture.take() {
315 let epoch_wall = host_capture_epoch_wall.unwrap_or_else(|| hc.epoch_wall());
316 let records = hc.finalize();
317 let windows = host_windows.lock().await;
318 let windows_indexed: Vec<(usize, crate::host_capture::HostWindow)> =
319 windows.iter().copied().enumerate().collect();
320 crate::host_capture::attribute_records_to_tests(
321 epoch_wall,
322 &records,
323 &windows_indexed,
324 &mut results,
325 );
326 }
327 output.finished_suite(&all_tests, &results, start.elapsed());
328 exit_code = SuiteResult::exit_code(&results);
329
330 if !is_top_level_parent || exit_code == ExitCode::SUCCESS {
335 break;
336 } else {
337 remaining_retries -= 1;
338 }
339 }
340 exit_code
341 }
342}
343
344#[allow(clippy::too_many_arguments)]
345async fn test_thread(
346 args: Arguments,
347 execution: Arc<Mutex<TestSuiteExecution>>,
348 output: Arc<dyn TestRunnerOutput>,
349 count: usize,
350 results: Arc<Mutex<Vec<(RegisteredTest, TestResult)>>>,
351 host_windows: Arc<Mutex<Vec<crate::host_capture::HostWindow>>>,
352 cloneable_wire_bytes: Arc<Vec<DepWireBytes>>,
353 hosted_descriptor_bytes: Arc<Vec<DepWireBytes>>,
354 cloneable_codecs: Arc<HashMap<String, (CloneableCodec, WorkerReconstructor)>>,
355 rpc_factories: Arc<HashMap<String, RpcFactory>>,
356 hosted_rpc_owner_cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>,
357 host_capture_epoch: Option<std::time::Instant>,
358) {
359 let mut worker = spawn_worker_if_needed(&args).await;
360 if let Some(worker) = worker.as_mut() {
365 worker.set_hosted_rpc_owner_cells(hosted_rpc_owner_cells.clone());
366 }
367 let connection_arc = if let Some(ref name) = args.ipc {
368 let name = ipc_name(name.clone());
369 let stream = Stream::connect(name)
370 .await
371 .expect("Failed to connect to IPC socket");
372 Some(Arc::new(Mutex::new(stream)))
373 } else {
374 None
375 };
376
377 if let Some(worker) = worker.as_mut() {
378 for (dep_id, wire_bytes) in cloneable_wire_bytes.iter() {
379 worker
380 .provide_cloneable(dep_id.clone(), wire_bytes.clone())
381 .await;
382 }
383 for (dep_id, descriptor_bytes) in hosted_descriptor_bytes.iter() {
385 worker
386 .provide_hosted_descriptor(dep_id.clone(), descriptor_bytes.clone())
387 .await;
388 }
389 }
390
391 if let Some(connection) = connection_arc.as_ref() {
396 if !rpc_factories.is_empty() {
397 install_worker_subprocess_hosted_rpc_stubs(
398 &execution,
399 &rpc_factories,
400 connection.clone(),
401 )
402 .await;
403 }
404 }
405
406 let mut expected_test = None;
407 let mut owned_worker_exhausted = false;
408
409 'test_loop: loop {
410 let is_ipc_worker = connection_arc.is_some();
411 if crate::ipc::test_loop_should_exit(
412 is_ipc_worker,
413 is_done(&execution).await,
414 owned_worker_exhausted,
415 ) {
416 break;
417 }
418
419 if let Some(connection) = connection_arc.as_ref() {
420 while expected_test.is_none() {
421 let mut conn = connection.lock().await;
422 let command_bytes = read_frame_async(&mut *conn)
423 .await
424 .expect("Failed to read IPC command frame");
425 drop(conn);
426 let command: IpcCommand =
427 deserialize(&command_bytes).expect("Failed to decode IPC command");
428
429 match command {
430 IpcCommand::RunTest {
431 name,
432 crate_name,
433 module_path,
434 } => {
435 expected_test = Some((name, crate_name, module_path));
436 }
437 IpcCommand::Shutdown => break 'test_loop,
438 IpcCommand::ProvideCloneable { dep_id, wire_bytes } => {
439 apply_provided_wire_bytes(
441 &execution,
442 &cloneable_codecs,
443 &dep_id,
444 &wire_bytes,
445 "ProvideCloneable",
446 )
447 .await;
448 let response = IpcResponse::CloneableAccepted { dep_id };
449 let msg = serialize_to_byte_vec(&response)
450 .expect("Failed to encode IPC response");
451 let mut conn = connection.lock().await;
452 write_frame_async(&mut *conn, &msg)
453 .await
454 .expect("Failed to write IPC response frame");
455 }
456 IpcCommand::ProvideHostedDescriptor { dep_id, wire_bytes } => {
457 apply_provided_wire_bytes(
461 &execution,
462 &cloneable_codecs,
463 &dep_id,
464 &wire_bytes,
465 "ProvideHostedDescriptor",
466 )
467 .await;
468 let response = IpcResponse::HostedDescriptorAccepted { dep_id };
469 let msg = serialize_to_byte_vec(&response)
470 .expect("Failed to encode IPC response");
471 let mut conn = connection.lock().await;
472 write_frame_async(&mut *conn, &msg)
473 .await
474 .expect("Failed to write IPC response frame");
475 }
476 IpcCommand::HostedRpcReply { .. } => {
477 panic!(
484 "unexpected `HostedRpcReply` while waiting for the next \
485 between-tests command in the tokio worker subprocess: a \
486 stub call must have left a reply on the wire without \
487 draining it inline"
488 );
489 }
490 }
491 }
492 }
493
494 if let Some(next) = pick_next(&execution).await {
495 let skip = if let Some((name, crate_name, module_path)) = &expected_test {
496 next.test.name != *name
497 || next.test.crate_name != *crate_name
498 || next.test.module_path != *module_path
499 } else {
500 false
501 };
502
503 if !skip {
504 expected_test = None;
505
506 let ensure_time = get_ensure_time(&args, &next.test);
507
508 let window_start = std::time::Instant::now();
516
517 output.start_running_test(&next.test, next.index, count);
518 let result = run_test(
519 output.clone(),
520 next.index,
521 count,
522 args.nocapture,
523 args.include_ignored,
524 ensure_time,
525 next.deps.clone(),
526 &next.test,
527 &mut worker,
528 )
529 .await;
530 owned_worker_exhausted = worker
531 .as_ref()
532 .map(|worker| worker.exhausted)
533 .unwrap_or(false);
534 output.finished_running_test(&next.test, next.index, count, &result);
535 let window_end = std::time::Instant::now();
536
537 if let Some(connection) = connection_arc.as_ref() {
538 let finish_marker = Uuid::new_v4().to_string();
539 let finish_marker_line = format!("{finish_marker}\n");
540 let mut stdout = tokio::io::stdout();
541 stdout
542 .write_all(finish_marker_line.as_bytes())
543 .await
544 .unwrap();
545 stdout.flush().await.unwrap();
546
547 let mut stderr = tokio::io::stderr();
548 stderr
549 .write_all(finish_marker_line.as_bytes())
550 .await
551 .unwrap();
552 stderr.flush().await.unwrap();
553
554 let response = IpcResponse::TestFinished {
555 result: (&result).into(),
556 finish_marker,
557 worker_exhausted: is_done(&execution).await,
558 };
559 let msg =
560 serialize_to_byte_vec(&response).expect("Failed to encode IPC response");
561 let mut conn = connection.lock().await;
562 write_frame_async(&mut *conn, &msg)
563 .await
564 .expect("Failed to write IPC response frame");
565 }
566
567 let window = crate::host_capture::HostWindow::from_instants(
571 host_capture_epoch,
572 window_start,
573 window_end,
574 )
575 .unwrap_or(crate::host_capture::HostWindow {
576 start: std::time::Duration::ZERO,
577 end: std::time::Duration::ZERO,
578 });
579 let mut results_guard = results.lock().await;
580 let mut windows_guard = host_windows.lock().await;
581 results_guard.push((next.test.clone(), result));
582 windows_guard.push(window);
583 }
584 }
585 }
586
587 if let Some(worker) = worker {
588 worker.shutdown().await;
589 }
590}
591
592async fn is_done(execution: &Arc<Mutex<TestSuiteExecution>>) -> bool {
593 let execution = execution.lock().await;
594 execution.is_done()
595}
596
597async fn apply_provided_wire_bytes(
607 execution: &Arc<Mutex<TestSuiteExecution>>,
608 wire_codecs: &HashMap<String, (CloneableCodec, WorkerReconstructor)>,
609 dep_id: &str,
610 wire_bytes: &[u8],
611 source_command: &str,
612) {
613 let (codec, worker_fn) = wire_codecs.get(dep_id).unwrap_or_else(|| {
614 panic!("{source_command} referenced unknown wire-shipped dep '{dep_id}'")
615 });
616
617 let wire_payload = (codec.from_wire_bytes)(wire_bytes);
618 let empty_deps: Arc<dyn internal::DependencyView + Send + Sync> =
619 Arc::new(HashMap::<String, Arc<dyn Any + Send + Sync>>::new());
620 let reconstructed = match worker_fn {
621 WorkerReconstructor::Sync(f) => f(wire_payload, empty_deps),
622 WorkerReconstructor::Async(f) => f(wire_payload, empty_deps).await,
623 };
624
625 let mut execution = execution.lock().await;
626 let applied = execution.provide_cloneable_value(dep_id, reconstructed);
627 assert!(
628 applied,
629 "{source_command} for dep '{dep_id}' did not match any registered dep in this worker"
630 );
631}
632
633fn apply_cloneable_values_locally(
648 execution: &mut TestSuiteExecution,
649 cloneable_local_values: &[(String, Arc<dyn Any + Send + Sync>)],
650) {
651 for (dep_id, value) in cloneable_local_values {
652 let applied = execution.provide_cloneable_value(dep_id, value.clone());
653 assert!(
654 applied,
655 "Cloneable dep '{dep_id}' could not be pre-populated locally"
656 );
657 }
658}
659
660fn apply_parent_constructed_shared_values_locally(
666 execution: &mut TestSuiteExecution,
667 values: &[(String, Arc<dyn Any + Send + Sync>)],
668) {
669 for (dep_id, value) in values {
670 let applied = execution.provide_materialized_shared_value(dep_id, value.clone());
671 assert!(
672 applied,
673 "Shared/PerWorker dep '{dep_id}' could not be pre-populated locally"
674 );
675 }
676}
677
678async fn apply_hosted_descriptors_locally(
691 execution: &mut TestSuiteExecution,
692 wire_codecs: &HashMap<String, (CloneableCodec, WorkerReconstructor)>,
693 descriptor_bytes: &[DepWireBytes],
694) {
695 for (dep_id, wire_bytes) in descriptor_bytes {
696 let (codec, worker_fn) = wire_codecs.get(dep_id).unwrap_or_else(|| {
697 panic!("Hosted dep '{dep_id}' missing codec/worker_fn for local handle reconstruction")
698 });
699 let wire_payload = (codec.from_wire_bytes)(wire_bytes);
700 let empty_deps: Arc<dyn internal::DependencyView + Send + Sync> =
701 Arc::new(HashMap::<String, Arc<dyn Any + Send + Sync>>::new());
702 let reconstructed = match worker_fn {
703 WorkerReconstructor::Sync(f) => f(wire_payload, empty_deps),
704 WorkerReconstructor::Async(f) => f(wire_payload, empty_deps).await,
705 };
706 let applied = execution.provide_cloneable_value(dep_id, reconstructed);
707 assert!(
708 applied,
709 "Hosted dep '{dep_id}' could not be pre-populated locally"
710 );
711 }
712}
713
714async fn pick_next(execution: &Arc<Mutex<TestSuiteExecution>>) -> Option<TestExecution> {
715 let mut execution = execution.lock().await;
716 execution.pick_next().await
717}
718
719async fn run_with_flakiness_control<F>(
720 output: Arc<dyn TestRunnerOutput>,
721 test_description: &RegisteredTest,
722 idx: usize,
723 count: usize,
724 test: F,
725) -> Result<Result<(), FailureCause>, Box<dyn Any + Send>>
726where
727 F: Fn(
728 Instant,
729 )
730 -> Pin<Box<dyn Future<Output = Result<Result<(), FailureCause>, Box<dyn Any + Send>>>>>
731 + Send
732 + Sync,
733{
734 match &test_description.props.flakiness_control {
735 FlakinessControl::None => {
736 let start = Instant::now();
737 test(start).await
738 }
739 FlakinessControl::ProveNonFlaky(tries) => {
740 for n in 0..*tries {
741 if n > 0 {
742 output.repeat_running_test(
743 test_description,
744 idx,
745 count,
746 n + 1,
747 *tries,
748 "to ensure test is not flaky",
749 );
750 }
751 let start = Instant::now();
752 match test(start).await {
753 Ok(Ok(())) => {}
754 Ok(Err(e)) => return Ok(Err(e)),
755 Err(e) => return Err(e),
756 };
757 }
758 Ok(Ok(()))
759 }
760 FlakinessControl::RetryKnownFlaky(max_retries) => {
761 let mut tries = 1;
762 loop {
763 let start = Instant::now();
764 let result = test(start).await;
765
766 if result.is_err() && tries < *max_retries {
767 tries += 1;
768 output.repeat_running_test(
769 test_description,
770 idx,
771 count,
772 tries,
773 *max_retries,
774 "because test is known to be flaky",
775 );
776 } else {
777 break result;
778 }
779 }
780 }
781 }
782}
783
784#[allow(clippy::too_many_arguments)]
785async fn run_test(
786 output: Arc<dyn TestRunnerOutput>,
787 idx: usize,
788 count: usize,
789 nocapture: bool,
790 include_ignored: bool,
791 ensure_time: Option<TimeThreshold>,
792 dependency_view: Arc<dyn internal::DependencyView + Send + Sync>,
793 test: &RegisteredTest,
794 worker: &mut Option<Worker>,
795) -> TestResult {
796 if test.props.is_ignored && !include_ignored {
797 TestResult::ignored()
798 } else if let Some(worker) = worker.as_mut() {
799 worker.run_test(nocapture, test).await
800 } else {
801 let start = Instant::now();
802 let test = test.clone();
803 match &test.run {
804 TestFunction::Sync(_) => {
805 let handle = spawn_blocking(move || {
806 let test = test.clone();
807 crate::sync::run_sync_test_function(
808 output,
809 &test,
810 idx,
811 count,
812 ensure_time,
813 dependency_view,
814 )
815 });
816 handle.await.unwrap_or_else(|join_error| {
817 TestResult::failed(
818 start.elapsed(),
819 FailureCause::HarnessError(format!(
820 "Failed joining test task: {join_error}"
821 )),
822 )
823 })
824 }
825 TestFunction::Async(test_fn) => {
826 let timeout = test.props.timeout;
827 let test_fn = test_fn.clone();
828 let detached_panic_policy = test.props.detached_panic_policy.clone();
829 let result = run_with_flakiness_control(output, &test, idx, count, |start| {
830 let dependency_view = dependency_view.clone();
831 let test_fn = test_fn.clone();
832 Box::pin(async move {
833 let test_id = crate::panic_hook::next_test_id();
834 crate::panic_hook::set_current_test_id(test_id);
835 crate::panic_hook::create_detached_collector(test_id);
836 let result = AssertUnwindSafe(Box::pin(async move {
837 match timeout {
838 None => test_fn(dependency_view).await,
839 Some(duration) => {
840 let result =
841 tokio::time::timeout(duration, test_fn(dependency_view))
842 .await;
843 match result {
844 Ok(result) => result,
845 Err(_) => {
846 return Err(FailureCause::HarnessError(
847 "Test timed out".to_string(),
848 ))
849 }
850 }
851 }
852 }
853 .into_result()?;
854 if let Some(ensure_time) = ensure_time {
855 let elapsed = start.elapsed();
856 if ensure_time.is_critical(&elapsed) {
857 return Err(FailureCause::HarnessError(format!(
858 "Test run time exceeds critical threshold: {elapsed:?}"
859 )));
860 }
861 }
862 Ok(())
863 }))
864 .catch_unwind()
865 .await;
866 result
867 })
868 })
869 .await;
870 let mut test_result =
871 TestResult::from_result(&test.props.should_panic, start.elapsed(), result);
872 if let Some(test_id) = crate::panic_hook::current_test_id() {
873 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
874 let panics = match collector.lock() {
875 Ok(p) => p,
876 Err(poisoned) => poisoned.into_inner(),
877 };
878 if !panics.is_empty()
879 && detached_panic_policy == internal::DetachedPanicPolicy::FailTest
880 && test_result.is_passed()
881 {
882 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
883 test_result = TestResult::failed(
884 start.elapsed(),
885 FailureCause::Panic(internal::PanicCause {
886 message: Some(format!(
887 "Detached task(s) panicked:\n{}",
888 messages.join("\n---\n")
889 )),
890 location: panics.first().and_then(|p| p.location.clone()),
891 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
892 }),
893 );
894 }
895 }
896 }
897 crate::panic_hook::clear_current_test_id();
898 test_result
899 }
900 TestFunction::SyncBench(_) => {
901 let handle = spawn_blocking(move || {
902 let test = test.clone();
903 crate::sync::run_sync_test_function(
904 output,
905 &test,
906 idx,
907 count,
908 ensure_time,
909 dependency_view,
910 )
911 });
912 handle.await.unwrap_or_else(|join_error| {
913 TestResult::failed(
914 start.elapsed(),
915 FailureCause::HarnessError(format!(
916 "Failed joining test task: {join_error}"
917 )),
918 )
919 })
920 }
921 TestFunction::AsyncBench(bench_fn) => {
922 let mut bencher = AsyncBencher::new();
923 let test_id = crate::panic_hook::next_test_id();
924 crate::panic_hook::set_current_test_id(test_id);
925 let result = AssertUnwindSafe(async move {
926 bench_fn(&mut bencher, dependency_view).await;
927 (
928 bencher
929 .summary()
930 .expect("iter() was not called in bench function"),
931 bencher.bytes,
932 )
933 })
934 .catch_unwind()
935 .await;
936 let bytes = result.as_ref().map(|(_, bytes)| *bytes).unwrap_or_default();
937 let test_result = TestResult::from_summary(
938 &test.props.should_panic,
939 start.elapsed(),
940 result.map(|(summary, _)| summary),
941 bytes,
942 );
943 crate::panic_hook::clear_current_test_id();
944 test_result
945 }
946 }
947 }
948}
949
950struct Worker {
951 _listener: Listener,
952 process: Child,
953 _out_handle: JoinHandle<()>,
954 _err_handle: JoinHandle<()>,
955 out_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
956 err_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
957 capture_enabled: Arc<Mutex<bool>>,
958 connection: Stream,
959 exhausted: bool,
960 hosted_rpc_owner_cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>,
964}
965
966impl Worker {
967 fn set_hosted_rpc_owner_cells(&mut self, cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>) {
971 self.hosted_rpc_owner_cells = cells;
972 }
973
974 async fn handle_hosted_rpc_call(
980 &mut self,
981 dump_on_ipc_failure: &DumpOnFailure,
982 request_id: u64,
983 dep_id: String,
984 method_idx: u32,
985 args_bytes: Vec<u8>,
986 ) {
987 let body = match self.hosted_rpc_owner_cells.get(&dep_id) {
988 Some(cell) => match cell.dispatch_async(method_idx, &args_bytes).await {
994 Ok(result_bytes) => HostedRpcReplyBody::Ok { result_bytes },
995 Err(message) => HostedRpcReplyBody::Err { message },
996 },
997 None => HostedRpcReplyBody::Err {
998 message: format!(
999 "HostedRpc dispatch: unknown dep id '{dep_id}' in parent owner-cell map"
1000 ),
1001 },
1002 };
1003 let reply = IpcCommand::HostedRpcReply { request_id, body };
1004 let msg = serialize_to_byte_vec(&reply).expect("Failed to encode HostedRpcReply");
1005 dump_on_ipc_failure
1006 .run(write_frame_async(&mut self.connection, &msg).await)
1007 .await;
1008 }
1009
1010 pub async fn run_test(&mut self, nocapture: bool, test: &RegisteredTest) -> TestResult {
1011 let mut capture_enabled = self.capture_enabled.lock().await;
1012 *capture_enabled = test.props.capture_control.requires_capturing(!nocapture);
1013 drop(capture_enabled);
1014
1015 let cmd = IpcCommand::RunTest {
1017 name: test.name.clone(),
1018 crate_name: test.crate_name.clone(),
1019 module_path: test.module_path.clone(),
1020 };
1021
1022 let dump_on_ipc_failure = self.dump_on_failure();
1023
1024 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
1025 dump_on_ipc_failure
1026 .run(write_frame_async(&mut self.connection, &msg).await)
1027 .await;
1028
1029 let response = loop {
1030 let response_bytes = dump_on_ipc_failure
1031 .run(read_frame_async(&mut self.connection).await)
1032 .await;
1033 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
1034 match response {
1035 IpcResponse::TestFinished { .. } => break response,
1036 IpcResponse::CloneableAccepted { .. }
1037 | IpcResponse::HostedDescriptorAccepted { .. } => continue,
1038 IpcResponse::HostedRpcCall {
1039 request_id,
1040 dep_id,
1041 method_idx,
1042 args_bytes,
1043 } => {
1044 self.handle_hosted_rpc_call(
1045 &dump_on_ipc_failure,
1046 request_id,
1047 dep_id,
1048 method_idx,
1049 args_bytes,
1050 )
1051 .await;
1052 continue;
1053 }
1054 }
1055 };
1056
1057 let IpcResponse::TestFinished {
1058 result,
1059 finish_marker,
1060 worker_exhausted,
1061 } = response
1062 else {
1063 unreachable!("loop only breaks on TestFinished")
1064 };
1065 self.exhausted = worker_exhausted;
1066
1067 if test.props.capture_control.requires_capturing(!nocapture) {
1068 let out_lines: Vec<_> =
1069 Self::drain_until(self.out_lines.clone(), finish_marker.clone()).await;
1070 let err_lines: Vec<_> =
1071 Self::drain_until(self.err_lines.clone(), finish_marker.clone()).await;
1072 result.into_test_result(out_lines, err_lines)
1073 } else {
1074 result.into_test_result(Vec::new(), Vec::new())
1075 }
1076 }
1077
1078 async fn shutdown(mut self) {
1082 let msg = serialize_to_byte_vec(&IpcCommand::Shutdown)
1083 .expect("Failed to encode IPC shutdown command");
1084 let dump_on_ipc_failure = self.dump_on_failure();
1085 dump_on_ipc_failure
1086 .run(write_frame_async(&mut self.connection, &msg).await)
1087 .await;
1088
1089 self.process
1090 .wait()
1091 .await
1092 .expect("Failed to wait for worker process");
1093 }
1094
1095 async fn provide_cloneable(&mut self, dep_id: String, wire_bytes: Vec<u8>) {
1098 let dump_on_ipc_failure = self.dump_on_failure();
1099 let cmd = IpcCommand::ProvideCloneable {
1100 dep_id: dep_id.clone(),
1101 wire_bytes,
1102 };
1103 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
1104 dump_on_ipc_failure
1105 .run(write_frame_async(&mut self.connection, &msg).await)
1106 .await;
1107
1108 loop {
1109 let response_bytes = dump_on_ipc_failure
1110 .run(read_frame_async(&mut self.connection).await)
1111 .await;
1112 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
1113 match response {
1114 IpcResponse::CloneableAccepted { dep_id: ack_id } => {
1115 if ack_id == dep_id {
1116 return;
1117 }
1118 }
1119 IpcResponse::HostedDescriptorAccepted { .. } => {
1120 }
1122 IpcResponse::TestFinished { .. } => {
1123 }
1125 IpcResponse::HostedRpcCall {
1126 request_id,
1127 dep_id: rpc_dep_id,
1128 method_idx,
1129 args_bytes,
1130 } => {
1131 self.handle_hosted_rpc_call(
1136 &dump_on_ipc_failure,
1137 request_id,
1138 rpc_dep_id,
1139 method_idx,
1140 args_bytes,
1141 )
1142 .await;
1143 }
1144 }
1145 }
1146 }
1147
1148 async fn provide_hosted_descriptor(&mut self, dep_id: String, wire_bytes: Vec<u8>) {
1150 let dump_on_ipc_failure = self.dump_on_failure();
1151 let cmd = IpcCommand::ProvideHostedDescriptor {
1152 dep_id: dep_id.clone(),
1153 wire_bytes,
1154 };
1155 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
1156 dump_on_ipc_failure
1157 .run(write_frame_async(&mut self.connection, &msg).await)
1158 .await;
1159
1160 loop {
1161 let response_bytes = dump_on_ipc_failure
1162 .run(read_frame_async(&mut self.connection).await)
1163 .await;
1164 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
1165 match response {
1166 IpcResponse::HostedDescriptorAccepted { dep_id: ack_id } => {
1167 if ack_id == dep_id {
1168 return;
1169 }
1170 }
1171 IpcResponse::CloneableAccepted { .. } => {
1172 }
1174 IpcResponse::TestFinished { .. } => {
1175 }
1177 IpcResponse::HostedRpcCall {
1178 request_id,
1179 dep_id: rpc_dep_id,
1180 method_idx,
1181 args_bytes,
1182 } => {
1183 self.handle_hosted_rpc_call(
1186 &dump_on_ipc_failure,
1187 request_id,
1188 rpc_dep_id,
1189 method_idx,
1190 args_bytes,
1191 )
1192 .await;
1193 }
1194 }
1195 }
1196 }
1197
1198 fn dump_on_failure(&self) -> DumpOnFailure {
1199 DumpOnFailure {
1200 out_lines: self.out_lines.clone(),
1201 err_lines: self.err_lines.clone(),
1202 }
1203 }
1204
1205 async fn drain_until(
1206 source: Arc<Mutex<VecDeque<CapturedOutput>>>,
1207 finish_marker: String,
1208 ) -> Vec<CapturedOutput> {
1209 let mut result = Vec::new();
1210 loop {
1211 let mut source = source.lock().await;
1212 while let Some(line) = source.pop_front() {
1213 if line.line() == finish_marker {
1214 return result;
1215 } else {
1216 result.push(line.clone());
1217 }
1218 }
1219 drop(source);
1220
1221 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1222 }
1223 }
1224}
1225
1226struct DumpOnFailure {
1227 out_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
1228 err_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
1229}
1230
1231impl DumpOnFailure {
1232 pub async fn run<T, E>(&self, result: Result<T, E>) -> T {
1233 match result {
1234 Ok(value) => value,
1235 Err(_error) => {
1236 let out_lines: Vec<_> = self.out_lines.lock().await.drain(..).collect();
1237 let err_lines: Vec<_> = self.err_lines.lock().await.drain(..).collect();
1238 let mut all_lines = [out_lines, err_lines].concat();
1239 all_lines.sort();
1240
1241 use std::io::Write;
1249 let mut err = crate::host_capture::TerminalStderr;
1250 for line in all_lines {
1251 let _ = writeln!(err, "{}", line.line());
1252 }
1253 let _ = err.flush();
1254
1255 std::process::exit(1);
1256 }
1257 }
1258 }
1259}
1260
1261async fn spawn_worker_if_needed(args: &Arguments) -> Option<Worker> {
1262 if args.spawn_workers {
1263 let id = Uuid::new_v4();
1264 let name_str = format!("{id}.sock");
1265 let name = name_str
1266 .clone()
1267 .to_ns_name::<GenericNamespaced>()
1268 .expect("Invalid local socket name");
1269 let opts = ListenerOptions::new().name(name.clone());
1270 let listener = opts
1271 .create_tokio()
1272 .expect("Failed to create local socket listener");
1273
1274 let exe = std::env::current_exe().expect("Failed to get current executable path");
1275
1276 let mut args = args.clone();
1277 args.ipc = Some(name_str);
1278 args.spawn_workers = false;
1279 args.logfile = None;
1280 let args = args.to_args();
1281
1282 let mut process = Command::new(exe)
1283 .args(args)
1284 .stdin(Stdio::inherit())
1285 .stderr(Stdio::piped())
1286 .stdout(Stdio::piped())
1287 .spawn()
1288 .expect("Failed to spawn worker process");
1289
1290 let stdout = process.stdout.take().unwrap();
1291 let stderr = process.stderr.take().unwrap();
1292
1293 let out_lines = Arc::new(Mutex::new(VecDeque::new()));
1294 let err_lines = Arc::new(Mutex::new(VecDeque::new()));
1295 let capture_enabled = Arc::new(Mutex::new(true));
1296
1297 let out_lines_clone = out_lines.clone();
1298 let capture_enabled_clone = capture_enabled.clone();
1299 let out_handle = spawn(async move {
1300 let reader = BufReader::new(stdout);
1301 let mut lines = reader.lines();
1302 while let Some(line) = lines
1303 .next_line()
1304 .await
1305 .expect("Failed to read from worker stdout")
1306 {
1307 if *capture_enabled_clone.lock().await {
1308 out_lines_clone
1309 .lock()
1310 .await
1311 .push_back(CapturedOutput::stdout(line));
1312 } else {
1313 use std::io::Write;
1319 let mut out = crate::host_capture::TerminalStdout;
1320 let _ = writeln!(out, "{line}");
1321 let _ = out.flush();
1322 }
1323 }
1324 });
1325
1326 let err_lines_clone = err_lines.clone();
1327 let capture_enabled_clone = capture_enabled.clone();
1328 let err_handle = spawn(async move {
1329 let reader = BufReader::new(stderr);
1330 let mut lines = reader.lines();
1331 while let Some(line) = lines
1332 .next_line()
1333 .await
1334 .expect("Failed to read from worker stderr")
1335 {
1336 if *capture_enabled_clone.lock().await {
1337 err_lines_clone
1338 .lock()
1339 .await
1340 .push_back(CapturedOutput::stderr(line));
1341 } else {
1342 use std::io::Write;
1346 let mut err = crate::host_capture::TerminalStderr;
1347 let _ = writeln!(err, "{line}");
1348 let _ = err.flush();
1349 }
1350 }
1351 });
1352
1353 let connection = listener
1354 .accept()
1355 .await
1356 .expect("Failed to accept connection");
1357
1358 Some(Worker {
1359 _listener: listener,
1360 process,
1361 _out_handle: out_handle,
1362 _err_handle: err_handle,
1363 out_lines,
1364 err_lines,
1365 connection,
1366 capture_enabled,
1367 exhausted: false,
1368 hosted_rpc_owner_cells: Arc::new(HashMap::new()),
1369 })
1370 } else {
1371 None
1372 }
1373}
1374
1375fn install_local_hosted_rpc_stubs(
1381 execution: &mut TestSuiteExecution,
1382 rpc_factories: &HashMap<String, RpcFactory>,
1383 owner_cells: &HashMap<String, Arc<HostedRpcOwnerCell>>,
1384) {
1385 let transport: Arc<dyn HostedRpcTransport> =
1386 Arc::new(InProcessHostedRpcTransport::new(owner_cells.clone()));
1387 for (dep_id, factory) in rpc_factories.iter() {
1388 if !owner_cells.contains_key(dep_id) {
1389 continue;
1393 }
1394 let channel = HostedRpcChannel::new(dep_id.clone(), transport.clone());
1395 let stub = (factory.build_stub)(channel);
1396 let applied = execution.provide_cloneable_value(dep_id, stub);
1397 if !applied {
1398 continue;
1404 }
1405 }
1406}
1407
1408async fn install_worker_subprocess_hosted_rpc_stubs(
1413 execution: &Arc<Mutex<TestSuiteExecution>>,
1414 rpc_factories: &HashMap<String, RpcFactory>,
1415 connection_arc: Arc<Mutex<Stream>>,
1416) {
1417 let transport: Arc<dyn HostedRpcTransport> =
1418 Arc::new(IpcHostedRpcTransport::new(connection_arc));
1419 for (dep_id, factory) in rpc_factories.iter() {
1420 let channel = HostedRpcChannel::new(dep_id.clone(), transport.clone());
1421 let stub = (factory.build_stub)(channel);
1422 let mut execution = execution.lock().await;
1423 let applied = execution.provide_cloneable_value(dep_id, stub);
1424 let _ = applied;
1427 }
1428}
1429
1430struct IpcHostedRpcTransport {
1448 connection: Arc<Mutex<Stream>>,
1449 next_request_id: AtomicU64,
1450}
1451
1452impl IpcHostedRpcTransport {
1453 fn new(connection: Arc<Mutex<Stream>>) -> Self {
1454 Self {
1455 connection,
1456 next_request_id: AtomicU64::new(0),
1457 }
1458 }
1459}
1460
1461impl HostedRpcTransport for IpcHostedRpcTransport {
1462 fn call(
1463 &self,
1464 dep_id: &str,
1465 method_idx: u32,
1466 args: Vec<u8>,
1467 ) -> Result<Vec<u8>, HostedRpcError> {
1468 let request_id = self.next_request_id.fetch_add(1, Ordering::SeqCst);
1469 let call = IpcResponse::HostedRpcCall {
1470 request_id,
1471 dep_id: dep_id.to_string(),
1472 method_idx,
1473 args_bytes: args,
1474 };
1475 let msg = serialize_to_byte_vec(&call).map_err(|e| {
1476 HostedRpcError::Transport(format!("encode HostedRpcCall failed: {e:?}"))
1477 })?;
1478
1479 let connection = self.connection.clone();
1480 let handle = tokio::runtime::Handle::current();
1481
1482 tokio::task::block_in_place(move || {
1487 handle.block_on(async move {
1488 let mut conn = connection.lock().await;
1489 write_frame_async(&mut *conn, &msg).await.map_err(|e| {
1490 HostedRpcError::Transport(format!("write HostedRpcCall failed: {e:?}"))
1491 })?;
1492 let reply_bytes = read_frame_async(&mut *conn).await.map_err(|e| {
1493 HostedRpcError::Transport(format!("read HostedRpcReply failed: {e:?}"))
1494 })?;
1495 let command: IpcCommand = deserialize(&reply_bytes).map_err(|e| {
1496 HostedRpcError::Transport(format!("decode HostedRpcReply failed: {e:?}"))
1497 })?;
1498 match command {
1499 IpcCommand::HostedRpcReply {
1500 request_id: reply_id,
1501 body,
1502 } => {
1503 if reply_id != request_id {
1504 return Err(HostedRpcError::Transport(format!(
1505 "HostedRpcReply request_id mismatch: expected {request_id}, got {reply_id}"
1506 )));
1507 }
1508 match body {
1509 HostedRpcReplyBody::Ok { result_bytes } => Ok(result_bytes),
1510 HostedRpcReplyBody::Err { message } => {
1511 Err(HostedRpcError::Dispatch(message))
1512 }
1513 }
1514 }
1515 other => Err(HostedRpcError::Transport(format!(
1516 "unexpected IpcCommand while waiting for HostedRpcReply: {other:?}"
1517 ))),
1518 }
1519 })
1520 })
1521 }
1522}