test-r-core 10.0.0

Test framework for Rust (core module)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
pub mod args;
pub mod bench;
mod execution;
mod host_capture;
pub mod internal;
mod ipc;
mod output;
mod panic_hook;
pub mod spawn;
mod stats;
#[cfg(feature = "tokio")]
mod tokio;
pub mod worker;

#[allow(dead_code)]
mod sync;

#[cfg(not(feature = "tokio"))]
pub use sync::test_runner;

#[cfg(feature = "tokio")]
pub use tokio::test_runner;

pub use worker::worker_index;

/// Re-export of [`desert_rust`] so that proc-macros emitted by
/// `test-r-macro` (e.g. [`test_r::hosted_rpc`]) can refer to it via
/// `::test_r::core::desert_rust::...` without forcing downstream
/// users to add `desert_rust` to their own `Cargo.toml`.
///
/// This is an internal support re-export — user code should not depend
/// on it being available at this path. The `#[doc(hidden)]` attribute
/// keeps it out of the rendered docs and signals that the surface
/// belongs to the macro support layer, not the public test-r API.
#[doc(hidden)]
pub use desert_rust;

// =====================================================================
// Hosted descriptor codec / worker reconstructor helpers.
//
// These two functions are the single place the descriptor-based Hosted
// dep wiring is built. The `tokio` cargo feature on `test-r-core`
// selects which variant of each function compiles, so that:
//
// - Under the **tokio** runtime, Hosted deps always use the *async*
//   path (`AsyncHostedDep::descriptor` / `from_descriptor`). Thanks to the
//   blanket `impl<T: HostedDep> AsyncHostedDep for T`, every sync `HostedDep`
//   automatically reaches the async path with no user-visible cost (the
//   bridged `from_descriptor` just wraps the sync impl in
//   `std::future::ready(...)`).
// - Under the **sync** runtime, Hosted deps always use the *sync* path
//   (`HostedDep::descriptor` / `from_descriptor`). This intentionally
//   keeps sync builds free of any block-poll machinery: a Hosted dep
//   that only implements `AsyncHostedDep` simply fails to compile
//   here, rather than panicking at runtime.
//
// The macro now emits a single uniform call to these helpers regardless
// of the (now deprecated) `async_worker` attribute; see
// `test-r-macro/src/deps.rs`.
// =====================================================================

