1use crate::clock;
14use fuchsia_async as fasync;
15use fuchsia_inspect::{self as inspect, NumericProperty, component};
16use fuchsia_inspect_derive::{IValue, Inspect};
17use futures::StreamExt;
18use futures::channel::mpsc::UnboundedReceiver;
19#[cfg(test)]
20use futures::channel::mpsc::UnboundedSender;
21use settings_common::inspect::event::{Direction, ResponseType, UsageEvent};
22use settings_common::trace;
23use settings_inspect_utils::joinable_inspect_vecdeque::JoinableInspectVecDeque;
24use settings_inspect_utils::managed_inspect_map::ManagedInspectMap;
25use settings_inspect_utils::managed_inspect_queue::ManagedInspectQueue;
26
27const MAX_PENDING_REQUESTS: usize = 20;
31
32const MAX_REQUEST_RESPONSE_PAIRS: usize = 10;
34
35const MAX_REQUEST_RESPONSE_TIMESTAMPS: usize = 10;
37
38const REQUEST_RESPONSE_NODE_NAME: &str = "requests_and_responses";
40
41const RESPONSE_COUNTS_NODE_NAME: &str = "response_counts";
43
44#[derive(Default, Inspect)]
45struct SettingTypeResponseCountInfo {
47 #[inspect(forward)]
50 response_counts_by_type: ManagedInspectMap<ResponseTypeCount>,
51}
52
53#[derive(Default, Inspect)]
54struct ResponseTypeCount {
56 count: inspect::UintProperty,
57 inspect_node: inspect::Node,
58}
59
60#[derive(Inspect)]
62struct SettingTypeRequestResponseInfo {
63 #[inspect(rename = "requests_and_responses")]
70 requests_and_responses_by_type: ManagedInspectMap<ManagedInspectMap<RequestResponsePairInfo>>,
71
72 pending_requests: ManagedInspectQueue<PendingRequestInspectInfo>,
74
75 inspect_node: inspect::Node,
77
78 #[inspect(skip)]
83 count: u64,
84}
85
86impl SettingTypeRequestResponseInfo {
87 fn new() -> Self {
88 Self {
89 requests_and_responses_by_type: Default::default(),
90 pending_requests: ManagedInspectQueue::<PendingRequestInspectInfo>::new(
91 MAX_PENDING_REQUESTS,
92 ),
93 inspect_node: Default::default(),
94 count: 0,
95 }
96 }
97}
98
99#[derive(Debug, Default, Inspect)]
101struct PendingRequestInspectInfo {
102 request: IValue<String>,
104
105 #[inspect(skip)]
108 request_type: String,
109
110 timestamp: IValue<String>,
113
114 #[inspect(skip)]
116 count: u64,
117
118 inspect_node: inspect::Node,
120}
121
122#[derive(Default, Inspect)]
128struct RequestResponsePairInfo {
129 request: IValue<String>,
131
132 response: IValue<String>,
134
135 #[inspect(skip)]
137 request_count: u64,
138
139 request_timestamps: IValue<JoinableInspectVecDeque>,
144
145 response_timestamps: IValue<JoinableInspectVecDeque>,
150
151 inspect_node: inspect::Node,
153}
154
155impl RequestResponsePairInfo {
156 fn new(request: String, response: String, count: u64) -> Self {
157 Self {
158 request: IValue::new(request),
159 response: IValue::new(response),
160 request_count: count,
161 request_timestamps: Default::default(),
162 response_timestamps: Default::default(),
163 inspect_node: Default::default(),
164 }
165 }
166}
167
168pub(crate) struct SettingProxyInspectAgent {
171 response_counts: ManagedInspectMap<SettingTypeResponseCountInfo>,
173
174 setting_request_response_info: ManagedInspectMap<SettingTypeRequestResponseInfo>,
176
177 usage_rx: Option<UnboundedReceiver<UsageEvent>>,
178
179 #[cfg(test)]
180 done_tx: Option<UnboundedSender<()>>,
181}
182
183impl SettingProxyInspectAgent {
184 pub fn new(rx: UnboundedReceiver<UsageEvent>) -> Self {
185 Self::create_with_node(
186 rx,
187 component::inspector().root().create_child(REQUEST_RESPONSE_NODE_NAME),
188 component::inspector().root().create_child(RESPONSE_COUNTS_NODE_NAME),
189 #[cfg(test)]
190 None,
191 )
192 }
193
194 fn create_with_node(
195 rx: UnboundedReceiver<UsageEvent>,
196 request_response_inspect_node: inspect::Node,
197 response_counts_node: inspect::Node,
198 #[cfg(test)] done_tx: Option<UnboundedSender<()>>,
199 ) -> Self {
200 SettingProxyInspectAgent {
201 response_counts: ManagedInspectMap::<SettingTypeResponseCountInfo>::with_node(
202 response_counts_node,
203 ),
204 setting_request_response_info:
205 ManagedInspectMap::<SettingTypeRequestResponseInfo>::with_node(
206 request_response_inspect_node,
207 ),
208 usage_rx: Some(rx),
209 #[cfg(test)]
210 done_tx,
211 }
212 }
213
214 pub fn initialize(mut self) {
215 fasync::Task::local(async move {
216 let id = fuchsia_trace::Id::new();
217 trace!(id, c"setting_proxy_inspect_agent");
218 let mut usage_rx = self.usage_rx.take().unwrap();
219 while let Some(usage_event) = usage_rx.next().await {
220 if let Direction::Request(_) = usage_event.direction {
221 self.process_usage_event(usage_event);
222 } else {
223 self.process_usage_response_event(usage_event);
224 }
225 #[cfg(test)]
226 if let Some(done_tx) = &self.done_tx {
227 let _ = done_tx.unbounded_send(());
228 }
229 }
230 })
231 .detach();
232 }
233
234 fn process_usage_event(&mut self, event: UsageEvent) {
235 let timestamp = clock::inspect_format_now();
236
237 let request_response_info = self
239 .setting_request_response_info
240 .get_or_insert_with(event.setting.to_string(), SettingTypeRequestResponseInfo::new);
241
242 request_response_info.count += 1;
243
244 let Direction::Request(request) = event.direction else {
245 panic!("Call process_usage_event with response!");
246 };
247 let pending_request_info = PendingRequestInspectInfo {
248 request: request.into(),
249 request_type: format!("{:?}", event.request_type),
250 timestamp: timestamp.into(),
251 count: event.id,
252 inspect_node: inspect::Node::default(),
253 };
254
255 let count_key = format!("{:020}", event.id);
256 request_response_info.pending_requests.push(&count_key, pending_request_info);
257 }
258
259 fn process_usage_response_event(&mut self, event: UsageEvent) {
260 let setting_type_str = event.setting.to_string();
261 let timestamp = clock::inspect_format_now();
262 let Direction::Response(response, response_type) = event.direction else {
263 panic!("Called process_usage_response_event with a request!");
264 };
265
266 self.increment_response_count(setting_type_str.clone(), response_type);
268
269 let condensed_setting_type_info = self
272 .setting_request_response_info
273 .map_mut()
274 .get_mut(&setting_type_str)
275 .expect("Missing info for request");
276
277 let pending_requests = &mut condensed_setting_type_info.pending_requests;
278
279 let position = match pending_requests.iter_mut().position(|info| info.count == event.id) {
283 Some(position) => position,
284 None => {
285 return;
288 }
289 };
290 let pending =
291 pending_requests.items_mut().remove(position).expect("Failed to find pending item");
292
293 let request_type_info_map = condensed_setting_type_info
295 .requests_and_responses_by_type
296 .get_or_insert_with(pending.request_type, || {
297 ManagedInspectMap::<RequestResponsePairInfo>::default()
298 });
299
300 let map_key = format!("{:?}{:?}", pending.request, response);
303
304 let removed_info = request_type_info_map.map_mut().remove(&map_key);
309
310 let mut info = removed_info.unwrap_or_else(|| {
311 RequestResponsePairInfo::new(pending.request.into_inner(), response, pending.count)
312 });
313 {
314 let mut_requests = &mut info.request_timestamps.as_mut().0;
318 let mut_responses = &mut info.response_timestamps.as_mut().0;
319
320 mut_requests.push_back(pending.timestamp.into_inner());
321 mut_responses.push_back(timestamp);
322
323 if mut_requests.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
325 let _ = mut_requests.pop_front();
326 }
327 if mut_responses.len() > MAX_REQUEST_RESPONSE_TIMESTAMPS {
328 let _ = mut_responses.pop_front();
329 }
330 }
331
332 let count_key = format!("{:020}", pending.count);
334 let _ = request_type_info_map.insert_with_property_name(map_key, count_key, info);
335
336 let num_request_response_pairs = request_type_info_map.map().len();
338 if num_request_response_pairs > MAX_REQUEST_RESPONSE_PAIRS {
339 let mut lowest_count: u64 = u64::MAX;
342 let mut lowest_key: Option<String> = None;
343 for (key, inspect_info) in request_type_info_map.map() {
344 if inspect_info.request_count < lowest_count {
345 lowest_count = inspect_info.request_count;
346 lowest_key = Some(key.clone());
347 }
348 }
349
350 if let Some(key_to_remove) = lowest_key {
351 let _ = request_type_info_map.map_mut().remove(&key_to_remove);
352 }
353 }
354 }
355
356 fn increment_response_count(&mut self, setting_type_str: String, response_type: ResponseType) {
357 let response_count_info = self
360 .response_counts
361 .get_or_insert_with(setting_type_str, SettingTypeResponseCountInfo::default);
362
363 let response_count = response_count_info
366 .response_counts_by_type
367 .get_or_insert_with(format!("{response_type:?}"), ResponseTypeCount::default);
368 let _ = response_count.count.add(1u64);
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use diagnostics_assertions::{TreeAssertion, assert_data_tree};
376 use futures::channel::mpsc;
377 use settings_common::inspect::event::RequestType;
378 use zx::MonotonicInstant;
379
380 #[fuchsia::test(allow_stalls = false)]
383 async fn test_inspect_grouped_responses() {
384 clock::mock::set(MonotonicInstant::from_nanos(0));
386
387 let inspector = inspect::Inspector::default();
388 let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
389 let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
390
391 let (tx, rx) = mpsc::unbounded();
392 let (done_tx, mut done_rx) = mpsc::unbounded();
393 let agent = SettingProxyInspectAgent::create_with_node(
394 rx,
395 condense_node,
396 response_counts_node,
397 Some(done_tx),
398 );
399 agent.initialize();
400
401 let _ = tx.unbounded_send(UsageEvent {
403 setting: "Display",
404 request_type: RequestType::Set,
405 direction: Direction::Request(
406 "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
407 ),
408 id: 0,
409 });
410 let _ = done_rx.next().await;
411 let _ = tx.unbounded_send(UsageEvent {
412 setting: "Display",
413 request_type: RequestType::Set,
414 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
415 id: 0,
416 });
417 let _ = done_rx.next().await;
418
419 clock::mock::set(MonotonicInstant::from_nanos(100));
421 let _ = tx.unbounded_send(UsageEvent {
422 setting: "Display",
423 request_type: RequestType::Set,
424 direction: Direction::Request(
425 "SetDisplayInfo{auto_brightness: Some(true)}".to_string(),
426 ),
427 id: 1,
428 });
429 let _ = done_rx.next().await;
430 let _ = tx.unbounded_send(UsageEvent {
431 setting: "Display",
432 request_type: RequestType::Set,
433 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
434 id: 1,
435 });
436 let _ = done_rx.next().await;
437
438 clock::mock::set(MonotonicInstant::from_nanos(200));
441 let _ = tx.unbounded_send(UsageEvent {
442 setting: "Display",
443 request_type: RequestType::Set,
444 direction: Direction::Request(
445 "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
446 ),
447 id: 2,
448 });
449 let _ = done_rx.next().await;
450 let _ = tx.unbounded_send(UsageEvent {
451 setting: "Display",
452 request_type: RequestType::Set,
453 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
454 id: 2,
455 });
456 let _ = done_rx.next().await;
457
458 assert_data_tree!(inspector, root: contains {
459 requests_and_responses: {
460 "Display": {
461 "pending_requests": {},
462 "requests_and_responses": {
463 "Set": {
464 "00000000000000000001": {
465 "request": "SetDisplayInfo{auto_brightness: Some(true)}",
466 "request_timestamps": "0.000000100",
467 "response": "Ok(None)",
468 "response_timestamps": "0.000000100"
469 },
470 "00000000000000000002": {
471 "request": "SetDisplayInfo{auto_brightness: Some(false)}",
472 "request_timestamps": "0.000000000,0.000000200",
473 "response": "Ok(None)",
474 "response_timestamps": "0.000000000,0.000000200"
475 }
476 }
477 }
478 }
479 },
480 });
481 }
482
483 #[fuchsia::test(allow_stalls = false)]
486 async fn test_inspect_mixed_request_types() {
487 clock::mock::set(MonotonicInstant::from_nanos(0));
489
490 let inspector = inspect::Inspector::default();
491 let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
492 let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
493
494 let (tx, rx) = mpsc::unbounded();
495 let (done_tx, mut done_rx) = mpsc::unbounded();
496 let agent = SettingProxyInspectAgent::create_with_node(
497 rx,
498 condense_node,
499 response_counts_node,
500 Some(done_tx),
501 );
502 agent.initialize();
503
504 let _ = tx.unbounded_send(UsageEvent {
506 setting: "Display",
507 request_type: RequestType::Set,
508 direction: Direction::Request(
509 "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
510 ),
511 id: 0,
512 });
513 let _ = done_rx.next().await;
514 let _ = tx.unbounded_send(UsageEvent {
515 setting: "Display",
516 request_type: RequestType::Set,
517 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
518 id: 0,
519 });
520 let _ = done_rx.next().await;
521
522 clock::mock::set(MonotonicInstant::from_nanos(100));
524 let _ = tx.unbounded_send(UsageEvent {
525 setting: "Display",
526 request_type: RequestType::Get,
527 direction: Direction::Request("WatchDisplayInfo".to_string()),
528 id: 1,
529 });
530 let _ = done_rx.next().await;
531 let _ = tx.unbounded_send(UsageEvent {
532 setting: "Display",
533 request_type: RequestType::Get,
534 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
535 id: 1,
536 });
537 let _ = done_rx.next().await;
538
539 clock::mock::set(MonotonicInstant::from_nanos(200));
541 let _ = tx.unbounded_send(UsageEvent {
542 setting: "Display",
543 request_type: RequestType::Set,
544 direction: Direction::Request(
545 "SetDisplayInfo{auto_brightness: Some(true)}".to_string(),
546 ),
547 id: 2,
548 });
549 let _ = done_rx.next().await;
550 let _ = tx.unbounded_send(UsageEvent {
551 setting: "Display",
552 request_type: RequestType::Set,
553 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
554 id: 2,
555 });
556 let _ = done_rx.next().await;
557
558 clock::mock::set(MonotonicInstant::from_nanos(300));
559 let _ = tx.unbounded_send(UsageEvent {
560 setting: "Display",
561 request_type: RequestType::Get,
562 direction: Direction::Request("WatchDisplayInfo".to_string()),
563 id: 3,
564 });
565 let _ = done_rx.next().await;
566 let _ = tx.unbounded_send(UsageEvent {
567 setting: "Display",
568 request_type: RequestType::Get,
569 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
570 id: 3,
571 });
572 let _ = done_rx.next().await;
573
574 assert_data_tree!(inspector, root: contains {
575 requests_and_responses: {
576 "Display": {
577 "pending_requests": {},
578 "requests_and_responses": {
579 "Get": {
580 "00000000000000000003": {
581 "request": "WatchDisplayInfo",
582 "request_timestamps": "0.000000100,0.000000300",
583 "response": "Ok(None)",
584 "response_timestamps": "0.000000100,0.000000300"
585 }
586 },
587 "Set": {
588 "00000000000000000000": {
589 "request": "SetDisplayInfo{auto_brightness: Some(false)}",
590 "request_timestamps": "0.000000000",
591 "response": "Ok(None)",
592 "response_timestamps": "0.000000000"
593 },
594 "00000000000000000002": {
595 "request": "SetDisplayInfo{auto_brightness: Some(true)}",
596 "request_timestamps": "0.000000200",
597 "response": "Ok(None)",
598 "response_timestamps": "0.000000200"
599 }
600 }
601 }
602 }
603 },
604 response_counts: {
605 "Display": {
606 "OkNone": {
607 count: 4u64,
608 }
609 },
610 },
611 });
612 }
613
614 #[fuchsia::test(allow_stalls = false)]
615 async fn test_pending_request() {
616 clock::mock::set(MonotonicInstant::from_nanos(0));
618
619 let inspector = inspect::Inspector::default();
620 let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
621 let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
622
623 let (tx, rx) = mpsc::unbounded();
624 let (done_tx, mut done_rx) = mpsc::unbounded();
625 let agent = SettingProxyInspectAgent::create_with_node(
626 rx,
627 condense_node,
628 response_counts_node,
629 Some(done_tx),
630 );
631 agent.initialize();
632
633 let _ = tx.unbounded_send(UsageEvent {
634 setting: "Display",
635 request_type: RequestType::Set,
636 direction: Direction::Request(
637 "SetDisplayInfo{auto_brightness: Some(false)}".to_string(),
638 ),
639 id: 0,
640 });
641 let _ = done_rx.next().await;
642
643 assert_data_tree!(inspector, root: contains {
644 requests_and_responses: {
645 "Display": {
646 "pending_requests": {
647 "00000000000000000000": {
648 "request": "SetDisplayInfo{auto_brightness: Some(false)}",
649 "timestamp": "0.000000000",
650 }
651 },
652 "requests_and_responses": {}
653 }
654 },
655 });
656 }
657
658 #[fuchsia::test(allow_stalls = false)]
659 async fn test_response_counts_inspect() {
660 clock::mock::set(MonotonicInstant::from_nanos(0));
662
663 let inspector = inspect::Inspector::default();
664 let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
665 let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
666
667 let (tx, rx) = mpsc::unbounded();
668 let (done_tx, mut done_rx) = mpsc::unbounded();
669 let agent = SettingProxyInspectAgent::create_with_node(
670 rx,
671 condense_node,
672 response_counts_node,
673 Some(done_tx),
674 );
675 agent.initialize();
676 let _ = tx.unbounded_send(UsageEvent {
677 setting: "Display",
678 request_type: RequestType::Set,
679 direction: Direction::Request(
680 "SetDisplayInfo{auto_brightness:Some(false)}".to_string(),
681 ),
682 id: 0,
683 });
684 let _ = done_rx.next().await;
685 let _ = tx.unbounded_send(UsageEvent {
686 setting: "Display",
687 request_type: RequestType::Set,
688 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
689 id: 0,
690 });
691 let _ = done_rx.next().await;
692
693 clock::mock::set(MonotonicInstant::from_nanos(100));
694 let _ = tx.unbounded_send(UsageEvent {
695 setting: "Display",
696 request_type: RequestType::Get,
697 direction: Direction::Request("WatchDisplayInfo".to_string()),
698 id: 1,
699 });
700 let _ = done_rx.next().await;
701 let _ = tx.unbounded_send(UsageEvent {
702 setting: "Display",
703 request_type: RequestType::Get,
704 direction: Direction::Response("Ok(Some())".to_string(), ResponseType::OkNone),
705 id: 1,
706 });
707 let _ = done_rx.next().await;
708
709 clock::mock::set(MonotonicInstant::from_nanos(200));
710 let _ = tx.unbounded_send(UsageEvent {
711 setting: "Display",
712 request_type: RequestType::Set,
713 direction: Direction::Request("SetDisplayInfo{auto_brightness:None}".to_string()),
714 id: 2,
715 });
716 let _ = done_rx.next().await;
717 let _ = tx.unbounded_send(UsageEvent {
718 setting: "Display",
719 request_type: RequestType::Set,
720 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
721 id: 2,
722 });
723 let _ = done_rx.next().await;
724
725 clock::mock::set(MonotonicInstant::from_nanos(300));
726 let _ = tx.unbounded_send(UsageEvent {
727 setting: "Display",
728 request_type: RequestType::Get,
729 direction: Direction::Request("WatchDisplayInfo".to_string()),
730 id: 3,
731 });
732 let _ = done_rx.next().await;
733 let _ = tx.unbounded_send(UsageEvent {
734 setting: "Display",
735 request_type: RequestType::Get,
736 direction: Direction::Response("Ok(Some())".to_string(), ResponseType::OkNone),
737 id: 3,
738 });
739 let _ = done_rx.next().await;
740
741 assert_data_tree!(inspector, root: contains {
742 response_counts: {
743 "Display": {
744 "OkNone": {
745 count: 4u64,
746 },
747 },
748 },
749 });
750 }
751
752 #[fuchsia::test(allow_stalls = false)]
755 async fn inspect_queue_test() {
756 clock::mock::set(MonotonicInstant::from_nanos(0));
758 let inspector = inspect::Inspector::default();
759 let condense_node = inspector.root().create_child(REQUEST_RESPONSE_NODE_NAME);
760 let response_counts_node = inspector.root().create_child(RESPONSE_COUNTS_NODE_NAME);
761
762 let (tx, rx) = mpsc::unbounded();
763 let (done_tx, mut done_rx) = mpsc::unbounded();
764 let agent = SettingProxyInspectAgent::create_with_node(
765 rx,
766 condense_node,
767 response_counts_node,
768 Some(done_tx),
769 );
770 agent.initialize();
771
772 let _ = tx.unbounded_send(UsageEvent {
773 setting: "Intl",
774 request_type: RequestType::Set,
775 direction: Direction::Request(
776 "SetIntlInfo { \
777 locales: Some([LocaleId { id: \"en-US\" }]), \
778 temperature_unit: Some(Celsius), \
779 time_zone_id: Some(\"UTC\"), \
780 hour_cycle: None \
781 }"
782 .to_string(),
783 ),
784 id: 0,
785 });
786 let _ = done_rx.next().await;
787 let _ = tx.unbounded_send(UsageEvent {
788 setting: "Intl",
789 request_type: RequestType::Set,
790 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
791 id: 0,
792 });
793 let _ = done_rx.next().await;
794
795 for i in 0..MAX_REQUEST_RESPONSE_PAIRS + 1 {
798 let _ = tx.unbounded_send(UsageEvent {
799 setting: "Display",
800 request_type: RequestType::Set,
801 direction: Direction::Request(format!(
802 "SetDisplayInfo{{manual_brightness_value: Some({})}}",
803 (i as f32) / 100f32
804 )),
805 id: i as u64,
806 });
807 let _ = done_rx.next().await;
808 let _ = tx.unbounded_send(UsageEvent {
809 setting: "Display",
810 request_type: RequestType::Set,
811 direction: Direction::Response("Ok(None)".to_string(), ResponseType::OkNone),
812 id: i as u64,
813 });
814 let _ = done_rx.next().await;
815 }
816
817 fn display_subtree_assertion() -> TreeAssertion {
820 let mut tree_assertion = TreeAssertion::new("Display", false);
821 let mut request_response_assertion = TreeAssertion::new("requests_and_responses", true);
822 let mut request_assertion = TreeAssertion::new("Set", true);
823
824 for i in 1..MAX_REQUEST_RESPONSE_PAIRS + 1 {
825 request_assertion
827 .add_child_assertion(TreeAssertion::new(&format!("{i:020}"), false));
828 }
829 request_response_assertion.add_child_assertion(request_assertion);
830 tree_assertion.add_child_assertion(request_response_assertion);
831 tree_assertion
832 }
833
834 assert_data_tree!(inspector, root: contains {
835 requests_and_responses: {
836 display_subtree_assertion(),
837 "Intl": {
838 "pending_requests": {},
839 "requests_and_responses": {
840 "Set": {
841 "00000000000000000000": {
842 "request": "SetIntlInfo { \
843 locales: Some([LocaleId { id: \"en-US\" }]), \
844 temperature_unit: Some(Celsius), \
845 time_zone_id: Some(\"UTC\"), \
846 hour_cycle: None \
847 }",
848 "request_timestamps": "0.000000000",
849 "response": "Ok(None)",
850 "response_timestamps": "0.000000000"
851 }
852 }
853 }
854 }
855 },
856 });
857 }
858}