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