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 drop(hosted_rpc_owner_cells);
302 drop(_hosted_owners);
303
304 if let Some(hc) = host_capture.take() {
308 let epoch_wall = host_capture_epoch_wall.unwrap_or_else(|| hc.epoch_wall());
309 let records = hc.finalize();
310 let windows = host_windows.lock().await;
311 let windows_indexed: Vec<(usize, crate::host_capture::HostWindow)> =
312 windows.iter().copied().enumerate().collect();
313 crate::host_capture::attribute_records_to_tests(
314 epoch_wall,
315 &records,
316 &windows_indexed,
317 &mut results,
318 );
319 }
320 output.finished_suite(&all_tests, &results, start.elapsed());
321 exit_code = SuiteResult::exit_code(&results);
322
323 if exit_code == ExitCode::SUCCESS {
324 break;
325 } else {
326 remaining_retries -= 1;
327 }
328 }
329 exit_code
330 }
331}
332
333#[allow(clippy::too_many_arguments)]
334async fn test_thread(
335 args: Arguments,
336 execution: Arc<Mutex<TestSuiteExecution>>,
337 output: Arc<dyn TestRunnerOutput>,
338 count: usize,
339 results: Arc<Mutex<Vec<(RegisteredTest, TestResult)>>>,
340 host_windows: Arc<Mutex<Vec<crate::host_capture::HostWindow>>>,
341 cloneable_wire_bytes: Arc<Vec<DepWireBytes>>,
342 hosted_descriptor_bytes: Arc<Vec<DepWireBytes>>,
343 cloneable_codecs: Arc<HashMap<String, (CloneableCodec, WorkerReconstructor)>>,
344 rpc_factories: Arc<HashMap<String, RpcFactory>>,
345 hosted_rpc_owner_cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>,
346 host_capture_epoch: Option<std::time::Instant>,
347) {
348 let mut worker = spawn_worker_if_needed(&args).await;
349 if let Some(worker) = worker.as_mut() {
354 worker.set_hosted_rpc_owner_cells(hosted_rpc_owner_cells.clone());
355 }
356 let connection_arc = if let Some(ref name) = args.ipc {
357 let name = ipc_name(name.clone());
358 let stream = Stream::connect(name)
359 .await
360 .expect("Failed to connect to IPC socket");
361 Some(Arc::new(Mutex::new(stream)))
362 } else {
363 None
364 };
365
366 if let Some(worker) = worker.as_mut() {
367 for (dep_id, wire_bytes) in cloneable_wire_bytes.iter() {
368 worker
369 .provide_cloneable(dep_id.clone(), wire_bytes.clone())
370 .await;
371 }
372 for (dep_id, descriptor_bytes) in hosted_descriptor_bytes.iter() {
374 worker
375 .provide_hosted_descriptor(dep_id.clone(), descriptor_bytes.clone())
376 .await;
377 }
378 }
379
380 if let Some(connection) = connection_arc.as_ref() {
385 if !rpc_factories.is_empty() {
386 install_worker_subprocess_hosted_rpc_stubs(
387 &execution,
388 &rpc_factories,
389 connection.clone(),
390 )
391 .await;
392 }
393 }
394
395 let mut expected_test = None;
396
397 while !is_done(&execution).await {
398 if let Some(connection) = connection_arc.as_ref() {
399 while expected_test.is_none() {
400 let mut conn = connection.lock().await;
401 let command_bytes = read_frame_async(&mut *conn)
402 .await
403 .expect("Failed to read IPC command frame");
404 drop(conn);
405 let command: IpcCommand =
406 deserialize(&command_bytes).expect("Failed to decode IPC command");
407
408 match command {
409 IpcCommand::RunTest {
410 name,
411 crate_name,
412 module_path,
413 } => {
414 expected_test = Some((name, crate_name, module_path));
415 }
416 IpcCommand::ProvideCloneable { dep_id, wire_bytes } => {
417 apply_provided_wire_bytes(
419 &execution,
420 &cloneable_codecs,
421 &dep_id,
422 &wire_bytes,
423 "ProvideCloneable",
424 )
425 .await;
426 let response = IpcResponse::CloneableAccepted { dep_id };
427 let msg = serialize_to_byte_vec(&response)
428 .expect("Failed to encode IPC response");
429 let mut conn = connection.lock().await;
430 write_frame_async(&mut *conn, &msg)
431 .await
432 .expect("Failed to write IPC response frame");
433 }
434 IpcCommand::ProvideHostedDescriptor { dep_id, wire_bytes } => {
435 apply_provided_wire_bytes(
439 &execution,
440 &cloneable_codecs,
441 &dep_id,
442 &wire_bytes,
443 "ProvideHostedDescriptor",
444 )
445 .await;
446 let response = IpcResponse::HostedDescriptorAccepted { dep_id };
447 let msg = serialize_to_byte_vec(&response)
448 .expect("Failed to encode IPC response");
449 let mut conn = connection.lock().await;
450 write_frame_async(&mut *conn, &msg)
451 .await
452 .expect("Failed to write IPC response frame");
453 }
454 IpcCommand::HostedRpcReply { .. } => {
455 panic!(
462 "unexpected `HostedRpcReply` while waiting for the next \
463 between-tests command in the tokio worker subprocess: a \
464 stub call must have left a reply on the wire without \
465 draining it inline"
466 );
467 }
468 }
469 }
470 }
471
472 if let Some(next) = pick_next(&execution).await {
473 let skip = if let Some((name, crate_name, module_path)) = &expected_test {
474 next.test.name != *name
475 || next.test.crate_name != *crate_name
476 || next.test.module_path != *module_path
477 } else {
478 false
479 };
480
481 if !skip {
482 expected_test = None;
483
484 let ensure_time = get_ensure_time(&args, &next.test);
485
486 let window_start = std::time::Instant::now();
494
495 output.start_running_test(&next.test, next.index, count);
496 let result = run_test(
497 output.clone(),
498 next.index,
499 count,
500 args.nocapture,
501 args.include_ignored,
502 ensure_time,
503 next.deps.clone(),
504 &next.test,
505 &mut worker,
506 )
507 .await;
508 output.finished_running_test(&next.test, next.index, count, &result);
509 let window_end = std::time::Instant::now();
510
511 if let Some(connection) = connection_arc.as_ref() {
512 let finish_marker = Uuid::new_v4().to_string();
513 let finish_marker_line = format!("{finish_marker}\n");
514 tokio::io::stdout()
515 .write_all(finish_marker_line.as_bytes())
516 .await
517 .unwrap();
518 tokio::io::stderr()
519 .write_all(finish_marker_line.as_bytes())
520 .await
521 .unwrap();
522 tokio::io::stdout().flush().await.unwrap();
523 tokio::io::stderr().flush().await.unwrap();
524
525 let response = IpcResponse::TestFinished {
526 result: (&result).into(),
527 finish_marker,
528 };
529 let msg =
530 serialize_to_byte_vec(&response).expect("Failed to encode IPC response");
531 let mut conn = connection.lock().await;
532 write_frame_async(&mut *conn, &msg)
533 .await
534 .expect("Failed to write IPC response frame");
535 }
536
537 let window = crate::host_capture::HostWindow::from_instants(
541 host_capture_epoch,
542 window_start,
543 window_end,
544 )
545 .unwrap_or(crate::host_capture::HostWindow {
546 start: std::time::Duration::ZERO,
547 end: std::time::Duration::ZERO,
548 });
549 let mut results_guard = results.lock().await;
550 let mut windows_guard = host_windows.lock().await;
551 results_guard.push((next.test.clone(), result));
552 windows_guard.push(window);
553 }
554 }
555 }
556}
557
558async fn is_done(execution: &Arc<Mutex<TestSuiteExecution>>) -> bool {
559 let execution = execution.lock().await;
560 execution.is_done()
561}
562
563async fn apply_provided_wire_bytes(
573 execution: &Arc<Mutex<TestSuiteExecution>>,
574 wire_codecs: &HashMap<String, (CloneableCodec, WorkerReconstructor)>,
575 dep_id: &str,
576 wire_bytes: &[u8],
577 source_command: &str,
578) {
579 let (codec, worker_fn) = wire_codecs.get(dep_id).unwrap_or_else(|| {
580 panic!("{source_command} referenced unknown wire-shipped dep '{dep_id}'")
581 });
582
583 let wire_payload = (codec.from_wire_bytes)(wire_bytes);
584 let empty_deps: Arc<dyn internal::DependencyView + Send + Sync> =
585 Arc::new(HashMap::<String, Arc<dyn Any + Send + Sync>>::new());
586 let reconstructed = match worker_fn {
587 WorkerReconstructor::Sync(f) => f(wire_payload, empty_deps),
588 WorkerReconstructor::Async(f) => f(wire_payload, empty_deps).await,
589 };
590
591 let mut execution = execution.lock().await;
592 let applied = execution.provide_cloneable_value(dep_id, reconstructed);
593 assert!(
594 applied,
595 "{source_command} for dep '{dep_id}' did not match any registered dep in this worker"
596 );
597}
598
599fn apply_cloneable_values_locally(
614 execution: &mut TestSuiteExecution,
615 cloneable_local_values: &[(String, Arc<dyn Any + Send + Sync>)],
616) {
617 for (dep_id, value) in cloneable_local_values {
618 let applied = execution.provide_cloneable_value(dep_id, value.clone());
619 assert!(
620 applied,
621 "Cloneable dep '{dep_id}' could not be pre-populated locally"
622 );
623 }
624}
625
626fn apply_parent_constructed_shared_values_locally(
632 execution: &mut TestSuiteExecution,
633 values: &[(String, Arc<dyn Any + Send + Sync>)],
634) {
635 for (dep_id, value) in values {
636 let applied = execution.provide_materialized_shared_value(dep_id, value.clone());
637 assert!(
638 applied,
639 "Shared/PerWorker dep '{dep_id}' could not be pre-populated locally"
640 );
641 }
642}
643
644async fn apply_hosted_descriptors_locally(
657 execution: &mut TestSuiteExecution,
658 wire_codecs: &HashMap<String, (CloneableCodec, WorkerReconstructor)>,
659 descriptor_bytes: &[DepWireBytes],
660) {
661 for (dep_id, wire_bytes) in descriptor_bytes {
662 let (codec, worker_fn) = wire_codecs.get(dep_id).unwrap_or_else(|| {
663 panic!("Hosted dep '{dep_id}' missing codec/worker_fn for local handle reconstruction")
664 });
665 let wire_payload = (codec.from_wire_bytes)(wire_bytes);
666 let empty_deps: Arc<dyn internal::DependencyView + Send + Sync> =
667 Arc::new(HashMap::<String, Arc<dyn Any + Send + Sync>>::new());
668 let reconstructed = match worker_fn {
669 WorkerReconstructor::Sync(f) => f(wire_payload, empty_deps),
670 WorkerReconstructor::Async(f) => f(wire_payload, empty_deps).await,
671 };
672 let applied = execution.provide_cloneable_value(dep_id, reconstructed);
673 assert!(
674 applied,
675 "Hosted dep '{dep_id}' could not be pre-populated locally"
676 );
677 }
678}
679
680async fn pick_next(execution: &Arc<Mutex<TestSuiteExecution>>) -> Option<TestExecution> {
681 let mut execution = execution.lock().await;
682 execution.pick_next().await
683}
684
685async fn run_with_flakiness_control<F>(
686 output: Arc<dyn TestRunnerOutput>,
687 test_description: &RegisteredTest,
688 idx: usize,
689 count: usize,
690 test: F,
691) -> Result<Result<(), FailureCause>, Box<dyn Any + Send>>
692where
693 F: Fn(
694 Instant,
695 )
696 -> Pin<Box<dyn Future<Output = Result<Result<(), FailureCause>, Box<dyn Any + Send>>>>>
697 + Send
698 + Sync,
699{
700 match &test_description.props.flakiness_control {
701 FlakinessControl::None => {
702 let start = Instant::now();
703 test(start).await
704 }
705 FlakinessControl::ProveNonFlaky(tries) => {
706 for n in 0..*tries {
707 if n > 0 {
708 output.repeat_running_test(
709 test_description,
710 idx,
711 count,
712 n + 1,
713 *tries,
714 "to ensure test is not flaky",
715 );
716 }
717 let start = Instant::now();
718 match test(start).await {
719 Ok(Ok(())) => {}
720 Ok(Err(e)) => return Ok(Err(e)),
721 Err(e) => return Err(e),
722 };
723 }
724 Ok(Ok(()))
725 }
726 FlakinessControl::RetryKnownFlaky(max_retries) => {
727 let mut tries = 1;
728 loop {
729 let start = Instant::now();
730 let result = test(start).await;
731
732 if result.is_err() && tries < *max_retries {
733 tries += 1;
734 output.repeat_running_test(
735 test_description,
736 idx,
737 count,
738 tries,
739 *max_retries,
740 "because test is known to be flaky",
741 );
742 } else {
743 break result;
744 }
745 }
746 }
747 }
748}
749
750#[allow(clippy::too_many_arguments)]
751async fn run_test(
752 output: Arc<dyn TestRunnerOutput>,
753 idx: usize,
754 count: usize,
755 nocapture: bool,
756 include_ignored: bool,
757 ensure_time: Option<TimeThreshold>,
758 dependency_view: Arc<dyn internal::DependencyView + Send + Sync>,
759 test: &RegisteredTest,
760 worker: &mut Option<Worker>,
761) -> TestResult {
762 if test.props.is_ignored && !include_ignored {
763 TestResult::ignored()
764 } else if let Some(worker) = worker.as_mut() {
765 worker.run_test(nocapture, test).await
766 } else {
767 let start = Instant::now();
768 let test = test.clone();
769 match &test.run {
770 TestFunction::Sync(_) => {
771 let handle = spawn_blocking(move || {
772 let test = test.clone();
773 crate::sync::run_sync_test_function(
774 output,
775 &test,
776 idx,
777 count,
778 ensure_time,
779 dependency_view,
780 )
781 });
782 handle.await.unwrap_or_else(|join_error| {
783 TestResult::failed(
784 start.elapsed(),
785 FailureCause::HarnessError(format!(
786 "Failed joining test task: {join_error}"
787 )),
788 )
789 })
790 }
791 TestFunction::Async(test_fn) => {
792 let timeout = test.props.timeout;
793 let test_fn = test_fn.clone();
794 let detached_panic_policy = test.props.detached_panic_policy.clone();
795 let result = run_with_flakiness_control(output, &test, idx, count, |start| {
796 let dependency_view = dependency_view.clone();
797 let test_fn = test_fn.clone();
798 Box::pin(async move {
799 let test_id = crate::panic_hook::next_test_id();
800 crate::panic_hook::set_current_test_id(test_id);
801 crate::panic_hook::create_detached_collector(test_id);
802 let result = AssertUnwindSafe(Box::pin(async move {
803 match timeout {
804 None => test_fn(dependency_view).await,
805 Some(duration) => {
806 let result =
807 tokio::time::timeout(duration, test_fn(dependency_view))
808 .await;
809 match result {
810 Ok(result) => result,
811 Err(_) => {
812 return Err(FailureCause::HarnessError(
813 "Test timed out".to_string(),
814 ))
815 }
816 }
817 }
818 }
819 .into_result()?;
820 if let Some(ensure_time) = ensure_time {
821 let elapsed = start.elapsed();
822 if ensure_time.is_critical(&elapsed) {
823 return Err(FailureCause::HarnessError(format!(
824 "Test run time exceeds critical threshold: {elapsed:?}"
825 )));
826 }
827 }
828 Ok(())
829 }))
830 .catch_unwind()
831 .await;
832 result
833 })
834 })
835 .await;
836 let mut test_result =
837 TestResult::from_result(&test.props.should_panic, start.elapsed(), result);
838 if let Some(test_id) = crate::panic_hook::current_test_id() {
839 if let Some(collector) = crate::panic_hook::take_detached_collector(test_id) {
840 let panics = match collector.lock() {
841 Ok(p) => p,
842 Err(poisoned) => poisoned.into_inner(),
843 };
844 if !panics.is_empty()
845 && detached_panic_policy == internal::DetachedPanicPolicy::FailTest
846 && test_result.is_passed()
847 {
848 let messages: Vec<String> = panics.iter().map(|p| p.render()).collect();
849 test_result = TestResult::failed(
850 start.elapsed(),
851 FailureCause::Panic(internal::PanicCause {
852 message: Some(format!(
853 "Detached task(s) panicked:\n{}",
854 messages.join("\n---\n")
855 )),
856 location: panics.first().and_then(|p| p.location.clone()),
857 backtrace: panics.first().and_then(|p| p.backtrace.clone()),
858 }),
859 );
860 }
861 }
862 }
863 crate::panic_hook::clear_current_test_id();
864 test_result
865 }
866 TestFunction::SyncBench(_) => {
867 let handle = spawn_blocking(move || {
868 let test = test.clone();
869 crate::sync::run_sync_test_function(
870 output,
871 &test,
872 idx,
873 count,
874 ensure_time,
875 dependency_view,
876 )
877 });
878 handle.await.unwrap_or_else(|join_error| {
879 TestResult::failed(
880 start.elapsed(),
881 FailureCause::HarnessError(format!(
882 "Failed joining test task: {join_error}"
883 )),
884 )
885 })
886 }
887 TestFunction::AsyncBench(bench_fn) => {
888 let mut bencher = AsyncBencher::new();
889 let test_id = crate::panic_hook::next_test_id();
890 crate::panic_hook::set_current_test_id(test_id);
891 let result = AssertUnwindSafe(async move {
892 bench_fn(&mut bencher, dependency_view).await;
893 (
894 bencher
895 .summary()
896 .expect("iter() was not called in bench function"),
897 bencher.bytes,
898 )
899 })
900 .catch_unwind()
901 .await;
902 let bytes = result.as_ref().map(|(_, bytes)| *bytes).unwrap_or_default();
903 let test_result = TestResult::from_summary(
904 &test.props.should_panic,
905 start.elapsed(),
906 result.map(|(summary, _)| summary),
907 bytes,
908 );
909 crate::panic_hook::clear_current_test_id();
910 test_result
911 }
912 }
913 }
914}
915
916struct Worker {
917 _listener: Listener,
918 _process: Child,
919 _out_handle: JoinHandle<()>,
920 _err_handle: JoinHandle<()>,
921 out_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
922 err_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
923 capture_enabled: Arc<Mutex<bool>>,
924 connection: Stream,
925 hosted_rpc_owner_cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>,
929}
930
931impl Worker {
932 fn set_hosted_rpc_owner_cells(&mut self, cells: Arc<HashMap<String, Arc<HostedRpcOwnerCell>>>) {
936 self.hosted_rpc_owner_cells = cells;
937 }
938
939 async fn handle_hosted_rpc_call(
945 &mut self,
946 dump_on_ipc_failure: &DumpOnFailure,
947 request_id: u64,
948 dep_id: String,
949 method_idx: u32,
950 args_bytes: Vec<u8>,
951 ) {
952 let body = match self.hosted_rpc_owner_cells.get(&dep_id) {
953 Some(cell) => match cell.dispatch_async(method_idx, &args_bytes).await {
959 Ok(result_bytes) => HostedRpcReplyBody::Ok { result_bytes },
960 Err(message) => HostedRpcReplyBody::Err { message },
961 },
962 None => HostedRpcReplyBody::Err {
963 message: format!(
964 "HostedRpc dispatch: unknown dep id '{dep_id}' in parent owner-cell map"
965 ),
966 },
967 };
968 let reply = IpcCommand::HostedRpcReply { request_id, body };
969 let msg = serialize_to_byte_vec(&reply).expect("Failed to encode HostedRpcReply");
970 dump_on_ipc_failure
971 .run(write_frame_async(&mut self.connection, &msg).await)
972 .await;
973 }
974
975 pub async fn run_test(&mut self, nocapture: bool, test: &RegisteredTest) -> TestResult {
976 let mut capture_enabled = self.capture_enabled.lock().await;
977 *capture_enabled = test.props.capture_control.requires_capturing(!nocapture);
978 drop(capture_enabled);
979
980 let cmd = IpcCommand::RunTest {
982 name: test.name.clone(),
983 crate_name: test.crate_name.clone(),
984 module_path: test.module_path.clone(),
985 };
986
987 let dump_on_ipc_failure = self.dump_on_failure();
988
989 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
990 dump_on_ipc_failure
991 .run(write_frame_async(&mut self.connection, &msg).await)
992 .await;
993
994 let response = loop {
995 let response_bytes = dump_on_ipc_failure
996 .run(read_frame_async(&mut self.connection).await)
997 .await;
998 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
999 match response {
1000 IpcResponse::TestFinished { .. } => break response,
1001 IpcResponse::CloneableAccepted { .. }
1002 | IpcResponse::HostedDescriptorAccepted { .. } => continue,
1003 IpcResponse::HostedRpcCall {
1004 request_id,
1005 dep_id,
1006 method_idx,
1007 args_bytes,
1008 } => {
1009 self.handle_hosted_rpc_call(
1010 &dump_on_ipc_failure,
1011 request_id,
1012 dep_id,
1013 method_idx,
1014 args_bytes,
1015 )
1016 .await;
1017 continue;
1018 }
1019 }
1020 };
1021
1022 let IpcResponse::TestFinished {
1023 result,
1024 finish_marker,
1025 } = response
1026 else {
1027 unreachable!("loop only breaks on TestFinished")
1028 };
1029
1030 if test.props.capture_control.requires_capturing(!nocapture) {
1031 let out_lines: Vec<_> =
1032 Self::drain_until(self.out_lines.clone(), finish_marker.clone()).await;
1033 let err_lines: Vec<_> =
1034 Self::drain_until(self.err_lines.clone(), finish_marker.clone()).await;
1035 result.into_test_result(out_lines, err_lines)
1036 } else {
1037 result.into_test_result(Vec::new(), Vec::new())
1038 }
1039 }
1040
1041 async fn provide_cloneable(&mut self, dep_id: String, wire_bytes: Vec<u8>) {
1044 let dump_on_ipc_failure = self.dump_on_failure();
1045 let cmd = IpcCommand::ProvideCloneable {
1046 dep_id: dep_id.clone(),
1047 wire_bytes,
1048 };
1049 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
1050 dump_on_ipc_failure
1051 .run(write_frame_async(&mut self.connection, &msg).await)
1052 .await;
1053
1054 loop {
1055 let response_bytes = dump_on_ipc_failure
1056 .run(read_frame_async(&mut self.connection).await)
1057 .await;
1058 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
1059 match response {
1060 IpcResponse::CloneableAccepted { dep_id: ack_id } => {
1061 if ack_id == dep_id {
1062 return;
1063 }
1064 }
1065 IpcResponse::HostedDescriptorAccepted { .. } => {
1066 }
1068 IpcResponse::TestFinished { .. } => {
1069 }
1071 IpcResponse::HostedRpcCall {
1072 request_id,
1073 dep_id: rpc_dep_id,
1074 method_idx,
1075 args_bytes,
1076 } => {
1077 self.handle_hosted_rpc_call(
1082 &dump_on_ipc_failure,
1083 request_id,
1084 rpc_dep_id,
1085 method_idx,
1086 args_bytes,
1087 )
1088 .await;
1089 }
1090 }
1091 }
1092 }
1093
1094 async fn provide_hosted_descriptor(&mut self, dep_id: String, wire_bytes: Vec<u8>) {
1096 let dump_on_ipc_failure = self.dump_on_failure();
1097 let cmd = IpcCommand::ProvideHostedDescriptor {
1098 dep_id: dep_id.clone(),
1099 wire_bytes,
1100 };
1101 let msg = serialize_to_byte_vec(&cmd).expect("Failed to encode IPC command");
1102 dump_on_ipc_failure
1103 .run(write_frame_async(&mut self.connection, &msg).await)
1104 .await;
1105
1106 loop {
1107 let response_bytes = dump_on_ipc_failure
1108 .run(read_frame_async(&mut self.connection).await)
1109 .await;
1110 let response: IpcResponse = dump_on_ipc_failure.run(deserialize(&response_bytes)).await;
1111 match response {
1112 IpcResponse::HostedDescriptorAccepted { dep_id: ack_id } => {
1113 if ack_id == dep_id {
1114 return;
1115 }
1116 }
1117 IpcResponse::CloneableAccepted { .. } => {
1118 }
1120 IpcResponse::TestFinished { .. } => {
1121 }
1123 IpcResponse::HostedRpcCall {
1124 request_id,
1125 dep_id: rpc_dep_id,
1126 method_idx,
1127 args_bytes,
1128 } => {
1129 self.handle_hosted_rpc_call(
1132 &dump_on_ipc_failure,
1133 request_id,
1134 rpc_dep_id,
1135 method_idx,
1136 args_bytes,
1137 )
1138 .await;
1139 }
1140 }
1141 }
1142 }
1143
1144 fn dump_on_failure(&self) -> DumpOnFailure {
1145 DumpOnFailure {
1146 out_lines: self.out_lines.clone(),
1147 err_lines: self.err_lines.clone(),
1148 }
1149 }
1150
1151 async fn drain_until(
1152 source: Arc<Mutex<VecDeque<CapturedOutput>>>,
1153 finish_marker: String,
1154 ) -> Vec<CapturedOutput> {
1155 let mut result = Vec::new();
1156 loop {
1157 let mut source = source.lock().await;
1158 while let Some(line) = source.pop_front() {
1159 if line.line() == finish_marker {
1160 return result;
1161 } else {
1162 result.push(line.clone());
1163 }
1164 }
1165 drop(source);
1166
1167 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1168 }
1169 }
1170}
1171
1172struct DumpOnFailure {
1173 out_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
1174 err_lines: Arc<Mutex<VecDeque<CapturedOutput>>>,
1175}
1176
1177impl DumpOnFailure {
1178 pub async fn run<T, E>(&self, result: Result<T, E>) -> T {
1179 match result {
1180 Ok(value) => value,
1181 Err(_error) => {
1182 let out_lines: Vec<_> = self.out_lines.lock().await.drain(..).collect();
1183 let err_lines: Vec<_> = self.err_lines.lock().await.drain(..).collect();
1184 let mut all_lines = [out_lines, err_lines].concat();
1185 all_lines.sort();
1186
1187 use std::io::Write;
1195 let mut err = crate::host_capture::TerminalStderr;
1196 for line in all_lines {
1197 let _ = writeln!(err, "{}", line.line());
1198 }
1199 let _ = err.flush();
1200
1201 std::process::exit(1);
1202 }
1203 }
1204 }
1205}
1206
1207async fn spawn_worker_if_needed(args: &Arguments) -> Option<Worker> {
1208 if args.spawn_workers {
1209 let id = Uuid::new_v4();
1210 let name_str = format!("{id}.sock");
1211 let name = name_str
1212 .clone()
1213 .to_ns_name::<GenericNamespaced>()
1214 .expect("Invalid local socket name");
1215 let opts = ListenerOptions::new().name(name.clone());
1216 let listener = opts
1217 .create_tokio()
1218 .expect("Failed to create local socket listener");
1219
1220 let exe = std::env::current_exe().expect("Failed to get current executable path");
1221
1222 let mut args = args.clone();
1223 args.ipc = Some(name_str);
1224 args.spawn_workers = false;
1225 args.logfile = None;
1226 let args = args.to_args();
1227
1228 let mut process = Command::new(exe)
1229 .args(args)
1230 .stdin(Stdio::inherit())
1231 .stderr(Stdio::piped())
1232 .stdout(Stdio::piped())
1233 .spawn()
1234 .expect("Failed to spawn worker process");
1235
1236 let stdout = process.stdout.take().unwrap();
1237 let stderr = process.stderr.take().unwrap();
1238
1239 let out_lines = Arc::new(Mutex::new(VecDeque::new()));
1240 let err_lines = Arc::new(Mutex::new(VecDeque::new()));
1241 let capture_enabled = Arc::new(Mutex::new(true));
1242
1243 let out_lines_clone = out_lines.clone();
1244 let capture_enabled_clone = capture_enabled.clone();
1245 let out_handle = spawn(async move {
1246 let reader = BufReader::new(stdout);
1247 let mut lines = reader.lines();
1248 while let Some(line) = lines
1249 .next_line()
1250 .await
1251 .expect("Failed to read from worker stdout")
1252 {
1253 if *capture_enabled_clone.lock().await {
1254 out_lines_clone
1255 .lock()
1256 .await
1257 .push_back(CapturedOutput::stdout(line));
1258 } else {
1259 use std::io::Write;
1265 let mut out = crate::host_capture::TerminalStdout;
1266 let _ = writeln!(out, "{line}");
1267 let _ = out.flush();
1268 }
1269 }
1270 });
1271
1272 let err_lines_clone = err_lines.clone();
1273 let capture_enabled_clone = capture_enabled.clone();
1274 let err_handle = spawn(async move {
1275 let reader = BufReader::new(stderr);
1276 let mut lines = reader.lines();
1277 while let Some(line) = lines
1278 .next_line()
1279 .await
1280 .expect("Failed to read from worker stderr")
1281 {
1282 if *capture_enabled_clone.lock().await {
1283 err_lines_clone
1284 .lock()
1285 .await
1286 .push_back(CapturedOutput::stderr(line));
1287 } else {
1288 use std::io::Write;
1292 let mut err = crate::host_capture::TerminalStderr;
1293 let _ = writeln!(err, "{line}");
1294 let _ = err.flush();
1295 }
1296 }
1297 });
1298
1299 let connection = listener
1300 .accept()
1301 .await
1302 .expect("Failed to accept connection");
1303
1304 Some(Worker {
1305 _listener: listener,
1306 _process: process,
1307 _out_handle: out_handle,
1308 _err_handle: err_handle,
1309 out_lines,
1310 err_lines,
1311 connection,
1312 capture_enabled,
1313 hosted_rpc_owner_cells: Arc::new(HashMap::new()),
1314 })
1315 } else {
1316 None
1317 }
1318}
1319
1320fn install_local_hosted_rpc_stubs(
1326 execution: &mut TestSuiteExecution,
1327 rpc_factories: &HashMap<String, RpcFactory>,
1328 owner_cells: &HashMap<String, Arc<HostedRpcOwnerCell>>,
1329) {
1330 let transport: Arc<dyn HostedRpcTransport> =
1331 Arc::new(InProcessHostedRpcTransport::new(owner_cells.clone()));
1332 for (dep_id, factory) in rpc_factories.iter() {
1333 if !owner_cells.contains_key(dep_id) {
1334 continue;
1338 }
1339 let channel = HostedRpcChannel::new(dep_id.clone(), transport.clone());
1340 let stub = (factory.build_stub)(channel);
1341 let applied = execution.provide_cloneable_value(dep_id, stub);
1342 if !applied {
1343 continue;
1349 }
1350 }
1351}
1352
1353async fn install_worker_subprocess_hosted_rpc_stubs(
1358 execution: &Arc<Mutex<TestSuiteExecution>>,
1359 rpc_factories: &HashMap<String, RpcFactory>,
1360 connection_arc: Arc<Mutex<Stream>>,
1361) {
1362 let transport: Arc<dyn HostedRpcTransport> =
1363 Arc::new(IpcHostedRpcTransport::new(connection_arc));
1364 for (dep_id, factory) in rpc_factories.iter() {
1365 let channel = HostedRpcChannel::new(dep_id.clone(), transport.clone());
1366 let stub = (factory.build_stub)(channel);
1367 let mut execution = execution.lock().await;
1368 let applied = execution.provide_cloneable_value(dep_id, stub);
1369 let _ = applied;
1372 }
1373}
1374
1375struct IpcHostedRpcTransport {
1393 connection: Arc<Mutex<Stream>>,
1394 next_request_id: AtomicU64,
1395}
1396
1397impl IpcHostedRpcTransport {
1398 fn new(connection: Arc<Mutex<Stream>>) -> Self {
1399 Self {
1400 connection,
1401 next_request_id: AtomicU64::new(0),
1402 }
1403 }
1404}
1405
1406impl HostedRpcTransport for IpcHostedRpcTransport {
1407 fn call(
1408 &self,
1409 dep_id: &str,
1410 method_idx: u32,
1411 args: Vec<u8>,
1412 ) -> Result<Vec<u8>, HostedRpcError> {
1413 let request_id = self.next_request_id.fetch_add(1, Ordering::SeqCst);
1414 let call = IpcResponse::HostedRpcCall {
1415 request_id,
1416 dep_id: dep_id.to_string(),
1417 method_idx,
1418 args_bytes: args,
1419 };
1420 let msg = serialize_to_byte_vec(&call).map_err(|e| {
1421 HostedRpcError::Transport(format!("encode HostedRpcCall failed: {e:?}"))
1422 })?;
1423
1424 let connection = self.connection.clone();
1425 let handle = tokio::runtime::Handle::current();
1426
1427 tokio::task::block_in_place(move || {
1432 handle.block_on(async move {
1433 let mut conn = connection.lock().await;
1434 write_frame_async(&mut *conn, &msg).await.map_err(|e| {
1435 HostedRpcError::Transport(format!("write HostedRpcCall failed: {e:?}"))
1436 })?;
1437 let reply_bytes = read_frame_async(&mut *conn).await.map_err(|e| {
1438 HostedRpcError::Transport(format!("read HostedRpcReply failed: {e:?}"))
1439 })?;
1440 let command: IpcCommand = deserialize(&reply_bytes).map_err(|e| {
1441 HostedRpcError::Transport(format!("decode HostedRpcReply failed: {e:?}"))
1442 })?;
1443 match command {
1444 IpcCommand::HostedRpcReply {
1445 request_id: reply_id,
1446 body,
1447 } => {
1448 if reply_id != request_id {
1449 return Err(HostedRpcError::Transport(format!(
1450 "HostedRpcReply request_id mismatch: expected {request_id}, got {reply_id}"
1451 )));
1452 }
1453 match body {
1454 HostedRpcReplyBody::Ok { result_bytes } => Ok(result_bytes),
1455 HostedRpcReplyBody::Err { message } => {
1456 Err(HostedRpcError::Dispatch(message))
1457 }
1458 }
1459 }
1460 other => Err(HostedRpcError::Transport(format!(
1461 "unexpected IpcCommand while waiting for HostedRpcReply: {other:?}"
1462 ))),
1463 }
1464 })
1465 })
1466 }
1467}