Skip to main content

fidl_fuchsia_net_neighbor_ext/
testutil.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Test utilities for the fuchsia.net.neighbors FIDL library.
6//!
7//! This library defines a mix of internal and external test utilities,
8//! supporting tests of this `fidl_fuchsia_net_neighbor_ext` crate and tests
9//! of clients of the `fuchsia.net.neighbors` FIDL library, respectively.
10
11use flex_fuchsia_net as fnet;
12use flex_fuchsia_net_neighbor as fnet_neighbor;
13use futures::future::FusedFuture;
14use futures::{FutureExt, Stream, StreamExt as _};
15
16/// Responds to the given `GetNext` request with the given batch of events.
17fn handle_get_next(
18    request: fnet_neighbor::EntryIteratorRequest,
19    event_batch: Vec<fnet_neighbor::EntryIteratorItem>,
20) {
21    match request {
22        fnet_neighbor::EntryIteratorRequest::GetNext { responder } => {
23            responder.send(&event_batch).expect("failed to respond to `GetNext`")
24        }
25    }
26}
27
28/// A fake implementation of the `EntryIterator` protocol.
29///
30/// Feeds events received in `events` as responses to `GetNext()`.
31async fn fake_entry_iterator_impl(
32    events: impl Stream<Item = Vec<fnet_neighbor::EntryIteratorItem>>,
33    server_end: flex_client::fidl::ServerEnd<fnet_neighbor::EntryIteratorMarker>,
34) {
35    let request_stream = server_end.into_stream();
36    request_stream
37        .zip(events)
38        .for_each(|(request, event_batch)| {
39            handle_get_next(request.expect("failed to receive `GetNext` request"), event_batch);
40            futures::future::ready(())
41        })
42        .await
43}
44
45/// Serve an `OpenEntryIterator` request to the `View` protocol by instantiating an
46/// entry iterator client backed by the given event stream. The returned future
47/// drives the entry iterator implementation.
48pub async fn serve_view_request(
49    request: fnet_neighbor::ViewRequest,
50    event_stream: impl Stream<Item = Vec<fnet_neighbor::EntryIteratorItem>>,
51) {
52    match request {
53        fnet_neighbor::ViewRequest::OpenEntryIterator { it, .. } => {
54            fake_entry_iterator_impl(event_stream, it)
55        }
56    }
57    .await
58}
59
60/// Serve the first `OpenEntryIterator` request received on `server_end` by
61/// returning an entry iterator backed by the given event stream. The returned
62/// future drives the entry iterator implementation.
63pub fn serve_view_impl(
64    event_stream: impl Stream<Item = Vec<fnet_neighbor::EntryIteratorItem>>,
65    server_end: flex_client::fidl::ServerEnd<fnet_neighbor::ViewMarker>,
66) -> impl FusedFuture<Output = ()> {
67    server_end
68        .into_stream()
69        .into_future()
70        .then(async |(req, _rest)| {
71            serve_view_request(
72                req.expect("View request stream unexpectedly ended")
73                    .expect("failed to receive `OpenEntryIterator` request"),
74                event_stream,
75            )
76            .await
77        })
78        .fuse()
79}
80
81/// Create a `ViewProxy` whose server end will respond to the first
82/// `OpenEntryIterator` request it receives by returning an entry iterator
83/// backed by the given event stream. The returned future drives the entry
84/// iterator implementation.
85#[cfg(not(feature = "fdomain"))]
86pub fn create_fake_view(
87    event_stream: impl Stream<Item = Vec<fnet_neighbor::EntryIteratorItem>>,
88) -> (fnet_neighbor::ViewProxy, impl FusedFuture<Output = ()>) {
89    let (view, view_server_end) = fidl::endpoints::create_proxy::<fnet_neighbor::ViewMarker>();
90    let entry_iter_fut = serve_view_impl(event_stream, view_server_end);
91    (view, entry_iter_fut)
92}
93
94/// Specification for a generated neighbor event.
95#[derive(Clone, Debug, PartialEq)]
96pub enum EventSpec {
97    /// A neighbor unique to the given seed existed prior to watching.
98    Existing(u8),
99    /// A neighbor unique to the given seed was added.
100    Added(u8),
101    /// A neighbor unique to the given seed was changed.
102    Changed(u8),
103    /// A neighbor unique to the given seed was removed.
104    Removed(u8),
105    /// An idle event.
106    Idle,
107}
108
109// Generates a neighbor entry whose IP address is unique to the given `seed`.
110fn generate_entry(seed: u8) -> fnet_neighbor::Entry {
111    fnet_neighbor::Entry {
112        interface: Some(1),
113        neighbor: Some(fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [192, 168, 0, seed] })),
114        state: Some(fnet_neighbor::EntryState::Reachable),
115        mac: Some(fnet::MacAddress { octets: [0, 1, 2, 3, 4, 5] }),
116        updated_at: Some(12345),
117        ..Default::default()
118    }
119}
120
121/// Generates a neighbor entry iterator item from the provided spec.
122pub fn generate_event_from_spec(spec: &EventSpec) -> fnet_neighbor::EntryIteratorItem {
123    match *spec {
124        EventSpec::Existing(seed) => {
125            fnet_neighbor::EntryIteratorItem::Existing(generate_entry(seed))
126        }
127        EventSpec::Added(seed) => fnet_neighbor::EntryIteratorItem::Added(generate_entry(seed)),
128        EventSpec::Changed(seed) => fnet_neighbor::EntryIteratorItem::Changed(generate_entry(seed)),
129        EventSpec::Removed(seed) => fnet_neighbor::EntryIteratorItem::Removed(generate_entry(seed)),
130        EventSpec::Idle => fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent),
131    }
132}
133
134/// Generates a list of neighbor entry iterator items from the provided spec.
135pub fn generate_events_from_spec(spec: &[EventSpec]) -> Vec<fnet_neighbor::EntryIteratorItem> {
136    spec.iter().map(generate_event_from_spec).collect()
137}
138
139/// Generates an arbitrary but valid neighbor entry iterator item that is unique
140/// to the given `seed`.
141pub fn generate_event(seed: u8) -> fnet_neighbor::EntryIteratorItem {
142    generate_event_from_spec(&EventSpec::Added(seed))
143}
144
145/// Generates a list of arbitrary but valid neighbor entry iterator items, one
146/// for each value in the provided range of `seeds`.
147pub fn generate_events_in_range(
148    seeds: std::ops::Range<u8>,
149) -> Vec<fnet_neighbor::EntryIteratorItem> {
150    seeds.into_iter().map(generate_event).collect()
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    use assert_matches::assert_matches;
158    use futures::FutureExt;
159    use test_case::test_case;
160
161    #[test_case(Vec::new(); "no events")]
162    #[test_case(vec![0..1]; "single_batch_single_event")]
163    #[test_case(vec![0..10]; "single_batch_many_events")]
164    #[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
165    #[fuchsia::test]
166    async fn fake_view_impl_against_shape(test_shape: Vec<std::ops::Range<u8>>) {
167        // Build the event stream based on the `test_shape`. Use a channel so
168        // that the stream stays open until `close_channel` is called later.
169        let (event_stream_sender, event_stream_receiver) =
170            futures::channel::mpsc::unbounded::<Vec<fnet_neighbor::EntryIteratorItem>>();
171        for batch_shape in &test_shape {
172            event_stream_sender
173                .unbounded_send(generate_events_in_range(batch_shape.clone()))
174                .expect("failed to send event batch");
175        }
176
177        // Instantiate the fake View implementation.
178        #[cfg(feature = "fdomain")]
179        let client = fdomain_local::local_client_empty();
180        #[cfg(not(feature = "fdomain"))]
181        let client = fidl::endpoints::ZirconClient;
182        let (view, view_server_end) = client.create_proxy::<fnet_neighbor::ViewMarker>();
183        let entry_iter_fut = serve_view_impl(event_stream_receiver, view_server_end);
184        futures::pin_mut!(entry_iter_fut);
185
186        // Drive the event iterator, asserting it observes the expected data.
187        use flex_client::ProxyHasDomain as _;
188        let (entry_iter, entry_iter_server_end) =
189            view.domain().create_proxy::<fnet_neighbor::EntryIteratorMarker>();
190        view.open_entry_iterator(entry_iter_server_end, &Default::default())
191            .expect("failed to open entry iterator");
192        for batch_shape in test_shape {
193            futures::select!(
194                 () = entry_iter_fut => panic!("fake view implementation unexpectedly finished"),
195                events = entry_iter.get_next().fuse() => assert_eq!(
196                    events.expect("failed to watch for events"),
197                    generate_events_in_range(batch_shape.clone())));
198        }
199
200        // Close the event_stream_sender and observe the event iterator finish.
201        event_stream_sender.close_channel();
202        entry_iter_fut.await;
203
204        // Trying to watch again after we've exhausted the data should
205        // result in `PEER_CLOSED`.
206        assert_matches!(
207            entry_iter.get_next().await,
208            Err(fidl::Error::ClientChannelClosed { epitaph: fidl::Epitaph::PeerClosed, .. })
209        );
210    }
211
212    #[test]
213    fn generate_entry_unique_to_seed() {
214        assert_eq!(generate_entry(1), generate_entry(1));
215        assert_ne!(generate_entry(1), generate_entry(2));
216    }
217
218    #[test]
219    fn generate_multiple_events_from_spec() {
220        use EventSpec::*;
221        assert_eq!(
222            generate_events_from_spec(&[Existing(1), Added(2), Changed(3), Removed(4), Idle]),
223            &[
224                fnet_neighbor::EntryIteratorItem::Existing(generate_entry(1)),
225                fnet_neighbor::EntryIteratorItem::Added(generate_entry(2)),
226                fnet_neighbor::EntryIteratorItem::Changed(generate_entry(3)),
227                fnet_neighbor::EntryIteratorItem::Removed(generate_entry(4)),
228                fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent),
229            ]
230        );
231    }
232}