/// **Hidden macro-support helper.**
///
/// Build the parent-side codec for a Hosted dep. Under the `tokio`
/// runtime this dispatches through [`internal::AsyncHostedDep`]; under
/// the sync runtime it dispatches through [`internal::HostedDep`]. The
/// returned [`internal::CloneableCodec`] knows how to:
///
/// - downcast the owner `Arc<dyn Any + Send + Sync>` back to `T` and
///   produce the descriptor bytes (`to_wire`), and
/// - turn raw descriptor bytes into a boxed payload that the matching
///   reconstructor below will hand off to `from_descriptor`
///   (`from_wire_bytes`).
///
/// Not part of the public API; only the proc-macro emits calls to it.
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_codec<T>() -> internal::CloneableCodec
where
    T: internal::AsyncHostedDep,
{
    use std::sync::Arc;
    internal::CloneableCodec {
        to_wire: Arc::new(|any: Arc<dyn std::any::Any + Send + Sync>| {
            let value: Arc<T> = any
                .downcast::<T>()
                .expect("Hosted dependency type mismatch in descriptor()");
            <T as internal::AsyncHostedDep>::descriptor(&*value)
        }),
        from_wire_bytes: Arc::new(|bytes: &[u8]| {
            // The "wire payload" for a Hosted dep is the raw descriptor
            // bytes; the matching reconstructor will run from_descriptor
            // against them on the worker side.
            let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(bytes.to_vec());
            boxed
        }),
    }
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_codec`]; see that doc-comment.
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_codec<T>() -> internal::CloneableCodec
where
    T: internal::HostedDep,
{
    use std::sync::Arc;
    internal::CloneableCodec {
        to_wire: Arc::new(|any: Arc<dyn std::any::Any + Send + Sync>| {
            let value: Arc<T> = any
                .downcast::<T>()
                .expect("Hosted dependency type mismatch in descriptor()");
            <T as internal::HostedDep>::descriptor(&*value)
        }),
        from_wire_bytes: Arc::new(|bytes: &[u8]| {
            let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(bytes.to_vec());
            boxed
        }),
    }
}

/// **Hidden macro-support helper.**
///
/// Build the worker-side [`internal::WorkerReconstructor`] for a Hosted
/// dep. Under the `tokio` runtime this returns an
/// [`internal::WorkerReconstructor::Async`] closure that awaits
/// [`internal::AsyncHostedDep::from_descriptor`]; under the sync
/// runtime it returns an [`internal::WorkerReconstructor::Sync`]
/// closure that calls [`internal::HostedDep::from_descriptor`].
///
/// The descriptor `Vec<u8>` is produced by the matching
/// [`__test_r_make_hosted_codec`] above; the two helpers must stay in
/// lockstep.
///
/// Not part of the public API; only the proc-macro emits calls to it.
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_worker_reconstructor<T>() -> internal::WorkerReconstructor
where
    T: internal::AsyncHostedDep,
{
    use std::sync::Arc;
    internal::WorkerReconstructor::Async(Arc::new(
        |wire_payload: Arc<dyn std::any::Any + Send + Sync>, _deps| {
            Box::pin(async move {
                let bytes: Arc<Vec<u8>> = wire_payload
                    .downcast::<Vec<u8>>()
                    .expect("Hosted worker reconstructor expected Vec<u8> descriptor payload");
                let value: T = <T as internal::AsyncHostedDep>::from_descriptor(&bytes).await;
                let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(value);
                boxed
            })
        },
    ))
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_worker_reconstructor`]; see that
/// doc-comment.
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_worker_reconstructor<T>() -> internal::WorkerReconstructor
where
    T: internal::HostedDep,
{
    use std::sync::Arc;
    internal::WorkerReconstructor::Sync(Arc::new(
        |wire_payload: Arc<dyn std::any::Any + Send + Sync>, _deps| {
            let bytes: Arc<Vec<u8>> = wire_payload
                .downcast::<Vec<u8>>()
                .expect("Hosted worker reconstructor expected Vec<u8> descriptor payload");
            let value: T = <T as internal::HostedDep>::from_descriptor(&bytes);
            let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(value);
            boxed
        },
    ))
}

// =====================================================================
// `worker = both(T)` helpers.
//
// `#[test_dep(scope = Hosted, worker = both(Trait))]` is lowered by the
// macro into two `RegisteredDependency` entries (one Hosted, one
// HostedRpc) that share a single parent-side owner via
// [`internal::HostedBothShared`]. The three helpers below centralize
// the shared logic:
//
// - `__test_r_make_hosted_both_shared::<T>(owner_arc, rpc_cell)` builds
//   the cell used by the macro's weak cache from an already-`Arc`'d
//   owner plus a prebuilt RPC cell. The macro acquire helper
//   constructs the `Arc<T>` once and shares it between this cell, the
//   RPC dispatch cell, and the parent-side owner getter — no second
//   owner instance, no `T: Clone` requirement. Cfg-selected on `tokio`
//   so the descriptor call uses `AsyncHostedDep::descriptor` under
//   tokio and `HostedDep::descriptor` under sync, mirroring the
//   single-view helpers.
// - `__test_r_make_hosted_both_codec()` produces the Hosted-view
//   codec; both bytes (`to_wire`) and payload (`from_wire_bytes`)
//   shapes match the existing single-view Hosted codec so the
//   runtime worker side stays unchanged.
// - `__test_r_make_hosted_both_rpc_factory::<T>()` produces the
//   HostedRpc-view factory. It downcasts the shared cell to extract
//   the inner `Arc<HostedRpcOwnerCell>`, and reuses the same
//   `build_stub(channel)` path the legacy HostedRpc factory uses.
// =====================================================================

