reloadtest_tools/reloadtest_tools.rs
1// Copyright 2023 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 anyhow::{anyhow, Result};
6use fidl_fuchsia_driver_development as fdd;
7use futures::channel::mpsc;
8use futures::StreamExt;
9use std::collections::{HashMap, HashSet};
10
11// Wait for the events from the |nodes| to be received. Updates the entries to be Some.
12pub async fn wait_for_nodes(
13 nodes: &mut HashMap<String, Option<Option<u64>>>,
14 receiver: &mut mpsc::Receiver<(String, String)>,
15) -> Result<()> {
16 while nodes.values().any(|&x| x.is_none()) {
17 let (from_node, _) = receiver.next().await.ok_or(anyhow!("Receiver failed"))?;
18 if !nodes.contains_key(&from_node) {
19 return Err(anyhow!("Couldn't find node '{}' in 'nodes'.", from_node.to_string()));
20 }
21 nodes.entry(from_node).and_modify(|x| {
22 *x = Some(None);
23 });
24 }
25
26 Ok(())
27}
28
29// Validates the host koids given the device infos.
30// Performs the following:
31// - Stores the host koid for nodes in changed_or_new as those are expected to be new/changed.
32// - Validate bound nodes are not in should_not_exist
33// - Validate bound nodes have the same koid as the most recent item in previous.
34// - Validate that items in changed_or_new have been changed from the most recent item in previous
35// if they are not new.
36pub async fn validate_host_koids(
37 test_stage_name: &str,
38 device_infos: Vec<fdd::NodeInfo>,
39 changed_or_new: &mut HashMap<String, Option<Option<u64>>>,
40 previous: Vec<&HashMap<String, Option<Option<u64>>>>,
41 should_not_exist: Option<&HashSet<String>>,
42) -> Result<()> {
43 for dev in &device_infos {
44 let key = dev.moniker.clone().unwrap().split(".").last().unwrap().to_string();
45
46 // Items in changed_or_new are expected to be different so just save that info and move on.
47 if changed_or_new.contains_key(&key) {
48 changed_or_new.entry(key).and_modify(|x| {
49 *x = Some(dev.driver_host_koid);
50 });
51
52 continue;
53 }
54
55 // Skip comparison and should_not_exist check as the koid is not valid when its unbound.
56 if dev.bound_driver_url == Some("unbound".to_string()) {
57 continue;
58 }
59
60 // Error if the item is in should not exist.
61 if let Some(should_not_exist_value) = &should_not_exist {
62 if should_not_exist_value.contains(&key) {
63 return Err(anyhow!(
64 "Found node that should not exist after {}: '{}'.",
65 test_stage_name,
66 key
67 ));
68 }
69 }
70
71 // Go through the previous items (which should come in from most to least recent order)
72 // and make sure this matches the most recent instance.
73 for prev in &previous {
74 if let Some(prev_koid) = prev.get(&key) {
75 match prev_koid {
76 Some(prev_koid_value) => {
77 if *prev_koid_value != dev.driver_host_koid {
78 return Err(anyhow!(
79 "koid should not have changed for node '{}' after {}.",
80 key,
81 test_stage_name
82 ));
83 }
84
85 break;
86 }
87 None => {
88 // This is not possible as things are today.
89 // The values in the entries cannot be None since in wait_for_nodes we have
90 // waited for all of them to not be None.
91 return Err(anyhow!("prev koid not available after."));
92 }
93 }
94 }
95 }
96 }
97
98 // Now we can make sure those items in changed_or_new are different than their most recent
99 // previous item. Skipping ones that are not seen in previous.
100 for (key, changed_or_new_node_entry) in changed_or_new {
101 // First find the most recent previous koid if one exists.
102 let mut koid_before: Option<&Option<u64>> = None;
103 for prev in &previous {
104 if let Some(prev_koid) = prev.get(key) {
105 match prev_koid {
106 Some(prev_koid_value) => {
107 koid_before = Some(prev_koid_value);
108 break;
109 }
110 None => {
111 // This is not possible as things are today.
112 // The values in the entries cannot be None since in wait_for_nodes we have
113 // waited for all of them to not be None.
114 return Err(anyhow!("previous map entry cannot have None outer option."));
115 }
116 }
117 }
118 }
119
120 // Now compare it with current to make sure it is different.
121 match koid_before {
122 Some(koid_before) => match koid_before {
123 Some(koid_before) => match changed_or_new_node_entry {
124 Some(koid_after) => match koid_after {
125 Some(koid_after) => {
126 // The node had a koid in both current and previous. Ensure that the
127 // koid value is different.
128 if koid_before == koid_after {
129 return Err(anyhow!(
130 "koid should have changed for node '{}' after {}.",
131 key,
132 test_stage_name
133 ));
134 }
135 }
136 None => {
137 // The current node doesn't contain a koid.
138 // This can happen if the node is a composite parent, or if it is unbound.
139 continue;
140 }
141 },
142 None => {
143 // This is not possible as things are today.
144 // The values in the entries cannot be None since in wait_for_nodes we have
145 // waited for all of them to not be None.
146 return Err(anyhow!("changed_or_new_node entry cannot be None."));
147 }
148 },
149 None => {
150 // This node existed in a previous item, but it didn't contain a koid.
151 // This can happen if the node was a composite parent, or if it was unbound.
152 continue;
153 }
154 },
155 None => {
156 // This is a new node and it doesn't exist in previous.
157 continue;
158 }
159 }
160 }
161
162 Ok(())
163}