test_r_core/lib.rs
1pub mod args;
2pub mod bench;
3mod execution;
4pub mod internal;
5mod ipc;
6mod output;
7mod panic_hook;
8pub mod spawn;
9mod stats;
10#[cfg(feature = "tokio")]
11mod tokio;
12pub mod worker;
13
14#[allow(dead_code)]
15mod sync;
16
17#[cfg(not(feature = "tokio"))]
18pub use sync::test_runner;
19
20#[cfg(feature = "tokio")]
21pub use tokio::test_runner;
22
23pub use worker::worker_index;
24
25/// Re-export of [`desert_rust`] so that proc-macros emitted by
26/// `test-r-macro` (e.g. [`test_r::hosted_rpc`]) can refer to it via
27/// `::test_r::core::desert_rust::...` without forcing downstream
28/// users to add `desert_rust` to their own `Cargo.toml`.
29///
30/// This is an internal support re-export — user code should not depend
31/// on it being available at this path. The `#[doc(hidden)]` attribute
32/// keeps it out of the rendered docs and signals that the surface
33/// belongs to the macro support layer, not the public test-r API.
34#[doc(hidden)]
35pub use desert_rust;
36
37// =====================================================================
38// Hosted descriptor codec / worker reconstructor helpers.
39//
40// These two functions are the single place the descriptor-based Hosted
41// dep wiring is built. The `tokio` cargo feature on `test-r-core`
42// selects which variant of each function compiles, so that:
43//
44// - Under the **tokio** runtime, Hosted deps always use the *async*
45// path (`AsyncHostedDep::descriptor` / `from_descriptor`). Thanks to the
46// blanket `impl<T: HostedDep> AsyncHostedDep for T`, every sync `HostedDep`
47// automatically reaches the async path with no user-visible cost (the
48// bridged `from_descriptor` just wraps the sync impl in
49// `std::future::ready(...)`).
50// - Under the **sync** runtime, Hosted deps always use the *sync* path
51// (`HostedDep::descriptor` / `from_descriptor`). This intentionally
52// keeps sync builds free of any block-poll machinery: a Hosted dep
53// that only implements `AsyncHostedDep` simply fails to compile
54// here, rather than panicking at runtime.
55//
56// The macro now emits a single uniform call to these helpers regardless
57// of the (now deprecated) `async_worker` attribute; see
58// `test-r-macro/src/deps.rs`.
59// =====================================================================
60
61/// **Hidden macro-support helper.**
62///
63/// Build the parent-side codec for a Hosted dep. Under the `tokio`
64/// runtime this dispatches through [`internal::AsyncHostedDep`]; under
65/// the sync runtime it dispatches through [`internal::HostedDep`]. The
66/// returned [`internal::CloneableCodec`] knows how to:
67///
68/// - downcast the owner `Arc<dyn Any + Send + Sync>` back to `T` and
69/// produce the descriptor bytes (`to_wire`), and
70/// - turn raw descriptor bytes into a boxed payload that the matching
71/// reconstructor below will hand off to `from_descriptor`
72/// (`from_wire_bytes`).
73///
74/// Not part of the public API; only the proc-macro emits calls to it.
75#[doc(hidden)]
76#[cfg(feature = "tokio")]
77pub fn __test_r_make_hosted_codec<T>() -> internal::CloneableCodec
78where
79 T: internal::AsyncHostedDep,
80{
81 use std::sync::Arc;
82 internal::CloneableCodec {
83 to_wire: Arc::new(|any: Arc<dyn std::any::Any + Send + Sync>| {
84 let value: Arc<T> = any
85 .downcast::<T>()
86 .expect("Hosted dependency type mismatch in descriptor()");
87 <T as internal::AsyncHostedDep>::descriptor(&*value)
88 }),
89 from_wire_bytes: Arc::new(|bytes: &[u8]| {
90 // The "wire payload" for a Hosted dep is the raw descriptor
91 // bytes; the matching reconstructor will run from_descriptor
92 // against them on the worker side.
93 let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(bytes.to_vec());
94 boxed
95 }),
96 }
97}
98
99/// **Hidden macro-support helper.** Sync-runtime variant of
100/// [`__test_r_make_hosted_codec`]; see that doc-comment.
101#[doc(hidden)]
102#[cfg(not(feature = "tokio"))]
103pub fn __test_r_make_hosted_codec<T>() -> internal::CloneableCodec
104where
105 T: internal::HostedDep,
106{
107 use std::sync::Arc;
108 internal::CloneableCodec {
109 to_wire: Arc::new(|any: Arc<dyn std::any::Any + Send + Sync>| {
110 let value: Arc<T> = any
111 .downcast::<T>()
112 .expect("Hosted dependency type mismatch in descriptor()");
113 <T as internal::HostedDep>::descriptor(&*value)
114 }),
115 from_wire_bytes: Arc::new(|bytes: &[u8]| {
116 let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(bytes.to_vec());
117 boxed
118 }),
119 }
120}
121
122/// **Hidden macro-support helper.**
123///
124/// Build the worker-side [`internal::WorkerReconstructor`] for a Hosted
125/// dep. Under the `tokio` runtime this returns an
126/// [`internal::WorkerReconstructor::Async`] closure that awaits
127/// [`internal::AsyncHostedDep::from_descriptor`]; under the sync
128/// runtime it returns an [`internal::WorkerReconstructor::Sync`]
129/// closure that calls [`internal::HostedDep::from_descriptor`].
130///
131/// The descriptor `Vec<u8>` is produced by the matching
132/// [`__test_r_make_hosted_codec`] above; the two helpers must stay in
133/// lockstep.
134///
135/// Not part of the public API; only the proc-macro emits calls to it.
136#[doc(hidden)]
137#[cfg(feature = "tokio")]
138pub fn __test_r_make_hosted_worker_reconstructor<T>() -> internal::WorkerReconstructor
139where
140 T: internal::AsyncHostedDep,
141{
142 use std::sync::Arc;
143 internal::WorkerReconstructor::Async(Arc::new(
144 |wire_payload: Arc<dyn std::any::Any + Send + Sync>, _deps| {
145 Box::pin(async move {
146 let bytes: Arc<Vec<u8>> = wire_payload
147 .downcast::<Vec<u8>>()
148 .expect("Hosted worker reconstructor expected Vec<u8> descriptor payload");
149 let value: T = <T as internal::AsyncHostedDep>::from_descriptor(&bytes).await;
150 let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(value);
151 boxed
152 })
153 },
154 ))
155}
156
157/// **Hidden macro-support helper.** Sync-runtime variant of
158/// [`__test_r_make_hosted_worker_reconstructor`]; see that
159/// doc-comment.
160#[doc(hidden)]
161#[cfg(not(feature = "tokio"))]
162pub fn __test_r_make_hosted_worker_reconstructor<T>() -> internal::WorkerReconstructor
163where
164 T: internal::HostedDep,
165{
166 use std::sync::Arc;
167 internal::WorkerReconstructor::Sync(Arc::new(
168 |wire_payload: Arc<dyn std::any::Any + Send + Sync>, _deps| {
169 let bytes: Arc<Vec<u8>> = wire_payload
170 .downcast::<Vec<u8>>()
171 .expect("Hosted worker reconstructor expected Vec<u8> descriptor payload");
172 let value: T = <T as internal::HostedDep>::from_descriptor(&bytes);
173 let boxed: Arc<dyn std::any::Any + Send + Sync> = Arc::new(value);
174 boxed
175 },
176 ))
177}
178
179// =====================================================================
180// `worker = both(T)` helpers.
181//
182// `#[test_dep(scope = Hosted, worker = both(Trait))]` is lowered by the
183// macro into two `RegisteredDependency` entries (one Hosted, one
184// HostedRpc) that share a single parent-side owner via
185// [`internal::HostedBothShared`]. The three helpers below centralize
186// the shared logic:
187//
188// - `__test_r_make_hosted_both_shared::<T>(owner)` builds the cell
189// used by the macro's weak cache. Cfg-selected on `tokio` so the descriptor
190// call uses `AsyncHostedDep::descriptor` under tokio and
191// `HostedDep::descriptor` under sync, mirroring the single-view helpers.
192// - `__test_r_make_hosted_both_codec()` produces the Hosted-view
193// codec; both bytes (`to_wire`) and payload (`from_wire_bytes`)
194// shapes match the existing single-view Hosted codec so the
195// runtime worker side stays unchanged.
196// - `__test_r_make_hosted_both_rpc_factory::<T>()` produces the
197// HostedRpc-view factory. It downcasts the shared cell to extract
198// the inner `Arc<HostedRpcOwnerCell>`, and reuses the same
199// `build_stub(channel)` path the legacy HostedRpc factory uses.
200// =====================================================================
201
202/// **Hidden macro-support helper.** Build the shared owner cell for
203/// a `worker = both(T)` dep. Tokio variant: descriptor is computed
204/// via [`internal::AsyncHostedDep::descriptor`].
205#[doc(hidden)]
206#[cfg(feature = "tokio")]
207pub fn __test_r_make_hosted_both_shared<T>(owner: T) -> internal::HostedBothShared
208where
209 T: internal::AsyncHostedDep + internal::HostedRpcDep,
210{
211 use std::sync::Arc;
212 let descriptor_bytes = <T as internal::AsyncHostedDep>::descriptor(&owner);
213 let rpc_cell = Arc::new(internal::HostedRpcOwnerCell::from_owner(owner));
214 internal::HostedBothShared::new(descriptor_bytes, rpc_cell)
215}
216
217/// **Hidden macro-support helper.** Sync-runtime variant of
218/// [`__test_r_make_hosted_both_shared`]; descriptor is computed via
219/// [`internal::HostedDep::descriptor`].
220#[doc(hidden)]
221#[cfg(not(feature = "tokio"))]
222pub fn __test_r_make_hosted_both_shared<T>(owner: T) -> internal::HostedBothShared
223where
224 T: internal::HostedDep + internal::HostedRpcDep,
225{
226 use std::sync::Arc;
227 let descriptor_bytes = <T as internal::HostedDep>::descriptor(&owner);
228 let rpc_cell = Arc::new(internal::HostedRpcOwnerCell::from_owner(owner));
229 internal::HostedBothShared::new(descriptor_bytes, rpc_cell)
230}
231
232/// **Hidden macro-support helper.** Hosted-view codec for the `both`
233/// variant. Wire format is identical to the existing single-view
234/// Hosted codec so the worker reconstructor (still
235/// [`__test_r_make_hosted_worker_reconstructor`]) stays unchanged.
236#[doc(hidden)]
237pub fn __test_r_make_hosted_both_codec() -> internal::CloneableCodec {
238 use std::any::Any;
239 use std::sync::Arc;
240 internal::CloneableCodec {
241 to_wire: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
242 let shared: Arc<internal::HostedBothShared> = any
243 .downcast::<internal::HostedBothShared>()
244 .expect("HostedBothShared downcast failed in both-codec to_wire");
245 shared.descriptor_bytes().to_vec()
246 }),
247 from_wire_bytes: Arc::new(|bytes: &[u8]| {
248 // Same payload shape as the single-view Hosted codec: a
249 // boxed `Vec<u8>` for the worker reconstructor to consume.
250 let boxed: Arc<dyn Any + Send + Sync> = Arc::new(bytes.to_vec());
251 boxed
252 }),
253 }
254}
255
256/// **Hidden macro-support helper.** HostedRpc-view factory for the
257/// `both` variant. Pulls the inner `Arc<HostedRpcOwnerCell>` out of
258/// the shared cell and reuses the user's
259/// [`internal::HostedRpcDep::build_stub`] for the worker stub.
260#[doc(hidden)]
261pub fn __test_r_make_hosted_both_rpc_factory<T, Stub>() -> internal::RpcFactory
262where
263 T: internal::HostedRpcDep<Stub = Stub>,
264 Stub: Send + Sync + 'static,
265{
266 use std::any::Any;
267 use std::sync::Arc;
268 internal::RpcFactory {
269 owner_into_cell: Arc::new(|any: Arc<dyn Any + Send + Sync>| {
270 let shared: Arc<internal::HostedBothShared> = any
271 .downcast::<internal::HostedBothShared>()
272 .expect("HostedBothShared downcast failed in both-rpc-factory owner_into_cell");
273 shared.rpc_cell()
274 }),
275 build_stub: Arc::new(|channel: internal::HostedRpcChannel| {
276 let stub: Stub = <T as internal::HostedRpcDep>::build_stub(channel);
277 let boxed: Arc<dyn Any + Send + Sync> = Arc::new(stub);
278 boxed
279 }),
280 }
281}
282
283#[cfg(test)]
284mod hosted_helper_tests {
285 //! Exercise the feature-gated
286 //! [`__test_r_make_hosted_codec`] /
287 //! [`__test_r_make_hosted_worker_reconstructor`] helpers end to
288 //! end against a tiny `HostedDep` fixture.
289 //!
290 //! Both helper variants must reject `WorkerReconstructor::Sync`
291 //! vs `Async` choice at the cargo-feature level, so we keep this
292 //! test cfg-aware: it asserts the matching variant under each
293 //! feature.
294 use super::*;
295 use std::any::Any;
296 use std::sync::Arc;
297
298 /// Minimal sync `HostedDep` fixture. Under the `tokio` feature
299 /// the blanket bridge also makes it `AsyncHostedDep`, so the same fixture
300 /// is usable against both helper variants.
301 #[derive(Debug, PartialEq, Eq)]
302 struct Fixture {
303 bytes: Vec<u8>,
304 }
305
306 impl internal::HostedDep for Fixture {
307 fn descriptor(&self) -> Vec<u8> {
308 self.bytes.clone()
309 }
310 fn from_descriptor(bytes: &[u8]) -> Self {
311 Self {
312 bytes: bytes.to_vec(),
313 }
314 }
315 }
316
317 #[test]
318 fn make_hosted_codec_round_trips_descriptor_bytes() {
319 let codec = __test_r_make_hosted_codec::<Fixture>();
320 let owner: Arc<dyn Any + Send + Sync> = Arc::new(Fixture {
321 bytes: vec![1, 2, 3, 4],
322 });
323
324 let wire_bytes = (codec.to_wire)(owner);
325 assert_eq!(wire_bytes, vec![1, 2, 3, 4]);
326
327 let wire_payload = (codec.from_wire_bytes)(&wire_bytes);
328 let recovered_bytes: Arc<Vec<u8>> = wire_payload
329 .downcast::<Vec<u8>>()
330 .expect("from_wire_bytes must produce Arc<Vec<u8>>");
331 assert_eq!(*recovered_bytes, vec![1, 2, 3, 4]);
332 }
333
334 /// Under the tokio runtime, the worker reconstructor helper must
335 /// return [`internal::WorkerReconstructor::Async`].
336 #[cfg(feature = "tokio")]
337 #[test]
338 fn make_hosted_worker_reconstructor_is_async_under_tokio() {
339 let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();
340 match recon {
341 internal::WorkerReconstructor::Async(_) => {}
342 internal::WorkerReconstructor::Sync(_) => panic!(
343 "tokio build must produce a WorkerReconstructor::Async for Hosted deps; got Sync"
344 ),
345 }
346 }
347
348 /// Under the sync runtime, the worker reconstructor helper must
349 /// return [`internal::WorkerReconstructor::Sync`] so the sync
350 /// runner can drive it without any block-poll machinery.
351 #[cfg(not(feature = "tokio"))]
352 #[test]
353 fn make_hosted_worker_reconstructor_is_sync_under_sync_runtime() {
354 let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();
355 match recon {
356 internal::WorkerReconstructor::Sync(_) => {}
357 internal::WorkerReconstructor::Async(_) => panic!(
358 "sync build must produce a WorkerReconstructor::Sync for Hosted deps; got Async"
359 ),
360 }
361 }
362
363 /// Drive the sync-runtime reconstructor closure end to end on
364 /// the matching descriptor bytes the codec produces. Pinned only
365 /// for the sync build because the tokio build returns an Async
366 /// closure that needs a runtime to await.
367 #[cfg(not(feature = "tokio"))]
368 #[test]
369 fn sync_worker_reconstructor_rebuilds_fixture_from_descriptor() {
370 // Re-use a small `DependencyView` impl: the helper's worker
371 // closure ignores the view, so we pass an empty stub.
372 #[derive(Debug)]
373 struct EmptyView;
374 impl internal::DependencyView for EmptyView {
375 fn get(&self, _name: &str) -> Option<Arc<dyn Any + Send + Sync>> {
376 None
377 }
378 }
379
380 let codec = __test_r_make_hosted_codec::<Fixture>();
381 let recon = __test_r_make_hosted_worker_reconstructor::<Fixture>();
382
383 let owner: Arc<dyn Any + Send + Sync> = Arc::new(Fixture {
384 bytes: vec![5, 6, 7],
385 });
386 let wire_bytes = (codec.to_wire)(owner);
387 let payload = (codec.from_wire_bytes)(&wire_bytes);
388
389 let deps: Arc<dyn internal::DependencyView + Send + Sync> = Arc::new(EmptyView);
390 let rebuilt = match recon {
391 internal::WorkerReconstructor::Sync(f) => f(payload, deps),
392 internal::WorkerReconstructor::Async(_) => unreachable!(
393 "sync build cannot return Async; pinned by \
394 make_hosted_worker_reconstructor_is_sync_under_sync_runtime",
395 ),
396 };
397 let rebuilt: Arc<Fixture> = rebuilt
398 .downcast::<Fixture>()
399 .expect("worker reconstructor must produce the original Hosted dep type");
400 assert_eq!(
401 *rebuilt,
402 Fixture {
403 bytes: vec![5, 6, 7]
404 }
405 );
406 }
407
408 // -----------------------------------------------------------------
409 // `worker = both(T)` helper tests.
410 //
411 // The macro lowering for `#[test_dep(scope = Hosted, worker =
412 // both(Trait))]` is exercised end-to-end by the
413 // `sharing::hosted_both_basic` example fixtures. These unit tests
414 // pin the three pieces of cargo-feature-aware glue in this file:
415 //
416 // - `__test_r_make_hosted_both_shared::<T>(owner)` builds the
417 // shared cell that the macro's weak cache hands back to both
418 // the Hosted and HostedRpc registrations.
419 // - `__test_r_make_hosted_both_codec()` serializes the cached
420 // descriptor bytes for the Hosted view.
421 // - `__test_r_make_hosted_both_rpc_factory::<T>()` extracts the
422 // inner `Arc<HostedRpcOwnerCell>` for the HostedRpc view and
423 // builds the worker-side stub via `HostedRpcDep::build_stub`.
424 // -----------------------------------------------------------------
425
426 /// Minimal `HostedDep + HostedRpcDep` fixture for helper tests. The id
427 /// allocator stands in for any tiny control surface; the bytes field doubles
428 /// as the descriptor.
429 #[derive(Debug)]
430 struct BothFixture {
431 bytes: Vec<u8>,
432 counter: std::sync::Mutex<u64>,
433 }
434
435 impl BothFixture {
436 fn new(bytes: Vec<u8>) -> Self {
437 Self {
438 bytes,
439 counter: std::sync::Mutex::new(0),
440 }
441 }
442 }
443
444 impl internal::HostedDep for BothFixture {
445 fn descriptor(&self) -> Vec<u8> {
446 self.bytes.clone()
447 }
448 fn from_descriptor(bytes: &[u8]) -> Self {
449 Self::new(bytes.to_vec())
450 }
451 }
452
453 /// Stub view for the BothFixture. Holds a HostedRpcChannel so a
454 /// realistic build_stub round-trip is exercised; the tests below
455 /// just verify the factory hands back a usable `Arc<BothStub>`,
456 /// not a full IPC round-trip (covered by the example fixtures).
457 pub struct BothStub {
458 _channel: internal::HostedRpcChannel,
459 }
460
461 impl internal::HostedRpcDep for BothFixture {
462 type Stub = BothStub;
463 fn dispatch(&mut self, method_idx: u32, _args: &[u8]) -> Result<Vec<u8>, String> {
464 // Single method: bump the counter and return its value.
465 if method_idx == 1 {
466 let mut g = self.counter.lock().map_err(|e| e.to_string())?;
467 *g += 1;
468 Ok(g.to_be_bytes().to_vec())
469 } else {
470 Err(format!("BothFixture: unknown method_idx {method_idx}"))
471 }
472 }
473 fn build_stub(channel: internal::HostedRpcChannel) -> Self::Stub {
474 BothStub { _channel: channel }
475 }
476 }
477
478 /// `__test_r_make_hosted_both_shared(owner)` captures the
479 /// owner's descriptor bytes once and wraps the owner in a
480 /// `HostedRpcOwnerCell`. The descriptor must match the owner's
481 /// `HostedDep::descriptor()` output exactly.
482 #[test]
483 fn make_hosted_both_shared_captures_descriptor_bytes() {
484 let owner = BothFixture::new(vec![10, 20, 30]);
485 let shared = __test_r_make_hosted_both_shared::<BothFixture>(owner);
486 assert_eq!(shared.descriptor_bytes(), &[10, 20, 30]);
487 // The owner cell must be live: a dispatch call must succeed
488 // (the closure runs the method without panicking and returns
489 // a non-empty reply).
490 let reply = shared
491 .rpc_cell()
492 .dispatch(1, &[])
493 .expect("dispatch must succeed");
494 assert_eq!(reply, 1u64.to_be_bytes().to_vec());
495 }
496
497 /// `__test_r_make_hosted_both_codec().to_wire` downcasts the
498 /// shared cell and returns its captured descriptor bytes — the
499 /// same shape the worker-side reconstructor expects.
500 #[test]
501 fn make_hosted_both_codec_serializes_descriptor_bytes() {
502 let codec = __test_r_make_hosted_both_codec();
503 let shared: Arc<internal::HostedBothShared> =
504 Arc::new(__test_r_make_hosted_both_shared::<BothFixture>(
505 BothFixture::new(vec![1, 2, 3, 4]),
506 ));
507 let arc_any: Arc<dyn Any + Send + Sync> = shared;
508
509 let wire_bytes = (codec.to_wire)(arc_any);
510 assert_eq!(wire_bytes, vec![1, 2, 3, 4]);
511
512 // `from_wire_bytes` must produce the same `Arc<Vec<u8>>`
513 // payload shape the existing Hosted reconstructor consumes —
514 // otherwise the worker side would not be able to reuse the
515 // standard `__test_r_make_hosted_worker_reconstructor`.
516 let wire_payload = (codec.from_wire_bytes)(&wire_bytes);
517 let recovered_bytes: Arc<Vec<u8>> = wire_payload
518 .downcast::<Vec<u8>>()
519 .expect("from_wire_bytes must produce Arc<Vec<u8>>");
520 assert_eq!(*recovered_bytes, vec![1, 2, 3, 4]);
521 }
522
523 /// `__test_r_make_hosted_both_rpc_factory::<T, Stub>().owner_into_cell`
524 /// reaches into the shared cell, hands back the inner
525 /// `Arc<HostedRpcOwnerCell>`, and a dispatched call hits the
526 /// real owner (proven by the counter incrementing).
527 #[test]
528 fn make_hosted_both_rpc_factory_extracts_owner_cell() {
529 let factory = __test_r_make_hosted_both_rpc_factory::<BothFixture, BothStub>();
530 let shared: Arc<internal::HostedBothShared> = Arc::new(__test_r_make_hosted_both_shared::<
531 BothFixture,
532 >(BothFixture::new(vec![])));
533 let arc_any: Arc<dyn Any + Send + Sync> = shared.clone();
534
535 let cell = (factory.owner_into_cell)(arc_any);
536 assert!(
537 Arc::ptr_eq(&cell, &shared.rpc_cell()),
538 "factory must return the exact same inner HostedRpcOwnerCell Arc the \
539 shared cell holds; otherwise the RPC view would dispatch against a \
540 different owner than the descriptor view captured"
541 );
542
543 // The cell is functional: dispatch routes to the real owner
544 // method (counter starts at 0; first call must yield 1).
545 let reply = cell.dispatch(1, &[]).expect("dispatch must succeed");
546 assert_eq!(reply, 1u64.to_be_bytes().to_vec());
547 }
548
549 /// `__test_r_make_hosted_both_rpc_factory::<T, Stub>().build_stub`
550 /// constructs a worker-side `Stub` via the user's
551 /// `HostedRpcDep::build_stub` and boxes it as `Arc<dyn Any>` so
552 /// the runtime can route it through the standard dep view.
553 #[test]
554 fn make_hosted_both_rpc_factory_builds_stub() {
555 use internal::{HostedRpcChannel, HostedRpcError, HostedRpcTransport};
556
557 // Minimal in-process transport stand-in: every call returns
558 // the unit reply. We don't actually call into it; the test
559 // just exercises that build_stub produces a downcastable
560 // `Arc<dyn Any>` carrying the channel.
561 struct DummyTransport;
562 impl HostedRpcTransport for DummyTransport {
563 fn call(
564 &self,
565 _dep_id: &str,
566 _method_idx: u32,
567 _args: Vec<u8>,
568 ) -> Result<Vec<u8>, HostedRpcError> {
569 Ok(Vec::new())
570 }
571 }
572
573 let factory = __test_r_make_hosted_both_rpc_factory::<BothFixture, BothStub>();
574 let transport: Arc<dyn HostedRpcTransport> = Arc::new(DummyTransport);
575 let channel = HostedRpcChannel::new("test::both_fixture".to_string(), transport);
576
577 let stub_any: Arc<dyn Any + Send + Sync> = (factory.build_stub)(channel);
578 let _stub: Arc<BothStub> = stub_any
579 .downcast::<BothStub>()
580 .expect("build_stub must produce the BothFixture::Stub type");
581 }
582}