/// **Hidden macro-support helper.** Build the shared owner cell for
/// a `worker = both(T)` dep from an already-`Arc`'d owner plus a
/// pre-built RPC cell. The macro acquire helper constructs `Arc<T>`
/// once and clones it into both this helper and the RPC cell so the
/// parent-side getter, the descriptor view, and the RPC dispatcher
/// observe exactly one owner instance.
///
/// Tokio variant: descriptor is computed via
/// [`internal::AsyncHostedDep::descriptor`].
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_both_shared<T>(
    owner: std::sync::Arc<T>,
    rpc_cell: std::sync::Arc<internal::HostedRpcOwnerCell>,
) -> internal::HostedBothShared
where
    T: internal::AsyncHostedDep,
{
    use std::sync::Arc;
    let descriptor_bytes = <T as internal::AsyncHostedDep>::descriptor(&*owner);
    let owner_any: Arc<dyn std::any::Any + Send + Sync> = owner;
    internal::HostedBothShared::new(descriptor_bytes, owner_any, rpc_cell)
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_both_shared`]; descriptor is computed via
/// [`internal::HostedDep::descriptor`].
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_both_shared<T>(
    owner: std::sync::Arc<T>,
    rpc_cell: std::sync::Arc<internal::HostedRpcOwnerCell>,
) -> internal::HostedBothShared
where
    T: internal::HostedDep,
{
    use std::sync::Arc;
    let descriptor_bytes = <T as internal::HostedDep>::descriptor(&*owner);
    let owner_any: Arc<dyn std::any::Any + Send + Sync> = owner;
    internal::HostedBothShared::new(descriptor_bytes, owner_any, rpc_cell)
}

/// **Hidden macro-support helper.** Wrap a HostedRpc owner value into a
/// [`internal::HostedRpcOwnerCell`]. The tokio variant goes through
/// [`internal::HostedRpcOwnerCell::from_async_owner`] so async owners
/// are dispatched asynchronously; the sync variant uses the back-compat
/// sync constructor. Used by the `#[test_dep(scope = HostedRpc)]`
/// lowering so the choice of async vs sync cell happens in one place.
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_rpc_cell<T>(owner: T) -> internal::HostedRpcOwnerCell
where
    T: internal::AsyncHostedRpcDep,
{
    internal::HostedRpcOwnerCell::from_async_owner(owner)
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_rpc_cell`].
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_rpc_cell<T>(owner: T) -> internal::HostedRpcOwnerCell
where
    T: internal::HostedRpcDep,
{
    internal::HostedRpcOwnerCell::from_owner(owner)
}

/// **Hidden macro-support helper.** Hosted-view codec for the `both`
/// variant. Wire format is identical to the existing single-view
/// Hosted codec so the worker reconstructor (still
/// [`__test_r_make_hosted_worker_reconstructor`]) stays unchanged.
#[doc(hidden)]
pub fn __test_r_make_hosted_both_codec() -> internal::CloneableCodec {
    use std::any::Any;
    use std::sync::Arc;
    internal::CloneableCodec {
        to_wire: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
            let shared: Arc<internal::HostedBothShared> = any
                .downcast::<internal::HostedBothShared>()
                .expect("HostedBothShared downcast failed in both-codec to_wire");
            shared.descriptor_bytes().to_vec()
        }),
        from_wire_bytes: Arc::new(|bytes: &[u8]| {
            // Same payload shape as the single-view Hosted codec: a
            // boxed `Vec<u8>` for the worker reconstructor to consume.
            let boxed: Arc<dyn Any + Send + Sync> = Arc::new(bytes.to_vec());
            boxed
        }),
    }
}

/// **Hidden macro-support helper.** HostedRpc-view factory for the
/// `both` variant. Pulls the inner `Arc<HostedRpcOwnerCell>` out of
/// the shared cell and reuses the user's
/// [`internal::HostedRpcDep::build_stub`] for the worker stub.
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_both_rpc_factory<T, Stub>() -> internal::RpcFactory
where
    T: internal::AsyncHostedRpcDep<Stub = Stub>,
    Stub: Send + Sync + 'static,
{
    use std::any::Any;
    use std::sync::Arc;
    internal::RpcFactory {
        owner_into_cell: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
            let shared: Arc<internal::HostedBothShared> = any
                .downcast::<internal::HostedBothShared>()
                .expect("HostedBothShared downcast failed in both-rpc-factory owner_into_cell");
            shared.rpc_cell()
        }),
        build_stub: Arc::new(|channel: internal::HostedRpcChannel| {
            let stub: Stub = <T as internal::AsyncHostedRpcDep>::build_stub(channel);
            let boxed: Arc<dyn Any + Send + Sync> = Arc::new(stub);
            boxed
        }),
    }
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_both_rpc_factory`]. The `build_stub` is sourced
/// from [`internal::HostedRpcDep::build_stub`] because the sync runtime
/// cannot drive `AsyncHostedRpcDep` owners.
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_both_rpc_factory<T, Stub>() -> internal::RpcFactory
where
    T: internal::HostedRpcDep<Stub = Stub>,
    Stub: Send + Sync + 'static,
{
    use std::any::Any;
    use std::sync::Arc;
    internal::RpcFactory {
        owner_into_cell: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
            let shared: Arc<internal::HostedBothShared> = any
                .downcast::<internal::HostedBothShared>()
                .expect("HostedBothShared downcast failed in both-rpc-factory owner_into_cell");
            shared.rpc_cell()
        }),
        build_stub: Arc::new(|channel: internal::HostedRpcChannel| {
            let stub: Stub = <T as internal::HostedRpcDep>::build_stub(channel);
            let boxed: Arc<dyn Any + Send + Sync> = Arc::new(stub);
            boxed
        }),
    }
}

