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}