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                    fuchsia_trace::duration!(c"input", c"display_ownership");
229                    if event.is_handled() {
230                        // Forward handled events unmodified.
231                        output.unbounded_send(event).context("unable to send handled event")?;
232                        continue;
233                    }
234                    self.inspect_status.count_received_event(&event.event_time);
235                    match event.device_event {
236                        input_device::InputDeviceEvent::Keyboard(ref e) => {
237                            self.key_state.borrow_mut().update(e.get_event_type(), e.get_key());
238                        },
239                        _ => {},
240                    }
241                    let is_display_ownership_lost = self.is_display_ownership_lost();
242                    if is_display_ownership_lost {
243                        self.inspect_status.count_handled_event();
244                    }
245                    output.unbounded_send(
246                        input_device::InputEvent::from(event)
247                            .into_handled_if(is_display_ownership_lost)
248                    ).context("unable to send input event updates")?;
249                },
250            };
251            #[cfg(test)]
252            {
253                self.loop_done.borrow_mut().as_ref().unwrap().unbounded_send(()).unwrap();
254            }
255        }
256    }
257
258    pub fn set_handler_healthy(self: std::rc::Rc<Self>) {
259        self.inspect_status.health_node.borrow_mut().set_ok();
260    }
261
262    pub fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
263        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
264    }
265}
266
267fn empty_keyboard_device_descriptor() -> input_device::InputDeviceDescriptor {
268    input_device::InputDeviceDescriptor::Keyboard(
269        // Should descriptor be something sensible?
270        KeyboardDeviceDescriptor {
271            keys: vec![],
272            device_information: fidl_fuchsia_input_report::DeviceInformation {
273                vendor_id: Some(0),
274                product_id: Some(0),
275                version: Some(0),
276                polling_rate: Some(0),
277                ..Default::default()
278            },
279            device_id: 0,
280        },
281    )
282}
283
284fn into_input_event(
285    keyboard_event: KeyboardEvent,
286    event_time: MonotonicInstant,
287) -> input_device::InputEvent {
288    input_device::InputEvent {
289        device_event: input_device::InputDeviceEvent::Keyboard(keyboard_event),
290        device_descriptor: empty_keyboard_device_descriptor(),
291        event_time,
292        handled: input_device::Handled::No,
293        trace_id: None,
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::testing_utilities::{create_fake_input_event, create_input_event};
301    use fidl_fuchsia_input::Key;
302    use fuchsia_async as fasync;
303    use pretty_assertions::assert_eq;
304    use zx::{EventPair, Peered};
305
306    // Manages losing and regaining display, since manual management is error-prone:
307    // if signal_peer does not change the signal state, the waiting process will block
308    // forever, which makes tests run longer than needed.
309    struct DisplayWrangler {
310        event: EventPair,
311        last: Signals,
312    }
313
314    impl DisplayWrangler {
315        fn new(event: EventPair) -> Self {
316            let mut instance = DisplayWrangler { event, last: *DISPLAY_OWNED };
317            // Signal needs to be initialized before the handlers attempts to read it.
318            // This is normally always the case in production.
319            // Else, the `new_for_test` below will panic with a TIMEOUT error.
320            instance.set_unowned();
321            instance
322        }
323
324        fn set_unowned(&mut self) {
325            assert!(self.last != *DISPLAY_UNOWNED, "display is already unowned");
326            self.event.signal_peer(*DISPLAY_OWNED, *DISPLAY_UNOWNED).unwrap();
327            self.last = *DISPLAY_UNOWNED;
328        }
329
330        fn set_owned(&mut self) {
331            assert!(self.last != *DISPLAY_OWNED, "display is already owned");
332            self.event.signal_peer(*DISPLAY_UNOWNED, *DISPLAY_OWNED).unwrap();
333            self.last = *DISPLAY_OWNED;
334        }
335    }
336
337    #[fuchsia::test]
338    async fn display_ownership_change() {
339        // handler_event is the event that the unit under test will examine for
340        // display ownership changes.  test_event is used to set the appropriate
341        // signals.
342        let (test_event, handler_event) = EventPair::create();
343
344        // test_sender is used to pipe input events into the handler.
345        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
346
347        // test_receiver is used to pipe input events out of the handler.
348        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
349
350        // The unit under test adds a () each time it completes one pass through
351        // its event loop.  Use to ensure synchronization.
352        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
353
354        // We use a wrapper to signal test_event correctly, since doing it wrong
355        // by hand causes tests to hang, which isn't the best dev experience.
356        let mut wrangler = DisplayWrangler::new(test_event);
357        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
358
359        let _task = fasync::Task::local(async move {
360            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
361        });
362
363        let fake_time = MonotonicInstant::from_nanos(42);
364
365        // Go two full circles of signaling.
366
367        // 1
368        wrangler.set_owned();
369        loop_done.next().await;
370        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
371        loop_done.next().await;
372
373        // 2
374        wrangler.set_unowned();
375        loop_done.next().await;
376        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
377        loop_done.next().await;
378
379        // 3
380        wrangler.set_owned();
381        loop_done.next().await;
382        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
383        loop_done.next().await;
384
385        // 4
386        wrangler.set_unowned();
387        loop_done.next().await;
388        test_sender.unbounded_send(create_fake_input_event(fake_time)).unwrap();
389        loop_done.next().await;
390
391        let actual: Vec<InputEvent> =
392            test_receiver.take(4).map(|e| e.into_with_event_time(fake_time)).collect().await;
393
394        assert_eq!(
395            actual,
396            vec![
397                // Event received while we owned the display.
398                create_fake_input_event(fake_time),
399                // Event received when we lost the display.
400                create_fake_input_event(fake_time).into_handled(),
401                // Display ownership regained.
402                create_fake_input_event(fake_time),
403                // Display ownership lost.
404                create_fake_input_event(fake_time).into_handled(),
405            ]
406        );
407    }
408
409    fn new_keyboard_input_event(key: Key, event_type: KeyEventType) -> InputEvent {
410        let fake_time = MonotonicInstant::from_nanos(42);
411        create_input_event(
412            KeyboardEvent::new(key, event_type),
413            &input_device::InputDeviceDescriptor::Fake,
414            fake_time,
415            input_device::Handled::No,
416        )
417    }
418
419    #[fuchsia::test]
420    async fn basic_key_state_handling() {
421        let (test_event, handler_event) = EventPair::create();
422        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
423        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
424        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
425        let mut wrangler = DisplayWrangler::new(test_event);
426        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
427        let _task = fasync::Task::local(async move {
428            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
429        });
430
431        let fake_time = MonotonicInstant::from_nanos(42);
432
433        // Gain the display, and press a key.
434        wrangler.set_owned();
435        loop_done.next().await;
436        test_sender
437            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
438            .unwrap();
439        loop_done.next().await;
440
441        // Lose display.
442        wrangler.set_unowned();
443        loop_done.next().await;
444
445        // Regain display
446        wrangler.set_owned();
447        loop_done.next().await;
448
449        // Key event after regaining.
450        test_sender
451            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
452            .unwrap();
453        loop_done.next().await;
454
455        let actual: Vec<InputEvent> =
456            test_receiver.take(4).map(|e| e.into_with_event_time(fake_time)).collect().await;
457
458        assert_eq!(
459            actual,
460            vec![
461                new_keyboard_input_event(Key::A, KeyEventType::Pressed),
462                new_keyboard_input_event(Key::A, KeyEventType::Cancel)
463                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
464                new_keyboard_input_event(Key::A, KeyEventType::Sync)
465                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
466                new_keyboard_input_event(Key::A, KeyEventType::Released),
467            ]
468        );
469    }
470
471    #[fuchsia::test]
472    async fn more_key_state_handling() {
473        let (test_event, handler_event) = EventPair::create();
474        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
475        let (handler_sender, test_receiver) = mpsc::unbounded::<InputEvent>();
476        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
477        let mut wrangler = DisplayWrangler::new(test_event);
478        let handler = DisplayOwnership::new_for_test(handler_event, loop_done_sender);
479        let _task = fasync::Task::local(async move {
480            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
481        });
482
483        let fake_time = MonotonicInstant::from_nanos(42);
484
485        wrangler.set_owned();
486        loop_done.next().await;
487        test_sender
488            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
489            .unwrap();
490        loop_done.next().await;
491        test_sender
492            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Pressed))
493            .unwrap();
494        loop_done.next().await;
495
496        // Lose display, release a key, press a key.
497        wrangler.set_unowned();
498        loop_done.next().await;
499        test_sender
500            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Released))
501            .unwrap();
502        loop_done.next().await;
503        test_sender
504            .unbounded_send(new_keyboard_input_event(Key::C, KeyEventType::Pressed))
505            .unwrap();
506        loop_done.next().await;
507
508        // Regain display
509        wrangler.set_owned();
510        loop_done.next().await;
511
512        // Key event after regaining.
513        test_sender
514            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
515            .unwrap();
516        loop_done.next().await;
517        test_sender
518            .unbounded_send(new_keyboard_input_event(Key::C, KeyEventType::Released))
519            .unwrap();
520        loop_done.next().await;
521
522        let actual: Vec<InputEvent> =
523            test_receiver.take(10).map(|e| e.into_with_event_time(fake_time)).collect().await;
524
525        assert_eq!(
526            actual,
527            vec![
528                new_keyboard_input_event(Key::A, KeyEventType::Pressed),
529                new_keyboard_input_event(Key::B, KeyEventType::Pressed),
530                new_keyboard_input_event(Key::A, KeyEventType::Cancel)
531                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
532                new_keyboard_input_event(Key::B, KeyEventType::Cancel)
533                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
534                new_keyboard_input_event(Key::B, KeyEventType::Released).into_handled(),
535                new_keyboard_input_event(Key::C, KeyEventType::Pressed).into_handled(),
536                // The CANCEL and SYNC events are emitted in the sort ordering of the
537                // `Key` enum values. Perhaps they should be emitted instead in the order
538                // they have been received for SYNC, and in reverse order for CANCEL.
539                new_keyboard_input_event(Key::A, KeyEventType::Sync)
540                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
541                new_keyboard_input_event(Key::C, KeyEventType::Sync)
542                    .into_with_device_descriptor(empty_keyboard_device_descriptor()),
543                new_keyboard_input_event(Key::A, KeyEventType::Released),
544                new_keyboard_input_event(Key::C, KeyEventType::Released),
545            ]
546        );
547    }
548
549    #[fuchsia::test]
550    async fn display_ownership_initialized_with_inspect_node() {
551        let (test_event, handler_event) = EventPair::create();
552        let (loop_done_sender, _) = mpsc::unbounded::<()>();
553        let inspector = fuchsia_inspect::Inspector::default();
554        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
555        // Signal needs to be initialized first so DisplayOwnership::new doesn't panic with a TIMEOUT error
556        let _ = DisplayWrangler::new(test_event);
557        let _handler = DisplayOwnership::new_internal(
558            handler_event,
559            Some(loop_done_sender),
560            &fake_handlers_node,
561        );
562        diagnostics_assertions::assert_data_tree!(inspector, root: {
563            input_handlers_node: {
564                display_ownership: {
565                    events_received_count: 0u64,
566                    events_handled_count: 0u64,
567                    last_received_timestamp_ns: 0u64,
568                    "fuchsia.inspect.Health": {
569                        status: "STARTING_UP",
570                        // Timestamp value is unpredictable and not relevant in this context,
571                        // so we only assert that the property is present.
572                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
573                    },
574                }
575            }
576        });
577    }
578
579    #[fuchsia::test]
580    async fn display_ownership_inspect_counts_events() {
581        let (test_event, handler_event) = EventPair::create();
582        let (test_sender, handler_receiver) = mpsc::unbounded::<InputEvent>();
583        let (handler_sender, _test_receiver) = mpsc::unbounded::<InputEvent>();
584        let (loop_done_sender, mut loop_done) = mpsc::unbounded::<()>();
585        let mut wrangler = DisplayWrangler::new(test_event);
586        let inspector = fuchsia_inspect::Inspector::default();
587        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
588        let handler = DisplayOwnership::new_internal(
589            handler_event,
590            Some(loop_done_sender),
591            &fake_handlers_node,
592        );
593        let _task = fasync::Task::local(async move {
594            handler.handle_input_events(handler_receiver, handler_sender).await.unwrap();
595        });
596
597        // Gain the display, and press a key.
598        wrangler.set_owned();
599        loop_done.next().await;
600        test_sender
601            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Pressed))
602            .unwrap();
603        loop_done.next().await;
604
605        // Lose display
606        // Input event is marked `Handled` if received after display ownership is lost
607        wrangler.set_unowned();
608        loop_done.next().await;
609        test_sender
610            .unbounded_send(new_keyboard_input_event(Key::B, KeyEventType::Pressed))
611            .unwrap();
612        loop_done.next().await;
613
614        // Regain display
615        wrangler.set_owned();
616        loop_done.next().await;
617
618        // Key event after regaining.
619        test_sender
620            .unbounded_send(new_keyboard_input_event(Key::A, KeyEventType::Released))
621            .unwrap();
622        loop_done.next().await;
623
624        diagnostics_assertions::assert_data_tree!(inspector, root: {
625            input_handlers_node: {
626                display_ownership: {
627                    events_received_count: 3u64,
628                    events_handled_count: 1u64,
629                    last_received_timestamp_ns: 42u64,
630                    "fuchsia.inspect.Health": {
631                        status: "STARTING_UP",
632                        // Timestamp value is unpredictable and not relevant in this context,
633                        // so we only assert that the property is present.
634                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
635                    },
636                }
637            }
638        });
639    }
640}