/// **Hidden macro-support helper.** Build a `RpcFactory` for the
/// stand-alone `scope = HostedRpc` lowering (no `both(T)` companion).
/// Tokio variant goes through [`internal::AsyncHostedRpcDep::build_stub`]
/// so async owners flow through one entry point.
#[doc(hidden)]
#[cfg(feature = "tokio")]
pub fn __test_r_make_hosted_rpc_factory<T, Stub>() -> internal::RpcFactory
where
    T: internal::AsyncHostedRpcDep<Stub = Stub>,
    Stub: Send + Sync + 'static,
{
    use std::any::Any;
    use std::sync::Arc;
    internal::RpcFactory {
        owner_into_cell: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
            any.downcast::<internal::HostedRpcOwnerCell>()
                .expect("HostedRpc owner downcast to HostedRpcOwnerCell failed")
        }),
        build_stub: Arc::new(|channel: internal::HostedRpcChannel| {
            let stub: Stub = <T as internal::AsyncHostedRpcDep>::build_stub(channel);
            let boxed: Arc<dyn Any + Send + Sync> = Arc::new(stub);
            boxed
        }),
    }
}

/// **Hidden macro-support helper.** Sync-runtime variant of
/// [`__test_r_make_hosted_rpc_factory`].
#[doc(hidden)]
#[cfg(not(feature = "tokio"))]
pub fn __test_r_make_hosted_rpc_factory<T, Stub>() -> internal::RpcFactory
where
    T: internal::HostedRpcDep<Stub = Stub>,
    Stub: Send + Sync + 'static,
{
    use std::any::Any;
    use std::sync::Arc;
    internal::RpcFactory {
        owner_into_cell: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
            any.downcast::<internal::HostedRpcOwnerCell>()
                .expect("HostedRpc owner downcast to HostedRpcOwnerCell failed")
        }),
        build_stub: Arc::new(|channel: internal::HostedRpcChannel| {
            let stub: Stub = <T as internal::HostedRpcDep>::build_stub(channel);
            let boxed: Arc<dyn Any + Send + Sync> = Arc::new(stub);
            boxed
        }),
    }
}

#[cfg(test)]
mod hosted_helper_tests {
    //! Exercise the feature-gated
    //! [`__test_r_make_hosted_codec`] /
    //! [`__test_r_make_hosted_worker_reconstructor`] helpers end to
    //! end against a tiny `HostedDep` fixture.
    //!
    //! Both helper variants must reject `WorkerReconstructor::Sync`
    //! vs `Async` choice at the cargo-feature level, so we keep this
    //! test cfg-aware: it asserts the matching variant under each
    //! feature.
    use super::*;
    use std::any::Any;
    use std::sync::Arc;

    /// Minimal sync `HostedDep` fixture. Under the `tokio` feature
    /// the blanket bridge also makes it `AsyncHostedDep`, so the same fixture
    /// is usable against both helper variants.
    #[derive(Debug, PartialEq, Eq)]
    struct Fixture {
        bytes: Vec<u8>,
    }

    impl internal::HostedDep for Fixture {
        fn descriptor(&self) -> Vec<u8> {
            self.bytes.clone()
        }
        fn from_descriptor(bytes: &[u8]) -> Self {
            Self {
                bytes: bytes.to_vec(),
            }
        }
    }

    #[test]
    fn make_hosted_codec_round_trips_descriptor_bytes() {
        let codec = __test_r_make_hosted_codec::<Fixture>();
        let owner: Arc<dyn Any + Send + Sync> = Arc::new(Fixture {
            bytes: vec![1, 2, 3, 4],
        });

        let wire_bytes = (codec.to_wire)(owner);
        assert_eq!(wire_bytes, vec![1, 2, 3, 4]);

        let wire_payload = (codec.from_wire_bytes)(&wire_bytes);
        let recovered_bytes: Arc<Vec<u8>> = wire_payload
            .downcast::<Vec<u8>>()
            .expect("from_wire_bytes must produce Arc<Vec<u8>>");
        assert_eq!(*recovered_bytes, vec![1, 2, 3, 4]);
    }

