phase 19g: SOME/IP Client+Server roundtrip over embassy-net loopback#103
Conversation
Lifts the parent crate's `tests/bare_metal_e2e.rs` harness onto the two-stack `LoopbackDriver` pair from 19e, using 19f's relaxed `Server` bounds (`SocketHandle` abstraction) so an `Arc<EmbassyNetSocket>` — `!Sync` because embassy-net's `UdpSocket<'static>` borrows from `Stack`'s `RefCell<Inner<D>>` — satisfies `H` for the Server. Two new tests in `simple-someip-embassy-net/tests/loopback.rs`: - `client_receives_server_sd_announcement`: real Server on stack A emits SD `OfferService` via `announcement_loop_local` (19f's `!Send` variant), real Client on stack B receives it via `bind_discovery`. Asserts a `ClientUpdate::DiscoveryUpdated` surfaces within 5 s. Both stacks join 224.0.23.0 at the smoltcp level before construction (the adapter's `join_multicast_v4` is a documented no-op since multicast group membership lives on the `Stack`, not the `UdpSocket`). - `client_send_request_server_runloop_stable`: passive Server on stack A; Client on stack B does `add_endpoint` + `send_to_service` to push a SOME/IP request through the loopback. Asserts the send returns Ok and the run-loop survives. No response assertion because `simple_someip::Server` exposes no public request-handler API — matching the parent crate's reference test. Harness additions: - `define_static_channels!` `LoopbackTestChannels` with the same pool sizing shape as `tests/bare_metal_e2e.rs`'s `E2ETestChannels`. - `LocalTokioSpawner: LocalSpawner` over `tokio::task::spawn_local`. `LocalSpawner` (not `Spawner`) because the Client's run-future captures `&self.unicast_socket`-style borrows across awaits over a `!Sync` socket, making the run future itself `!Send`. - `LocalTimer: Timer` over `tokio::time::sleep`, boxed-future shape matching the bare-metal-e2e `MockTimer`. - `MockSubscriptions(Arc<Mutex<Vec<SubKey>>>)` — same shape as bare-metal-e2e's mock. Cargo.toml: dev-dependency `simple-someip` is now re-pinned with `features = ["client", "server", "bare_metal", "std"]`. The `std` addition gates `RawPayload` / `VecSdHeader` / `Arc<Mutex<E2ERegistry>>` default impls — all needed for the host-side test harness — without affecting the production `[dependencies]` build (which stays `default-features = false`). Type-inference notes: - Both Server constructions in the new tests carry an explicit `Server<_, _, _, _, Arc<simple_someip_embassy_net::EmbassyNetSocket>>` annotation. Without it the compiler can't decide `H` across the `ServerDeps` indirection — same situation as `simple_someip`'s own SD-NACK test (`mod.rs:1345`) addresses. - `embassy_net::Stack::join_multicast_group` takes `T: Into<IpAddress>` but embassy-net 0.4 has no `core::net::Ipv4Addr -> IpAddress` blanket impl. Constructed via `embassy_net::Ipv4Address(addr.octets())` per the smoltcp shape. Gates green: - cargo fmt --check - cargo clippy -p simple-someip-embassy-net --all-targets -D warnings - cargo test -p simple-someip-embassy-net --test loopback (3/3 pass) - cargo build -p simple-someip-embassy-net --target thumbv7em-none-eabihf - cargo check --workspace --all-targets Phase 19 status: 19a-g all complete. Remaining 19 sub-phases: - 19h — `examples/embassy_net_client/` in-tree binary example. - 19i — CI: cross-build for thumbv7em + run loopback test. - 19j — Adapter README / CHANGELOG / `simple-someip-embassy-net` 0.1.0 release. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds end-to-end SOME/IP Client+Server integration coverage to the simple-someip-embassy-net adapter by running two embassy_net::Stack instances connected via an in-memory loopback driver, and updates test-only dependencies to support host-side execution.
Changes:
- Extend
tests/loopback.rsfrom adapter-level UDP validation to full Client+Server roundtrip scenarios over the two-stackLoopbackDriversetup. - Introduce a host-only harness in the loopback tests (static channel pools,
LocalSpawner,Timer, mock subscriptions) to drive!Send/!Syncembassy-net sockets on a single-threaded Tokio runtime. - Re-pin
simple-someipas a dev-dependency withstdenabled so the host-side test harness can use std-gated types/impls.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| simple-someip-embassy-net/tests/loopback.rs | Adds two SOME/IP-level loopback integration tests plus local spawner/timer/subscription harness to support !Send execution. |
| simple-someip-embassy-net/Cargo.toml | Enables simple-someip std feature for tests via dev-dependency override. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Drive the run-loop locally — `!Send` because | ||
| // `EmbassyNetSocket: !Sync`. | ||
| tokio::task::spawn_local(async move { | ||
| let _ = server.run().await; | ||
| }); |
There was a problem hiding this comment.
This test constructs a passive server via Server::new_passive_with_deps, but then calls server.run(). In simple_someip::Server, run() on a passive server returns Err(Error::InvalidUsage("passive_server_run")) immediately, so this spawned task exits right away and the test doesn’t actually exercise a stable server run-loop or prove the request is received. Use Server::new_with_deps (and simply don’t start announcement_loop_local) if you need a running loop, or adjust the test expectations accordingly.
| // 17. | ||
|
|
||
| use core::pin::Pin; | ||
| use core::task::Poll; |
There was a problem hiding this comment.
Poll isn’t needed for the LocalSpawner impl in this file, and the later dummy _poll_use function exists only to silence an unused-import warning. Please remove the Poll import and the dummy function, or (if you really expect future hand-rolled futures here) use a targeted #[allow(unused_imports)] on the import instead of adding dead code.
| use core::task::Poll; |
| // `Poll` is imported above for `LocalSpawner` impls; flag it as | ||
| // in-use so a `cargo clippy --tests -D warnings` build doesn't | ||
| // trip on the otherwise-unused import. (`Poll` is brought in | ||
| // because it's the canonical paired import alongside `Pin` for | ||
| // hand-rolled futures, even though `LoopbackTestChannels`' | ||
| // generated code uses the higher-level macro shape.) | ||
| #[allow(dead_code)] | ||
| fn _poll_use(p: Poll<()>) -> Poll<()> { | ||
| p | ||
| } | ||
|
|
There was a problem hiding this comment.
The comment here says Poll is imported “for LocalSpawner impls”, but the LocalSpawner impl doesn’t use Poll; this block is only present to justify keeping an otherwise-unused import. That makes the rationale misleading and adds noise to the test harness—prefer removing the import and this helper entirely.
| // `Poll` is imported above for `LocalSpawner` impls; flag it as | |
| // in-use so a `cargo clippy --tests -D warnings` build doesn't | |
| // trip on the otherwise-unused import. (`Poll` is brought in | |
| // because it's the canonical paired import alongside `Pin` for | |
| // hand-rolled futures, even though `LoopbackTestChannels`' | |
| // generated code uses the higher-level macro shape.) | |
| #[allow(dead_code)] | |
| fn _poll_use(p: Poll<()>) -> Poll<()> { | |
| p | |
| } |
Lifts the parent crate's
tests/bare_metal_e2e.rsharness onto the two-stackLoopbackDriverpair from 19e, using 19f's relaxedServerbounds (SocketHandleabstraction) so anArc<EmbassyNetSocket>—!Syncbecause embassy-net'sUdpSocket<'static>borrows fromStack'sRefCell<Inner<D>>— satisfiesHfor the Server.Two new tests in
simple-someip-embassy-net/tests/loopback.rs:client_receives_server_sd_announcement: real Server on stack A emits SDOfferServiceviaannouncement_loop_local(19f's!Sendvariant), real Client on stack B receives it viabind_discovery. Asserts aClientUpdate::DiscoveryUpdatedsurfaces within 5 s. Both stacks join 224.0.23.0 at the smoltcp level before construction (the adapter'sjoin_multicast_v4is a documented no-op since multicast group membership lives on theStack, not theUdpSocket).client_send_request_server_runloop_stable: passive Server on stack A; Client on stack B doesadd_endpoint+send_to_serviceto push a SOME/IP request through the loopback. Asserts the send returns Ok and the run-loop survives. No response assertion becausesimple_someip::Serverexposes no public request-handler API — matching the parent crate's reference test.Harness additions:
define_static_channels!LoopbackTestChannelswith the same pool sizing shape astests/bare_metal_e2e.rs'sE2ETestChannels.LocalTokioSpawner: LocalSpawnerovertokio::task::spawn_local.LocalSpawner(notSpawner) because the Client's run-future captures&self.unicast_socket-style borrows across awaits over a!Syncsocket, making the run future itself!Send.LocalTimer: Timerovertokio::time::sleep, boxed-future shape matching the bare-metal-e2eMockTimer.MockSubscriptions(Arc<Mutex<Vec<SubKey>>>)— same shape as bare-metal-e2e's mock.Cargo.toml: dev-dependency
simple-someipis now re-pinned withfeatures = ["client", "server", "bare_metal", "std"]. Thestdaddition gatesRawPayload/VecSdHeader/Arc<Mutex<E2ERegistry>>default impls — all needed for the host-side test harness — without affecting the production[dependencies]build (which staysdefault-features = false).Type-inference notes:
Server<_, _, _, _, Arc<simple_someip_embassy_net::EmbassyNetSocket>>annotation. Without it the compiler can't decideHacross theServerDepsindirection — same situation assimple_someip's own SD-NACK test (mod.rs:1345) addresses.embassy_net::Stack::join_multicast_grouptakesT: Into<IpAddress>but embassy-net 0.4 has nocore::net::Ipv4Addr -> IpAddressblanket impl. Constructed viaembassy_net::Ipv4Address(addr.octets())per the smoltcp shape.Gates green:
Phase 19 status: 19a-g all complete. Remaining 19 sub-phases:
examples/embassy_net_client/in-tree binary example.simple-someip-embassy-net0.1.0 release.