input_pipeline/
display_ownership.rs

1// Copyright 2022 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
5use crate::input_device::{self, InputEvent};
6use crate::input_handler::InputHandlerStatus;
7use crate::keyboard_binding::{KeyboardDeviceDescriptor, KeyboardEvent};
8use anyhow::{Context, Result};
9use fidl_fuchsia_ui_composition_internal as fcomp;
10use fidl_fuchsia_ui_input3::KeyEventType;
11use fuchsia_async::{OnSignals, Task};
12use fuchsia_inspect::health::Reporter;
13use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
14use futures::{StreamExt, select};
15use keymaps::KeyState;
16use std::cell::RefCell;
17use std::rc::Rc;
18use std::sync::LazyLock;
19use zx::{AsHandleRef, MonotonicDuration, MonotonicInstant, Signals, Status};
20
21// The signal value corresponding to the `DISPLAY_OWNED_SIGNAL`.  Same as zircon's signal
22// USER_0.
23static DISPLAY_OWNED: LazyLock<Signals> = LazyLock::new(|| {
24    Signals::from_bits(fcomp::SIGNAL_DISPLAY_OWNED).expect("static init should not fail")
25});
26
27// The signal value corresponding to the `DISPLAY_NOT_OWNED_SIGNAL`.  Same as zircon's signal
28// USER_1.
29static DISPLAY_UNOWNED: LazyLock<Signals> = LazyLock::new(|| {
30    Signals::from_bits(fcomp::SIGNAL_DISPLAY_NOT_OWNED).expect("static init should not fail")
31});
32
33// Any display-related signal.
34static ANY_DISPLAY_EVENT: LazyLock<Signals> = LazyLock::new(|| *DISPLAY_OWNED | *DISPLAY_UNOWNED);
35
36// Stores the last received ownership signals.
37#[derive(Debug, Clone, PartialEq)]
38struct Ownership {
39    signals: Signals,
40}
41
42impl std::convert::From<Signals> for Ownership {
43    fn from(signals: Signals) -> Self {
44        Ownership { signals }
45    }
46}
47
48impl Ownership {
49    // Returns true if the display is currently indicated to be not owned by
50    // Scenic.
51    fn is_display_ownership_lost(&self) -> bool {
52        self.signals.contains(*DISPLAY_UNOWNED)
53    }
54
55    // Returns the mask of the next signal to watch.
56    //
57    // Since the ownership alternates, so does the next signal to wait on.
58    fn next_signal(&self) -> Signals {
59        match self.is_display_ownership_lost() {
60            true => *DISPLAY_OWNED,
61            false => *DISPLAY_UNOWNED,
62        }
63    }
64
65    /// Waits for the next signal change.
66    ///
67    /// If the display is owned, it will wait for display to become unowned.
68    /// If the display is unowned, it will wait for the display to become owned.
69    async fn wait_ownership_change<'a, T: AsHandleRef>(
70        &self,
71        event: &'a T,
72    ) -> Result<Signals, Status> {
73        OnSignals::new(event, self.next_signal()).await
74    }
75}
76
77/// A handler that turns the input pipeline off or on based on whether
78/// the Scenic owns the display.
79///
80/// This allows us to turn off keyboard processing when the user switches away
81/// from the product (e.g. terminal) into virtual console.
82///
83/// See the `README.md` file in this crate for details.
84pub struct DisplayOwnership {
85    /// The current view of the display ownership.  It is mutated by the
86    /// display ownership task when appropriate signals arrive.
87    ownership: Rc<RefCell<Ownership>>,
88
89    /// The registry of currently pressed keys.
90    key_state: RefCell<KeyState>,
91
92    /// The source of ownership change events for the main loop.
93    display_ownership_change_receiver: RefCell<UnboundedReceiver<Ownership>>,
94
95    /// A background task that watches for display ownership changes.  We keep
96    /// it alive to ensure that it keeps running.
97    _display_ownership_task: Task<()>,
98
99    /// The inventory of this handler's Inspect status.
100    inspect_status: InputHandlerStatus,
101
102    /// The event processing loop will do an `unbounded_send(())` on this
103    /// channel once at the end of each loop pass, in test configurations only.
104    /// The test fixture uses this channel to execute test fixture in
105    /// lock-step with the event processing loop for test cases where the
106    /// precise event sequencing is relevant.
107    #[cfg(test)]
108    loop_done: RefCell<Option<UnboundedSender<()>>>,
109}
110
111impl DisplayOwnership {
112    /// Creates a new handler that watches `display_ownership_event` for events.
113    ///
114    /// The `display_ownership_event` is assumed to be an [Event] obtained from
115    /// `fuchsia.ui.composition.internal.DisplayOwnership/GetEvent`.  There
116    /// isn't really a way for this code to know here whether this is true or
117    /// not, so implementor beware.
118    pub fn new(
119        display_ownership_event: impl AsHandleRef + 'static,
120        input_handlers_node: &fuchsia_inspect::Node,
121    ) -> Rc<Self> {
122        DisplayOwnership::new_internal(display_ownership_event, None, input_handlers_node)
123    }
124
125    #[cfg(test)]
126    pub fn new_for_test(
127        display_ownership_event: impl AsHandleRef + 'static,
128        loop_done: UnboundedSender<()>,
129    ) -> Rc<Self> {
130        let inspector = fuchsia_inspect::Inspector::default();
131        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
132        DisplayOwnership::new_internal(
133            display_ownership_event,
134            Some(loop_done),
135            &fake_handlers_node,
136        )
137    }
138
139    fn new_internal(
140        display_ownership_event: impl AsHandleRef + 'static,
141        _loop_done: Option<UnboundedSender<()>>,
142        input_handlers_node: &fuchsia_inspect::Node,
143    ) -> Rc<Self> {
144        let initial_state = display_ownership_event
145            // scenic guarantees that ANY_DISPLAY_EVENT is asserted. If it is
146            // not, this will fail with a timeout error.
147            .wait_handle(*ANY_DISPLAY_EVENT, MonotonicInstant::INFINITE_PAST)
148            .expect("unable to set the initial display state");
149        log::debug!("setting initial display ownership to: {:?}", &initial_state);
150        let initial_ownership: Ownership = initial_state.into();
151        let ownership = Rc::new(RefCell::new(initial_ownership.clone()));
152
153        let mut ownership_clone = initial_ownership;
154        let (ownership_sender, ownership_receiver) = mpsc::unbounded();
155        let display_ownership_task = Task::local(async move {
156            loop {
157                let signals = ownership_clone.wait_ownership_change(&display_ownership_event).await;
158                match signals {
159                    Err(e) => {
160                        log::warn!("could not read display state: {:?}", e);
161                        break;
162                    }
163                    Ok(signals) => {
164                        log::debug!("setting display ownership to: {:?}", &signals);
165                        ownership_sender.unbounded_send(signals.into()).unwrap();
166                        ownership_clone = signals.into();
167                    }
168                }
169            }
170            log::warn!(
171                "display loop exiting and will no longer monitor display changes - this is not expected"
172            );
173        });
174        log::info!("Display ownership handler installed");
175        let inspect_status = InputHandlerStatus::new(
176            input_handlers_node,
177            "display_ownership",
178            /* generates_events */ false,
179        );
180        Rc::new(Self {
181            ownership,
182            key_state: RefCell::new(KeyState::new()),
183            display_ownership_change_receiver: RefCell::new(ownership_receiver),
184            _display_ownership_task: display_ownership_task,
185            inspect_status,
186            #[cfg(test)]
187            loop_done: RefCell::new(_loop_done),
188        })
189    }
190
191    /// Returns true if the display is currently *not* owned by Scenic.
192    fn is_display_ownership_lost(&self) -> bool {
193        self.ownership.borrow().is_display_ownership_lost()
194    }
195
196    /// Run this function in an executor to handle events.
197    pub async fn handle_input_events(
198        self: &Rc<Self>,
199        mut input: UnboundedReceiver<InputEvent>,
200        output: UnboundedSender<InputEvent>,
201    ) -> Result<()> {
202        loop {
203            let mut ownership_source = self.display_ownership_change_receiver.borrow_mut();
204            select! {
205                // Display ownership changed.
206                new_ownership = ownership_source.select_next_some() => {
207                    let is_display_ownership_lost = new_ownership.is_display_ownership_lost();
208                    // When the ownership is modified, float a set of cancel or sync
209                    // events to scoop up stale keyboard state, treating it the same
210                    // as loss of focus.
211                    let event_type = match is_display_ownership_lost {
212                        true => KeyEventType::Cancel,
213                        false => KeyEventType::Sync,
214                    };
215                    let keys = self.key_state.borrow().get_set();
216                    let mut event_time = MonotonicInstant::get();
217                    for key in keys.into_iter() {
218                        let key_event = KeyboardEvent::new(key, event_type);
219                        output.unbounded_send(into_input_event(key_event, event_time))
220                            .context("unable to send display updates")?;
221                        event_time = event_time + MonotonicDuration::from_nanos(1);
222                    }
223                    *(self.ownership.borrow_mut()) = new_ownership;
224                },
225
226                // An input event arrived.
227                event = input.select_next_some() => {
228                    if event.is_handled() {
229                        // Forward handled events unmodified.
230                        output.unbounded_send(event).context("unable to send handled event")?;
231                        continue;
232                    }
233                    self.inspect_status.count_received_event(input_device::InputEvent::from(event.clone()));
234                    match event.device_event {
235                        input_device::InputDeviceEvent::Keyboard(ref e) => {
236                            self.key_state.borrow_mut().update(e.get_event_type(), e.get_key());
237                        },
238                        _ => {},
239                    }
240                    let is_display_ownership_lost = self.is_display_ownership_lost();
241                    if is_display_ownership_lost {
242                        self.inspect_status.count_handled_event();
243                    }
244                    output.unbounded_send(
245                        input_device::InputEvent::from(event)
246                            .into_handled_if(is_display_ownership_lost)
247                    ).context("unable to send input event updates")?;
248                },
249            };
250            #[cfg(test)]
251            {
252                self.loop_done.borrow_mut().as_ref().unwrap().unbounded_send(()).unwrap();
253            }
254        }
255    }
256
257    pub fn set_handler_healthy(self: std::rc::Rc<Self>) {
258        self.inspect_status.health_node.borrow_mut().set_ok();
259    }
260
261    pub fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
262        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
263    }
264}
265
266fn empty_keyboard_device_descriptor() -> input_device::InputDeviceDescriptor {
267    input_device::InputDeviceDescriptor::Keyboard(
268        // Should descriptor be something sensible?
269        KeyboardDeviceDescriptor {
270            keys: vec![],
271            device_information: fidl_fuchsia_input_report::DeviceInformation {
272                vendor_id: Some(0),
273                product_id: Some(0),
274                version: Some(0),
275                polling_rate: Some(0),
276                ..Default::default()
277            },
278            device_id: 0,
279        },
280    )
281}
282
283fn into_input_event(
284    keyboard_event: KeyboardEvent,
285    event_time: MonotonicInstant,
286) -> input_device::InputEvent {
287    input_device::InputEvent {
288        device_event: input_device::InputDeviceEvent::Keyboard(keyboard_event),
289        device_descriptor: empty_keyboard_device_descriptor(),
290        event_time,
291        handled: input_device::Handled::No,
292        trace_id: None,
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299    use crate::testing_utilities::{create_fake_input_event, create_input_event};
300    use fidl_fuchsia_input::Key;
301    use fuchsia_async as fasync;
302    use pretty_assertions::assert_eq;
303    use zx::{EventPair, Peered};
304
305    // Manages losing and regaining display, since manual management is error-prone:
306    // if signal_peer does not change the signal state, the waiting process will block
307    // forever, which makes tests run longer than needed.
308    struct DisplayWrangler {
309        event: EventPair,
310        last: Signals,
311    }
312
313    impl DisplayWrangler {
314        fn new(event: EventPair) -> Self {
315            let mut instance = DisplayWrangler { event, last: *DISPLAY_OWNED };
316            // Signal needs to be initialized before the handlers attempts to read it.
317            // This is normally always the case in production.
318            // Else, the `new_for_test` below will panic with a TIMEOUT error.
319            instance.set_unowned();
320            instance
321        }
322
323        fn set_unowned(&mut self) {
324            assert!(self.last != *DISPLAY_UNOWNED, "display is already unowned");
325            self.event.signal_peer(*DISPLAY_OWNED, *DISPLAY_UNOWNED).unwrap();
326            self.last = *DISPLAY_UNOWNED;
327        }
328
329        fn set_owned(&mut self) {
330            assert!(self.last != *DISPLAY_OWNED, "display is already owned");
331            self.event.signal_peer(*DISPLAY_UNOWNED, *DISPLAY_OWNED).unwrap();
332            self.last = *DISPLAY_OWNED;
333        }
334    }
335
336    #[fuchsia::test]
337    async fn display_ownership_change() {
338        // handler_event is the event that the unit under test will examine for
339        // display ownership changes.  test_event is used to set the appropriate
340        // signals.
341        let (test_event, handler_event) = EventPair::create();
342
343        // test_sender is used to pipe input events into the handler.
344        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
345
346        // test_receiver is used to pipe input events out of the handler.
347        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
348
349        // The unit under test adds a () each time it completes one pass through
350        // its event loop.  Use to ensure synchronization.
351        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
352
353        // We use a wrapper to signal test_event correctly, since doing it wrong
354        // by hand causes tests to hang, which isn't the best dev experience.
355        let mut wrangler = DisplayWrangler::new(test_event);
356        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
357
358        let _task = fasync::Task::local(async move {
359            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
360        });
361
362        let fake_time = MonotonicInstant::from_nanos(42);
363
364        // Go two full circles of signaling.
365
366        // 1
367        wrangler.set_owned();
368        loop_done.next().await;
369        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
370        loop_done.next().await;
371
372        // 2
373        wrangler.set_unowned();
374        loop_done.next().await;
375        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
376        loop_done.next().await;
377
378        // 3
379        wrangler.set_owned();
380        loop_done.next().await;
381        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
382        loop_done.next().await;
383
384        // 4
385        wrangler.set_unowned();
386        loop_done.next().await;
387        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
388        loop_done.next().await;
389
390        let actual: Vec<InputEvent> =
391            test_receiver.take(4).map(|e| e.into_with_event_time(fake_time)).collect().await;
392
393        assert_eq!(
394            actual,
395            vec![
396                // Event received while we owned the display.
397                create_fake_input_event(fake_time),
398                // Event received when we lost the display.
399                create_fake_input_event(fake_time).into_handled(),
400                // Display ownership regained.
401                create_fake_input_event(fake_time),
402                // Display ownership lost.
403                create_fake_input_event(fake_time).into_handled(),
404            ]
405        );
406    }
407
408    fn new_keyboard_input_event(key: Key, event_type: KeyEventType) -> InputEvent {
409        let fake_time = MonotonicInstant::from_nanos(42);
410        create_input_event(
411            KeyboardEvent::new(key, event_type),
412            &input_device::InputDeviceDescriptor::Fake,
413            fake_time,
414            input_device::Handled::No,
415        )
416    }
417
418    #[fuchsia::test]
419    async fn basic_key_state_handling() {
420        let (test_event, handler_event) = EventPair::create();
421        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
422        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
423        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
424        let mut wrangler = DisplayWrangler::new(test_event);
425        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
426        let _task = fasync::Task::local(async move {
427            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
428        });
429
430        let fake_time = MonotonicInstant::from_nanos(42);
431
432        // Gain the display, and press a key.
433        wrangler.set_owned();
434        loop_done.next().await;
435        test_sender
436            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
437            .unwrap();
438        loop_done.next().await;
439
440        // Lose display.
441        wrangler.set_unowned();
442        loop_done.next().await;
443
444        // Regain display
445        wrangler.set_owned();
446        loop_done.next().await;
447
448        // Key event after regaining.
449        test_sender
450            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
451            .unwrap();
452        loop_done.next().await;
453
454        let actual: Vec<InputEvent> =
455            test_receiver.take(4).map(|e| e.into_with_event_time(fake_time)).collect().await;
456
457        assert_eq!(
458            actual,
459            vec![
460                new_keyboard_input_event(Key::A, KeyEventType::Pressed),
461                new_keyboard_input_event(Key::A, KeyEventType::Cancel)
462                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
463                new_keyboard_input_event(Key::A, KeyEventType::Sync)
464                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
465                new_keyboard_input_event(Key::A, KeyEventType::Released),
466            ]
467        );
468    }
469
470    #[fuchsia::test]
471    async fn more_key_state_handling() {
472        let (test_event, handler_event) = EventPair::create();
473        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
474        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
475        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
476        let mut wrangler = DisplayWrangler::new(test_event);
477        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
478        let _task = fasync::Task::local(async move {
479            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
480        });
481
482        let fake_time = MonotonicInstant::from_nanos(42);
483
484        wrangler.set_owned();
485        loop_done.next().await;
486        test_sender
487            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
488            .unwrap();
489        loop_done.next().await;
490        test_sender
491            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Pressed))
492            .unwrap();
493        loop_done.next().await;
494
495        // Lose display, release a key, press a key.
496        wrangler.set_unowned();
497        loop_done.next().await;
498        test_sender
499            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Released))
500            .unwrap();
501        loop_done.next().await;
502        test_sender
503            .unbounded_send(new_keyboard_input_event(Key::C, KeyEventType::Pressed))
504            .unwrap();
505        loop_done.next().await;
506
507        // Regain display
508        wrangler.set_owned();
509        loop_done.next().await;
510
511        // Key event after regaining.
512        test_sender
513            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
514            .unwrap();
515        loop_done.next().await;
516        test_sender
517            .unbounded_send(new_keyboard_input_event(Key::C, KeyEventType::Released))
518            .unwrap();
519        loop_done.next().await;
520
521        let actual: Vec<InputEvent> =
522            test_receiver.take(10).map(|e| e.into_with_event_time(fake_time)).collect().await;
523
524        assert_eq!(
525            actual,
526            vec![
527                new_keyboard_input_event(Key::A, KeyEventType::Pressed),
528                new_keyboard_input_event(Key::B, KeyEventType::Pressed),
529                new_keyboard_input_event(Key::A, KeyEventType::Cancel)
530                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
531                new_keyboard_input_event(Key::B, KeyEventType::Cancel)
532                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
533                new_keyboard_input_event(Key::B, KeyEventType::Released).into_handled(),
534                new_keyboard_input_event(Key::C, KeyEventType::Pressed).into_handled(),
535                // The CANCEL and SYNC events are emitted in the sort ordering of the
536                // `Key` enum values. Perhaps they should be emitted instead in the order
537                // they have been received for SYNC, and in reverse order for CANCEL.
538                new_keyboard_input_event(Key::A, KeyEventType::Sync)
539                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
540                new_keyboard_input_event(Key::C, KeyEventType::Sync)
541                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
542                new_keyboard_input_event(Key::A, KeyEventType::Released),
543                new_keyboard_input_event(Key::C, KeyEventType::Released),
544            ]
545        );
546    }
547
548    #[fuchsia::test]
549    async fn display_ownership_initialized_with_inspect_node() {
550        let (test_event, handler_event) = EventPair::create();
551        let (loop_done_sender, _) = mpsc::unbounded::<()>();
552        let inspector = fuchsia_inspect::Inspector::default();
553        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
554        // Signal needs to be initialized first so DisplayOwnership::new doesn't panic with a TIMEOUT error
555        let _ = DisplayWrangler::new(test_event);
556        let _handler = DisplayOwnership::new_internal(
557            handler_event,
558            Some(loop_done_sender),
559            &fake_handlers_node,
560        );
561        diagnostics_assertions::assert_data_tree!(inspector, root: {
562            input_handlers_node: {
563                display_ownership: {
564                    events_received_count: 0u64,
565                    events_handled_count: 0u64,
566                    last_received_timestamp_ns: 0u64,
567                    "fuchsia.inspect.Health": {
568                        status: "STARTING_UP",
569                        // Timestamp value is unpredictable and not relevant in this context,
570                        // so we only assert that the property is present.
571                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
572                    },
573                }
574            }
575        });
576    }
577
578    #[fuchsia::test]
579    async fn display_ownership_inspect_counts_events() {
580        let (test_event, handler_event) = EventPair::create();
581        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
582        let (handler_sender, _test_receiver) = mpsc::unbounded::<InputEvent>();
583        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
584        let mut wrangler = DisplayWrangler::new(test_event);
585        let inspector = fuchsia_inspect::Inspector::default();
586        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
587        let handler = DisplayOwnership::new_internal(
588            handler_event,
589            Some(loop_done_sender),
590            &fake_handlers_node,
591        );
592        let _task = fasync::Task::local(async move {
593            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
594        });
595
596        // Gain the display, and press a key.
597        wrangler.set_owned();
598        loop_done.next().await;
599        test_sender
600            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
601            .unwrap();
602        loop_done.next().await;
603
604        // Lose display
605        // Input event is marked `Handled` if received after display ownership is lost
606        wrangler.set_unowned();
607        loop_done.next().await;
608        test_sender
609            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Pressed))
610            .unwrap();
611        loop_done.next().await;
612
613        // Regain display
614        wrangler.set_owned();
615        loop_done.next().await;
616
617        // Key event after regaining.
618        test_sender
619            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
620            .unwrap();
621        loop_done.next().await;
622
623        diagnostics_assertions::assert_data_tree!(inspector, root: {
624            input_handlers_node: {
625                display_ownership: {
626                    events_received_count: 3u64,
627                    events_handled_count: 1u64,
628                    last_received_timestamp_ns: 42u64,
629                    "fuchsia.inspect.Health": {
630                        status: "STARTING_UP",
631                        // Timestamp value is unpredictable and not relevant in this context,
632                        // so we only assert that the property is present.
633                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
634                    },
635                }
636            }
637        });
638    }
639}