    /// Under the tokio runtime, the worker reconstructor helper must
    /// return [`internal::WorkerReconstructor::Async`].
    #[cfg(feature = "tokio")]
    #[test]
    fn make_hosted_worker_reconstructor_is_async_under_tokio() {
        let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();
        match recon {
            internal::WorkerReconstructor::Async(_) => {}
            internal::WorkerReconstructor::Sync(_) => panic!(
                "tokio build must produce a WorkerReconstructor::Async for Hosted deps; got Sync"
            ),
        }
    }

    /// Under the sync runtime, the worker reconstructor helper must
    /// return [`internal::WorkerReconstructor::Sync`] so the sync
    /// runner can drive it without any block-poll machinery.
    #[cfg(not(feature = "tokio"))]
    #[test]
    fn make_hosted_worker_reconstructor_is_sync_under_sync_runtime() {
        let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();
        match recon {
            internal::WorkerReconstructor::Sync(_) => {}
            internal::WorkerReconstructor::Async(_) => panic!(
                "sync build must produce a WorkerReconstructor::Sync for Hosted deps; got Async"
            ),
        }
    }

    /// Drive the sync-runtime reconstructor closure end to end on
    /// the matching descriptor bytes the codec produces. Pinned only
    /// for the sync build because the tokio build returns an Async
    /// closure that needs a runtime to await.
    #[cfg(not(feature = "tokio"))]
    #[test]
    fn sync_worker_reconstructor_rebuilds_fixture_from_descriptor() {
        // Re-use a small `DependencyView` impl: the helper's worker
        // closure ignores the view, so we pass an empty stub.
        #[derive(Debug)]
        struct EmptyView;
        impl internal::DependencyView for EmptyView {
            fn get(&self, _name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
                None
            }
        }

        let codec = __test_r_make_hosted_codec::<Fixture>();
        let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();

        let owner: Arc<dyn Any + Send + Sync> = Arc::new(Fixture {
            bytes: vec![5, 6, 7],
        });
        let wire_bytes = (codec.to_wire)(owner);
        let payload = (codec.from_wire_bytes)(&wire_bytes);

        let deps: Arc<dyn internal::DependencyView + Send + Sync> = Arc::new(EmptyView);
        let rebuilt = match recon {
            internal::WorkerReconstructor::Sync(f) => f(payload, deps),
            internal::WorkerReconstructor::Async(_) => unreachable!(
                "sync build cannot return Async; pinned by \
                 make_hosted_worker_reconstructor_is_sync_under_sync_runtime",
            ),
        };
        let rebuilt: Arc<Fixture> = rebuilt
            .downcast::<Fixture>()
            .expect("worker reconstructor must produce the original Hosted dep type");
        assert_eq!(
            *rebuilt,
            Fixture {
                bytes: vec![5, 6, 7]
            }
        );
    }

    // -----------------------------------------------------------------
    // `worker = both(T)` helper tests.
    //
    // The macro lowering for `#[test_dep(scope = Hosted, worker =
    // both(Trait))]` is exercised end-to-end by the
    // `sharing::hosted_both_basic` example fixtures. These unit tests
    // pin the three pieces of cargo-feature-aware glue in this file:
    //
    // - `__test_r_make_hosted_both_shared::<T>(owner_arc, rpc_cell)`
    //   builds the shared cell that the macro's weak cache hands back
    //   to both the Hosted and HostedRpc registrations, from an
    //   already-`Arc`'d owner and the prebuilt
    //   [`internal::HostedRpcOwnerCell`] the macro produces.
    // - `__test_r_make_hosted_both_codec()` serializes the cached
    //   descriptor bytes for the Hosted view.
    // - `__test_r_make_hosted_both_rpc_factory::<T>()` extracts the
    //   inner `Arc<HostedRpcOwnerCell>` for the HostedRpc view and
    //   builds the worker-side stub via `HostedRpcDep::build_stub`.
    // -----------------------------------------------------------------

