| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::{anyhow, Result}; |
| use fidl_fuchsia_driver_development as fdd; |
| use futures::channel::mpsc; |
| use futures::StreamExt; |
| use std::collections::{HashMap, HashSet}; |
| |
| // Wait for the events from the |nodes| to be received. Updates the entries to be Some. |
| pub async fn wait_for_nodes( |
| nodes: &mut HashMap<String, Option<Option<u64>>>, |
| receiver: &mut mpsc::Receiver<(String, String)>, |
| ) -> Result<()> { |
| while nodes.values().any(|&x| x.is_none()) { |
| let (from_node, _) = receiver.next().await.ok_or(anyhow!("Receiver failed"))?; |
| if !nodes.contains_key(&from_node) { |
| return Err(anyhow!("Couldn't find node '{}' in 'nodes'.", from_node.to_string())); |
| } |
| nodes.entry(from_node).and_modify(|x| { |
| *x = Some(None); |
| }); |
| } |
| |
| Ok(()) |
| } |
| |
| // Validates the host koids given the device infos. |
| // Performs the following: |
| // - Stores the host koid for nodes in changed_or_new as those are expected to be new/changed. |
| // - Validate bound nodes are not in should_not_exist |
| // - Validate bound nodes have the same koid as the most recent item in previous. |
| // - Validate that items in changed_or_new have been changed from the most recent item in previous |
| // if they are not new. |
| pub async fn validate_host_koids( |
| test_stage_name: &str, |
| device_infos: Vec<fdd::NodeInfo>, |
| changed_or_new: &mut HashMap<String, Option<Option<u64>>>, |
| previous: Vec<&HashMap<String, Option<Option<u64>>>>, |
| should_not_exist: Option<&HashSet<String>>, |
| ) -> Result<()> { |
| for dev in &device_infos { |
| let key = dev.moniker.clone().unwrap().split(".").last().unwrap().to_string(); |
| |
| // Items in changed_or_new are expected to be different so just save that info and move on. |
| if changed_or_new.contains_key(&key) { |
| changed_or_new.entry(key).and_modify(|x| { |
| *x = Some(dev.driver_host_koid); |
| }); |
| |
| continue; |
| } |
| |
| // Skip comparison and should_not_exist check as the koid is not valid when its unbound. |
| if matches!(dev.bound_driver_url.as_deref(), Some("unbound")) { |
| continue; |
| } |
| |
| // Error if the item is in should not exist. |
| if let Some(should_not_exist_value) = &should_not_exist { |
| if should_not_exist_value.contains(&key) { |
| return Err(anyhow!( |
| "Found node that should not exist after {}: '{}'.", |
| test_stage_name, |
| key |
| )); |
| } |
| } |
| |
| // Go through the previous items (which should come in from most to least recent order) |
| // and make sure this matches the most recent instance. |
| for prev in &previous { |
| if let Some(prev_koid) = prev.get(&key) { |
| match prev_koid { |
| Some(prev_koid_value) => { |
| if *prev_koid_value != dev.driver_host_koid { |
| return Err(anyhow!( |
| "koid should not have changed for node '{}' after {}.", |
| key, |
| test_stage_name |
| )); |
| } |
| |
| break; |
| } |
| None => { |
| // This is not possible as things are today. |
| // The values in the entries cannot be None since in wait_for_nodes we have |
| // waited for all of them to not be None. |
| return Err(anyhow!("prev koid not available after.")); |
| } |
| } |
| } |
| } |
| } |
| |
| // Now we can make sure those items in changed_or_new are different than their most recent |
| // previous item. Skipping ones that are not seen in previous. |
| for (key, changed_or_new_node_entry) in changed_or_new { |
| // First find the most recent previous koid if one exists. |
| let mut koid_before: Option<&Option<u64>> = None; |
| for prev in &previous { |
| if let Some(prev_koid) = prev.get(key) { |
| match prev_koid { |
| Some(prev_koid_value) => { |
| koid_before = Some(prev_koid_value); |
| break; |
| } |
| None => { |
| // This is not possible as things are today. |
| // The values in the entries cannot be None since in wait_for_nodes we have |
| // waited for all of them to not be None. |
| return Err(anyhow!("previous map entry cannot have None outer option.")); |
| } |
| } |
| } |
| } |
| |
| // Now compare it with current to make sure it is different. |
| match koid_before { |
| Some(koid_before) => match koid_before { |
| Some(koid_before) => match changed_or_new_node_entry { |
| Some(koid_after) => match koid_after { |
| Some(koid_after) => { |
| // The node had a koid in both current and previous. Ensure that the |
| // koid value is different. |
| if koid_before == koid_after { |
| return Err(anyhow!( |
| "koid should have changed for node '{}' after {}.", |
| key, |
| test_stage_name |
| )); |
| } |
| } |
| None => { |
| // The current node doesn't contain a koid. |
| // This can happen if the node is a composite parent, or if it is unbound. |
| continue; |
| } |
| }, |
| None => { |
| // This is not possible as things are today. |
| // The values in the entries cannot be None since in wait_for_nodes we have |
| // waited for all of them to not be None. |
| return Err(anyhow!("changed_or_new_node entry cannot be None.")); |
| } |
| }, |
| None => { |
| // This node existed in a previous item, but it didn't contain a koid. |
| // This can happen if the node was a composite parent, or if it was unbound. |
| continue; |
| } |
| }, |
| None => { |
| // This is a new node and it doesn't exist in previous. |
| continue; |
| } |
| } |
| } |
| |
| Ok(()) |
| } |