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: fidl::endpoints::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/// Create a `ViewProxy` whose server end will respond to the first
61/// `OpenEntryIterator` request it receives by returning an entry iterator
62/// backed by the given event stream. The returned future drives the entry
63/// iterator implementation.
64pub fn create_fake_view(
65    event_stream: impl Stream<Item = Vec<fnet_neighbor::EntryIteratorItem>>,
66) -> (fnet_neighbor::ViewProxy, impl FusedFuture<Output = ()>) {
67    let (view, view_server_end) = fidl::endpoints::create_proxy::<fnet_neighbor::ViewMarker>();
68
69    let entry_iter_fut = view_server_end
70        .into_stream()
71        .into_future()
72        .then(async |(req, _rest)| {
73            serve_view_request(
74                req.expect("View request stream unexpectedly ended")
75                    .expect("failed to receive `OpenEntryIterator` request"),
76                event_stream,
77            )
78            .await
79        })
80        .fuse();
81
82    (view, entry_iter_fut)
83}
84
85/// Specification for a generated neighbor event.
86#[derive(Clone, Debug, PartialEq)]
87pub enum EventSpec {
88    /// A neighbor unique to the given seed existed prior to watching.
89    Existing(u8),
90    /// A neighbor unique to the given seed was added.
91    Added(u8),
92    /// A neighbor unique to the given seed was changed.
93    Changed(u8),
94    /// A neighbor unique to the given seed was removed.
95    Removed(u8),
96    /// An idle event.
97    Idle,
98}
99
100// Generates a neighbor entry whose IP address is unique to the given `seed`.
101fn generate_entry(seed: u8) -> fnet_neighbor::Entry {
102    fnet_neighbor::Entry {
103        interface: Some(1),
104        neighbor: Some(fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [192, 168, 0, seed] })),
105        state: Some(fnet_neighbor::EntryState::Reachable),
106        mac: Some(fnet::MacAddress { octets: [0, 1, 2, 3, 4, 5] }),
107        updated_at: Some(12345),
108        ..Default::default()
109    }
110}
111
112/// Generates a neighbor entry iterator item from the provided spec.
113pub fn generate_event_from_spec(spec: &EventSpec) -> fnet_neighbor::EntryIteratorItem {
114    match *spec {
115        EventSpec::Existing(seed) => {
116            fnet_neighbor::EntryIteratorItem::Existing(generate_entry(seed))
117        }
118        EventSpec::Added(seed) => fnet_neighbor::EntryIteratorItem::Added(generate_entry(seed)),
119        EventSpec::Changed(seed) => fnet_neighbor::EntryIteratorItem::Changed(generate_entry(seed)),
120        EventSpec::Removed(seed) => fnet_neighbor::EntryIteratorItem::Removed(generate_entry(seed)),
121        EventSpec::Idle => fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent),
122    }
123}
124
125/// Generates a list of neighbor entry iterator items from the provided spec.
126pub fn generate_events_from_spec(spec: &[EventSpec]) -> Vec<fnet_neighbor::EntryIteratorItem> {
127    spec.iter().map(generate_event_from_spec).collect()
128}
129
130/// Generates an arbitrary but valid neighbor entry iterator item that is unique
131/// to the given `seed`.
132pub fn generate_event(seed: u8) -> fnet_neighbor::EntryIteratorItem {
133    generate_event_from_spec(&EventSpec::Added(seed))
134}
135
136/// Generates a list of arbitrary but valid neighbor entry iterator items, one
137/// for each value in the provided range of `seeds`.
138pub fn generate_events_in_range(
139    seeds: std::ops::Range<u8>,
140) -> Vec<fnet_neighbor::EntryIteratorItem> {
141    seeds.into_iter().map(generate_event).collect()
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    use assert_matches::assert_matches;
149    use futures::FutureExt;
150    use test_case::test_case;
151
152    #[test_case(Vec::new(); "no events")]
153    #[test_case(vec![0..1]; "single_batch_single_event")]
154    #[test_case(vec![0..10]; "single_batch_many_events")]
155    #[test_case(vec![0..10, 10..20, 20..30]; "many_batches_many_events")]
156    #[fuchsia_async::run_singlethreaded(test)]
157    async fn fake_view_impl_against_shape(test_shape: Vec<std::ops::Range<u8>>) {
158        // Build the event stream based on the `test_shape`. Use a channel so
159        // that the stream stays open until `close_channel` is called later.
160        let (event_stream_sender, event_stream_receiver) =
161            futures::channel::mpsc::unbounded::<Vec<fnet_neighbor::EntryIteratorItem>>();
162        for batch_shape in &test_shape {
163            event_stream_sender
164                .unbounded_send(generate_events_in_range(batch_shape.clone()))
165                .expect("failed to send event batch");
166        }
167
168        // Instantiate the fake View implementation.
169        let (view, entry_iter_fut) = create_fake_view(event_stream_receiver);
170        futures::pin_mut!(entry_iter_fut);
171
172        // Drive the event iterator, asserting it observes the expected data.
173        let (entry_iter, entry_iter_server_end) =
174            fidl::endpoints::create_proxy::<fnet_neighbor::EntryIteratorMarker>();
175        view.open_entry_iterator(entry_iter_server_end, &Default::default())
176            .expect("failed to open entry iterator");
177        for batch_shape in test_shape {
178            futures::select!(
179                 () = entry_iter_fut => panic!("fake view implementation unexpectedly finished"),
180                events = entry_iter.get_next().fuse() => assert_eq!(
181                    events.expect("failed to watch for events"),
182                    generate_events_in_range(batch_shape.clone())));
183        }
184
185        // Close the event_stream_sender and observe the event iterator finish.
186        event_stream_sender.close_channel();
187        entry_iter_fut.await;
188
189        // Trying to watch again after we've exhausted the data should
190        // result in `PEER_CLOSED`.
191        assert_matches!(
192            entry_iter.get_next().await,
193            Err(fidl::Error::ClientChannelClosed { status: zx_status::Status::PEER_CLOSED, .. })
194        );
195    }
196
197    #[test]
198    fn generate_entry_unique_to_seed() {
199        assert_eq!(generate_entry(1), generate_entry(1));
200        assert_ne!(generate_entry(1), generate_entry(2));
201    }
202
203    #[test]
204    fn generate_multiple_events_from_spec() {
205        use EventSpec::*;
206        assert_eq!(
207            generate_events_from_spec(&[Existing(1), Added(2), Changed(3), Removed(4), Idle]),
208            &[
209                fnet_neighbor::EntryIteratorItem::Existing(generate_entry(1)),
210                fnet_neighbor::EntryIteratorItem::Added(generate_entry(2)),
211                fnet_neighbor::EntryIteratorItem::Changed(generate_entry(3)),
212                fnet_neighbor::EntryIteratorItem::Removed(generate_entry(4)),
213                fnet_neighbor::EntryIteratorItem::Idle(fnet_neighbor::IdleEvent),
214            ]
215        );
216    }
217}