    /// Minimal `HostedDep + HostedRpcDep` fixture for helper tests. The id
    /// allocator stands in for any tiny control surface; the bytes field doubles
    /// as the descriptor.
    #[derive(Debug)]
    struct BothFixture {
        bytes: Vec<u8>,
        counter: std::sync::Mutex<u64>,
    }

    impl BothFixture {
        fn new(bytes: Vec<u8>) -> Self {
            Self {
                bytes,
                counter: std::sync::Mutex::new(0),
            }
        }
    }

    impl internal::HostedDep for BothFixture {
        fn descriptor(&self) -> Vec<u8> {
            self.bytes.clone()
        }
        fn from_descriptor(bytes: &[u8]) -> Self {
            Self::new(bytes.to_vec())
        }
    }

    /// Stub view for the BothFixture. Holds a HostedRpcChannel so a
    /// realistic build_stub round-trip is exercised; the tests below
    /// just verify the factory hands back a usable `Arc<BothStub>`,
    /// not a full IPC round-trip (covered by the example fixtures).
    pub struct BothStub {
        _channel: internal::HostedRpcChannel,
    }

    impl internal::HostedRpcDep for BothFixture {
        type Stub = BothStub;
        fn dispatch(&mut self, method_idx: u32, args: &[u8]) -> Result<Vec<u8>, String> {
            // Delegate to the shared `&self` dispatcher so the same
            // implementation services both the legacy `from_owner`
            // path (which expects `&mut self`) and the new
            // `from_shared_owner_*` path (which expects `&self`).
            // Mirrors the shape of what `#[hosted_rpc]`'s
            // blanket-implemented dispatcher will look like once we
            // add the shared variant.
            #[cfg(feature = "tokio")]
            {
                futures::executor::block_on(Self::dispatch_shared(self, method_idx, args))
            }
            #[cfg(not(feature = "tokio"))]
            {
                Self::dispatch_shared(self, method_idx, args)
            }
        }
        fn build_stub(channel: internal::HostedRpcChannel) -> Self::Stub {
            BothStub { _channel: channel }
        }
    }

    impl BothFixture {
        /// `&self`-receiver dispatcher used by the shared-owner cell
        /// (`from_shared_owner_sync` / `from_shared_owner_async`). The
        /// sync runtime returns a plain `Result`; the tokio runtime
        /// wraps it in an `async fn` so the same body satisfies the
        /// `Pin<Box<dyn Future + Send + 'a>>` closure shape.
        #[cfg(not(feature = "tokio"))]
        fn dispatch_shared(this: &Self, method_idx: u32, _args: &[u8]) -> Result<Vec<u8>, String> {
            if method_idx == 1 {
                let mut g = this.counter.lock().map_err(|e| e.to_string())?;
                *g += 1;
                Ok(g.to_be_bytes().to_vec())
            } else {
                Err(format!("BothFixture: unknown method_idx {method_idx}"))
            }
        }

        /// Tokio variant of [`Self::dispatch_shared`]. Returning an
        /// `async fn` lets the boxed-future closure used by
        /// [`internal::HostedRpcOwnerCell::from_shared_owner_async`]
        /// hold the resulting future for the duration of the call.
        #[cfg(feature = "tokio")]
        async fn dispatch_shared(
            this: &Self,
            method_idx: u32,
            _args: &[u8],
        ) -> Result<Vec<u8>, String> {
            if method_idx == 1 {
                let mut g = this.counter.lock().map_err(|e| e.to_string())?;
                *g += 1;
                Ok(g.to_be_bytes().to_vec())
            } else {
                Err(format!("BothFixture: unknown method_idx {method_idx}"))
            }
        }
    }

    /// Build a `HostedBothShared` cell for tests in the shape the new
    /// macro acquire helper produces: one `Arc<T>` owner shared
    /// between the cell and the descriptor view, with a closure-based
    /// shared-owner cell so dispatch routes against `&T`. Used by the
    /// helper tests below so they don't have to repeat the closure
    /// glue, and so the parent-side `owner_arc::<T>()` accessor sees
    /// the exact same `Arc<T>` the cell holds.
    fn build_both_shared_for_test(
        owner: BothFixture,
    ) -> (Arc<BothFixture>, internal::HostedBothShared) {
        let owner_arc = Arc::new(owner);
        let cell_arc = build_both_rpc_cell_for_test(owner_arc.clone());
        let shared = __test_r_make_hosted_both_shared::<BothFixture>(owner_arc.clone(), cell_arc);
        (owner_arc, shared)
    }

