1#![warn(missing_docs)]
6
7pub mod constants;
10pub mod devices;
11pub mod dhcpv4;
12pub mod interfaces;
13pub mod ndp;
14pub mod nud;
15pub mod packets;
16pub mod pcap;
17pub mod ping;
18#[macro_use]
19pub mod realms;
20
21use anyhow::Context as _;
22use component_events::events::EventStream;
23use fidl_fuchsia_netemul as fnetemul;
24use fuchsia_async::{self as fasync, DurationExt as _};
25use futures::future::FutureExt as _;
26use futures::stream::{Stream, StreamExt as _, TryStreamExt as _};
27use futures::{Future, select};
28use std::pin::pin;
29
30use crate::realms::TestSandboxExt as _;
31
32pub type Result<T = ()> = std::result::Result<T, anyhow::Error>;
34
35pub const ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT: zx::MonotonicDuration =
39 zx::MonotonicDuration::from_seconds(120);
40
41pub const ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT: zx::MonotonicDuration =
47 zx::MonotonicDuration::from_seconds(5);
48
49pub const ASYNC_EVENT_CHECK_INTERVAL: zx::MonotonicDuration =
51 zx::MonotonicDuration::from_seconds(1);
52
53pub async fn try_any<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
57 let stream = pin!(stream);
58 stream.try_filter(|v| futures::future::ready(*v)).next().await.unwrap_or(Ok(false))
59}
60
61pub async fn try_all<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
65 let stream = pin!(stream);
66 stream.try_filter(|v| futures::future::ready(!*v)).next().await.unwrap_or(Ok(true))
67}
68
69pub async fn sleep(secs: i64) {
71 fasync::Timer::new(zx::MonotonicDuration::from_seconds(secs).after_now()).await;
72}
73
74pub async fn get_component_stopped_event_stream() -> Result<component_events::events::EventStream> {
76 EventStream::open_at_path("/events/stopped")
77 .await
78 .context("failed to subscribe to `Stopped` events")
79}
80
81pub async fn wait_for_component_stopped_with_stream(
86 event_stream: &mut component_events::events::EventStream,
87 realm: &netemul::TestRealm<'_>,
88 component_moniker: &str,
89 status_matcher: Option<component_events::matcher::ExitStatusMatcher>,
90) -> Result<component_events::events::Stopped> {
91 let matcher = get_child_component_event_matcher(realm, component_moniker)
92 .await
93 .context("get child component matcher")?;
94 matcher.stop(status_matcher).wait::<component_events::events::Stopped>(event_stream).await
95}
96
97pub async fn wait_for_component_stopped(
103 realm: &netemul::TestRealm<'_>,
104 component_moniker: &str,
105 status_matcher: Option<component_events::matcher::ExitStatusMatcher>,
106) -> Result<component_events::events::Stopped> {
107 let mut stream = get_component_stopped_event_stream().await?;
108 wait_for_component_stopped_with_stream(&mut stream, realm, component_moniker, status_matcher)
109 .await
110}
111
112pub async fn get_child_component_event_matcher(
114 realm: &netemul::TestRealm<'_>,
115 component_moniker: &str,
116) -> Result<component_events::matcher::EventMatcher> {
117 let realm_moniker = &realm.get_moniker().await.context("calling get moniker")?;
118 let moniker_for_match =
119 format!("./{}/{}/{}", NETEMUL_SANDBOX_MONIKER, realm_moniker, component_moniker);
120 Ok(component_events::matcher::EventMatcher::ok().moniker(moniker_for_match))
121}
122
123const NETEMUL_SANDBOX_MONIKER: &str = "sandbox";
126
127pub async fn get_component_moniker<'a>(
130 realm: &netemul::TestRealm<'a>,
131 component: &str,
132) -> Result<String> {
133 let realm_moniker = realm.get_moniker().await.context("calling get moniker")?;
134 Ok([NETEMUL_SANDBOX_MONIKER, &realm_moniker, component].join("/"))
135}
136
137pub async fn get_inspect_data(
141 realm: &netemul::TestRealm<'_>,
142 component_moniker: impl Into<String>,
143 tree_selector: impl Into<String>,
144) -> Result<diagnostics_hierarchy::DiagnosticsHierarchy> {
145 let moniker = realm.get_moniker().await.context("calling get moniker")?;
146 let realm_moniker = selectors::sanitize_string_for_selectors(&moniker);
147 let mut data = diagnostics_reader::ArchiveReader::inspect()
148 .retry(diagnostics_reader::RetryConfig::MinSchemaCount(1))
149 .add_selector(
150 diagnostics_reader::ComponentSelector::new(vec![
151 NETEMUL_SANDBOX_MONIKER.into(),
152 realm_moniker.into_owned(),
153 component_moniker.into(),
154 ])
155 .with_tree_selector(tree_selector.into()),
156 )
157 .snapshot()
158 .await
159 .context("snapshot did not return any inspect data")?
160 .into_iter()
161 .map(|inspect_data| {
162 inspect_data.payload.ok_or_else(|| {
163 anyhow::anyhow!(
164 "empty inspect payload, metadata errors: {:?}",
165 inspect_data.metadata.errors
166 )
167 })
168 });
169
170 let Some(datum) = data.next() else {
171 unreachable!("archive reader RetryConfig specifies non-empty")
172 };
173
174 let data: Vec<_> = data.collect();
175 assert!(
176 data.is_empty(),
177 "expected a single inspect entry; got {:?} and also {:?}",
178 datum,
179 data
180 );
181
182 datum
183}
184
185pub async fn get_inspect_property(
188 realm: &netemul::TestRealm<'_>,
189 component_moniker: impl Into<String>,
190 property_selector: impl Into<String>,
191) -> Result<diagnostics_hierarchy::Property> {
192 let property_selector = property_selector.into();
193 let hierarchy = get_inspect_data(&realm, component_moniker, property_selector.clone())
194 .await
195 .context("getting hierarchy")?;
196 let property_selector = property_selector.split(&['/', ':']).skip(1).collect::<Vec<_>>();
197 let property = hierarchy
198 .get_property_by_path(&property_selector)
199 .ok_or_else(|| anyhow::anyhow!("property not found in hierarchy: {hierarchy:?}"))?;
200 Ok(property.clone())
201}
202
203pub async fn setup_network<'a, N: realms::Netstack>(
205 sandbox: &'a netemul::TestSandbox,
206 name: &'a str,
207 metric: Option<u32>,
208) -> Result<(
209 netemul::TestNetwork<'a>,
210 netemul::TestRealm<'a>,
211 netemul::TestInterface<'a>,
212 netemul::TestFakeEndpoint<'a>,
213)> {
214 setup_network_with::<N, _>(
215 sandbox,
216 name,
217 netemul::InterfaceConfig { metric, ..Default::default() },
218 std::iter::empty::<fnetemul::ChildDef>(),
219 )
220 .await
221}
222
223pub async fn setup_network_with<'a, N: realms::Netstack, I>(
230 sandbox: &'a netemul::TestSandbox,
231 name: &'a str,
232 interface_config: netemul::InterfaceConfig<'a>,
233 children: I,
234) -> Result<(
235 netemul::TestNetwork<'a>,
236 netemul::TestRealm<'a>,
237 netemul::TestInterface<'a>,
238 netemul::TestFakeEndpoint<'a>,
239)>
240where
241 I: IntoIterator,
242 I::Item: Into<fnetemul::ChildDef>,
243{
244 let network = sandbox.create_network(name).await.context("failed to create network")?;
245 let realm = sandbox
246 .create_netstack_realm_with::<N, _, _>(name, children)
247 .context("failed to create netstack realm")?;
248 let fake_ep = network.create_fake_endpoint()?;
251
252 let iface = realm
253 .join_network_with_if_config(&network, name, interface_config)
254 .await
255 .context("failed to configure networking")?;
256
257 Ok((network, realm, iface, fake_ep))
258}
259
260pub async fn pause_fake_clock(realm: &netemul::TestRealm<'_>) -> Result<()> {
262 let fake_clock_control = realm
263 .connect_to_protocol::<fidl_fuchsia_testing::FakeClockControlMarker>()
264 .context("failed to connect to FakeClockControl")?;
265 fake_clock_control.pause().await.context("failed to pause time")?;
266 Ok(())
267}
268
269#[track_caller]
272pub fn annotate<'a, 'b: 'a, T>(
273 fut: impl Future<Output = T> + 'a,
274 interval: std::time::Duration,
275 event_name: &'b str,
276) -> impl Future<Output = T> + 'a {
277 let caller = std::panic::Location::caller();
278
279 async move {
280 let mut fut = pin!(fut.fuse());
281 let event_name = event_name.to_string();
282 let mut print_fut = pin!(
283 futures::stream::repeat(())
284 .for_each(|()| async {
285 fasync::Timer::new(interval).await;
286 eprintln!("waiting for {} at {}", event_name, caller);
287 })
288 .fuse()
289 );
290 let result = select! {
291 result = fut => result,
292 () = print_fut => unreachable!("should repeat printing forever"),
293 };
294 eprintln!("completed {} at {}", event_name, caller);
295 result
296 }
297}