    /// Build the shared `Arc<HostedRpcOwnerCell>` for the test
    /// fixture. The closure delegates to the fixture's
    /// `dispatch_shared` (a tiny `&self` dispatcher that mirrors what
    /// `#[hosted_rpc]` will generate via `dispatch_<snake>_shared`).
    /// Cargo-feature-aware so the same construction works under sync
    /// and tokio runtimes.
    fn build_both_rpc_cell_for_test(owner: Arc<BothFixture>) -> Arc<internal::HostedRpcOwnerCell> {
        #[cfg(feature = "tokio")]
        {
            Arc::new(internal::HostedRpcOwnerCell::from_shared_owner_async(
                owner,
                |o, idx, args| Box::pin(BothFixture::dispatch_shared(o, idx, args)),
            ))
        }
        #[cfg(not(feature = "tokio"))]
        {
            Arc::new(internal::HostedRpcOwnerCell::from_shared_owner_sync(
                owner,
                |o, idx, args| BothFixture::dispatch_shared(o, idx, args),
            ))
        }
    }

    /// `__test_r_make_hosted_both_shared(owner_arc, rpc_cell)` captures
    /// the owner's descriptor bytes once and stitches together the
    /// shared `Arc<T>` with the prebuilt `HostedRpcOwnerCell`. The
    /// captured descriptor must match the owner's `HostedDep::descriptor()`
    /// (or `AsyncHostedDep::descriptor()` under tokio) output exactly.
    #[test]
    fn make_hosted_both_shared_captures_descriptor_bytes() {
        let (_owner, shared) = build_both_shared_for_test(BothFixture::new(vec![10, 20, 30]));
        assert_eq!(shared.descriptor_bytes(), &[10, 20, 30]);
        // The owner cell must be live: a dispatch call must succeed
        // (the closure runs the method without panicking and returns
        // a non-empty reply). Under the tokio feature the cell is the
        // async variant — drive it through `dispatch_async` on a
        // tokio runtime; under the sync feature the cell is sync.
        let reply =
            dispatch_cell_for_test(&shared.rpc_cell(), 1, &[]).expect("dispatch must succeed");
        assert_eq!(reply, 1u64.to_be_bytes().to_vec());
    }

    /// The owner the cell dispatches against must be the exact same
    /// `Arc<T>` the parent-side getter would return. This pins the
    /// regression fix for the bug where `worker = both(...)` made the
    /// parent dep map hold an `Arc<HostedBothShared>` while the
    /// generated owner getter expected `Arc<T>` and panicked.
    #[test]
    fn make_hosted_both_shared_exposes_owner_arc() {
        let (owner_arc, shared) = build_both_shared_for_test(BothFixture::new(vec![42]));
        let recovered = shared.owner_arc::<BothFixture>();
        assert!(
            Arc::ptr_eq(&owner_arc, &recovered),
            "owner_arc::<T>() must return the very same Arc the cell holds"
        );
        // A round trip through the cell observes the same owner via
        // the shared counter that owner_arc points at.
        let _ = dispatch_cell_for_test(&shared.rpc_cell(), 1, &[]).expect("dispatch must succeed");
        let observed = *recovered
            .counter
            .lock()
            .expect("BothFixture counter must not be poisoned");
        assert_eq!(
            observed, 1,
            "the cell must mutate the same owner the parent getter would hand out, observed {observed}"
        );
    }

    /// Helper: dispatch on a `HostedRpcOwnerCell` whose async/sync
    /// variant depends on the active cargo feature. Used by the
    /// `worker = both(T)` helper tests so the test bodies don't need
    /// to know whether the cell was built via `from_owner` or
    /// `from_async_owner` — both surface the same per-call result.
    fn dispatch_cell_for_test(
        cell: &internal::HostedRpcOwnerCell,
        method_idx: u32,
        args: &[u8],
    ) -> Result<Vec<u8>, String> {
        #[cfg(feature = "tokio")]
        {
            // `tokio` resolves to the local `mod tokio` inside this
            // crate; reach the external crate with `::tokio`.
            let rt = ::tokio::runtime::Builder::new_multi_thread()
                .enable_all()
                .build()
                .expect("build tokio runtime");
            rt.block_on(cell.dispatch_async(method_idx, args))
        }
        #[cfg(not(feature = "tokio"))]
        {
            cell.dispatch(method_idx, args)
        }
    }

    /// `__test_r_make_hosted_both_codec().to_wire` downcasts the
    /// shared cell and returns its captured descriptor bytes — the
    /// same shape the worker-side reconstructor expects.
    #[test]
    fn make_hosted_both_codec_serializes_descriptor_bytes() {
        let codec = __test_r_make_hosted_both_codec();
        let (_owner, shared) = build_both_shared_for_test(BothFixture::new(vec![1, 2, 3, 4]));
        let shared = Arc::new(shared);
        let arc_any: Arc<dyn Any + Send + Sync> = shared;

        let wire_bytes = (codec.to_wire)(arc_any);
        assert_eq!(wire_bytes, vec![1, 2, 3, 4]);

        // `from_wire_bytes` must produce the same `Arc<Vec<u8>>`
        // payload shape the existing Hosted reconstructor consumes —
        // otherwise the worker side would not be able to reuse the
        // standard `__test_r_make_hosted_worker_reconstructor`.
        let wire_payload = (codec.from_wire_bytes)(&wire_bytes);
        let recovered_bytes: Arc<Vec<u8>> = wire_payload
            .downcast::<Vec<u8>>()
            .expect("from_wire_bytes must produce Arc<Vec<u8>>");
        assert_eq!(*recovered_bytes, vec![1, 2, 3, 4]);
    }

    /// `__test_r_make_hosted_both_rpc_factory::<T, Stub>().owner_into_cell`
    /// reaches into the shared cell, hands back the inner
    /// `Arc<HostedRpcOwnerCell>`, and a dispatched call hits the
    /// real owner (proven by the counter incrementing).
    #[test]
    fn make_hosted_both_rpc_factory_extracts_owner_cell() {
        let factory = __test_r_make_hosted_both_rpc_factory::<BothFixture, BothStub>();
        let (_owner, shared) = build_both_shared_for_test(BothFixture::new(vec![]));
        let shared = Arc::new(shared);
        let arc_any: Arc<dyn Any + Send + Sync> = shared.clone();

        let cell = (factory.owner_into_cell)(arc_any);
        assert!(
            Arc::ptr_eq(&cell, &shared.rpc_cell()),
            "factory must return the exact same inner HostedRpcOwnerCell Arc the \
             shared cell holds; otherwise the RPC view would dispatch against a \
             different owner than the descriptor view captured"
        );

        // The cell is functional: dispatch routes to the real owner
        // method (counter starts at 0; first call must yield 1). Same
        // cargo-feature-aware dispatch as
        // `make_hosted_both_shared_captures_descriptor_bytes` since the
        // underlying cell shares the same async/sync split.
        let reply = dispatch_cell_for_test(&cell, 1, &[]).expect("dispatch must succeed");
        assert_eq!(reply, 1u64.to_be_bytes().to_vec());
    }

    /// `__test_r_make_hosted_both_rpc_factory::<T, Stub>().build_stub`
    /// constructs a worker-side `Stub` via the user's
    /// `HostedRpcDep::build_stub` and boxes it as `Arc<dyn Any>` so
    /// the runtime can route it through the standard dep view.
    #[test]
    fn make_hosted_both_rpc_factory_builds_stub() {
        use internal::{HostedRpcChannel, HostedRpcError, HostedRpcTransport};

        // Minimal in-process transport stand-in: every call returns
        // the unit reply. We don't actually call into it; the test
        // just exercises that build_stub produces a downcastable
        // `Arc<dyn Any>` carrying the channel.
        struct DummyTransport;
        impl HostedRpcTransport for DummyTransport {
            fn call(
                &self,
                _dep_id: &str,
                _method_idx: u32,
                _args: Vec<u8>,
            ) -> Result<Vec<u8>, HostedRpcError> {
                Ok(Vec::new())
            }
        }

        let factory = __test_r_make_hosted_both_rpc_factory::<BothFixture, BothStub>();
        let transport: Arc<dyn HostedRpcTransport> = Arc::new(DummyTransport);
        let channel = HostedRpcChannel::new("test::both_fixture".to_string(), transport);

        let stub_any: Arc<dyn Any + Send + Sync> = (factory.build_stub)(channel);
        let _stub: Arc<BothStub> = stub_any
            .downcast::<BothStub>()
            .expect("build_stub must produce the BothFixture::Stub type");
    }
}