// Copyright (c) 2024 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. // // This program and the accompanying materials are made available under the // terms of the Apache Software License 2.0 which is available at // https://www.apache.org/licenses/LICENSE-2.0, or the MIT license // which is available at https://opensource.org/licenses/MIT. // // SPDX-License-Identifier: Apache-2.0 OR MIT #[generic_tests::define] mod node { use std::collections::{HashSet, VecDeque}; use std::sync::Barrier; use std::time::{Duration, Instant}; use iceoryx2::config::Config; use iceoryx2::node::{ NodeCleanupFailure, NodeCreationFailure, NodeId, NodeListFailure, NodeState, NodeView, }; use iceoryx2::prelude::*; use iceoryx2::service::Service; use iceoryx2_bb_posix::directory::Directory; use iceoryx2_bb_posix::system_configuration::SystemInfo; use iceoryx2_bb_system_types::path::*; use iceoryx2_bb_testing::watchdog::Watchdog; use iceoryx2_bb_testing::{assert_that, test_fail}; const TIMEOUT: Duration = Duration::from_millis(25); #[derive(Debug, Eq, PartialEq)] struct Details { name: NodeName, id: NodeId, config: Config, } impl Details { fn new(name: &NodeName, id: &NodeId, config: &Config) -> Self { Self { name: name.clone(), id: id.clone(), config: config.clone(), } } fn from_node(node: &Node) -> Self { Self::new(node.name(), node.id(), node.config()) } } fn assert_node_presence(node_details: &VecDeque
, config: &Config) { let mut node_list = vec![]; Node::::list(config, |node_state| { node_list.push(node_state); CallbackProgression::Continue }) .unwrap(); assert_that!(node_list, len node_details.len()); for node in node_list { let view = match node { NodeState::::Alive(ref view) => view as &dyn NodeView, NodeState::::Dead(ref view) => view as &dyn NodeView, NodeState::::Inaccessible(_) | NodeState::::Undefined(_) => { assert_that!(true, eq false); panic!(); } }; let details = view.details().as_ref().unwrap(); let triple = Details::new(details.name(), view.id(), details.config()); assert_that!( *node_details, contains triple ) } } fn generate_node_name(i: usize, prefix: &str) -> NodeName { NodeName::new(&(prefix.to_string() + &i.to_string())).unwrap() } #[test] fn node_without_name_can_be_created() { let sut = NodeBuilder::new().create::().unwrap(); assert_that!(*sut.name(), eq NodeName::new("").unwrap()); } #[test] fn node_with_name_can_be_created() { let node_name = NodeName::new("photons taste like chicken").unwrap(); let sut = NodeBuilder::new().name(&node_name).create::().unwrap(); assert_that!(*sut.name(), eq node_name); } #[test] fn multiple_nodes_with_the_same_name_can_be_created() { const NUMBER_OF_NODES: usize = 16; let node_name = NodeName::new("but what does an electron taste like?").unwrap(); let mut nodes = vec![]; for _ in 0..NUMBER_OF_NODES { nodes.push(NodeBuilder::new().name(&node_name).create::().unwrap()); } for node in nodes { assert_that!(*node.name(), eq node_name); } } #[test] fn without_custom_config_global_config_is_used() { let sut = NodeBuilder::new().create::().unwrap(); assert_that!(*sut.config(), eq * Config::global_config()); } #[test] fn nodes_can_be_listed() { const NUMBER_OF_NODES: usize = 16; let mut nodes = vec![]; let mut node_details = VecDeque::new(); for i in 0..NUMBER_OF_NODES { let node_name = generate_node_name(i, "give me a bit"); let node = NodeBuilder::new().name(&node_name).create::().unwrap(); node_details.push_back(Details::from_node(&node)); nodes.push(node); } assert_node_presence::(&node_details, Config::global_config()); } #[test] fn when_node_goes_out_of_scope_it_cleans_up() { const NUMBER_OF_NODES: usize = 16; let mut nodes = vec![]; let mut node_details = VecDeque::new(); for i in 0..NUMBER_OF_NODES { let node_name = generate_node_name(i, "gravity should be illegal"); let node = NodeBuilder::new().name(&node_name).create::().unwrap(); node_details.push_back(Details::from_node(&node)); nodes.push(node); } for _ in 0..NUMBER_OF_NODES { nodes.pop(); node_details.pop_back(); assert_node_presence::(&node_details, Config::global_config()); } } #[test] fn id_is_unique() { const NUMBER_OF_NODES: usize = 16; let mut nodes = vec![]; let mut node_ids = HashSet::new(); for i in 0..NUMBER_OF_NODES { let node_name = generate_node_name( i, "its a bird, its a plane, no its the mountain goat jumping through the code", ); nodes.push(NodeBuilder::new().name(&node_name).create::().unwrap()); assert_that!(node_ids.insert(nodes.last().unwrap().id().clone()), eq true); } } #[test] fn nodes_with_disjunct_config_are_separated() { const NUMBER_OF_NODES: usize = 16; let mut nodes_1 = VecDeque::new(); let mut node_details_1 = VecDeque::new(); let mut nodes_2 = VecDeque::new(); let mut node_details_2 = VecDeque::new(); let mut config = Config::default(); config.global.node.directory = Path::new(b"node2").unwrap(); for i in 0..NUMBER_OF_NODES { let node_name_1 = generate_node_name(i, "gravity should be illegal"); let node_name_2 = generate_node_name(i, "i like to name it name it"); let node_1 = NodeBuilder::new().name(&node_name_1).create::().unwrap(); let node_2 = NodeBuilder::new() .config(&config) .name(&node_name_2) .create::() .unwrap(); node_details_1.push_back(Details::from_node(&node_1)); node_details_2.push_back(Details::from_node(&node_2)); nodes_1.push_back(node_1); nodes_2.push_back(node_2); } for _ in 0..NUMBER_OF_NODES { nodes_1.pop_back(); nodes_2.pop_front(); node_details_1.pop_back(); node_details_2.pop_front(); assert_node_presence::(&node_details_1, Config::global_config()); assert_node_presence::(&node_details_2, &config); } let mut path = *config.global.root_path(); path.add_path_entry(&config.global.node.directory).unwrap(); let _ = Directory::remove(&path); } #[test] fn node_creation_failure_display_works() { assert_that!( format!("{}", NodeCreationFailure::InsufficientPermissions), eq "NodeCreationFailure::InsufficientPermissions"); assert_that!( format!("{}", NodeCreationFailure::InternalError), eq "NodeCreationFailure::InternalError"); } #[test] fn node_list_failure_display_works() { assert_that!( format!("{}", NodeListFailure::InsufficientPermissions), eq "NodeListFailure::InsufficientPermissions"); assert_that!( format!("{}", NodeListFailure::Interrupt), eq "NodeListFailure::Interrupt"); assert_that!( format!("{}", NodeListFailure::InternalError), eq "NodeListFailure::InternalError"); } #[test] fn node_cleanup_failure_display_works() { assert_that!( format!("{}", NodeCleanupFailure::InsufficientPermissions), eq "NodeCleanupFailure::InsufficientPermissions"); assert_that!( format!("{}", NodeCleanupFailure::Interrupt), eq "NodeCleanupFailure::Interrupt"); assert_that!( format!("{}", NodeCleanupFailure::InternalError), eq "NodeCleanupFailure::InternalError"); } #[test] fn concurrent_node_creation_and_listing_works() { let _watch_dog = Watchdog::new_with_timeout(Duration::from_secs(120)); let number_of_creators = (SystemInfo::NumberOfCpuCores.value()).clamp(2, 1024); const NUMBER_OF_ITERATIONS: usize = 100; let barrier = Barrier::new(number_of_creators); let mut config = Config::global_config().clone(); config.global.node.cleanup_dead_nodes_on_creation = false; config.global.node.cleanup_dead_nodes_on_destruction = false; std::thread::scope(|s| { let mut threads = vec![]; for _ in 0..number_of_creators { threads.push(s.spawn(|| { barrier.wait(); for _ in 0..NUMBER_OF_ITERATIONS { let node = NodeBuilder::new().config(&config).create::().unwrap(); let mut found_self = false; let result = Node::::list(node.config(), |node_state| { match node_state { NodeState::Alive(view) => { if view.id() == node.id() { found_self = true; } } NodeState::Dead(view) => { if view.id() == node.id() { found_self = true; } } NodeState::Inaccessible(node_id) => { if node_id == *node.id() { found_self = true; } } NodeState::Undefined(_) => { assert_that!(true, eq false); } }; CallbackProgression::Continue }); assert_that!(found_self, eq true); assert_that!(result, is_ok); } })); } for thread in threads { thread.join().unwrap(); } }); } #[test] fn node_listing_stops_when_callback_progression_signals_stop() { let node_1 = NodeBuilder::new().create::().unwrap(); let _node_2 = NodeBuilder::new().create::().unwrap(); let mut node_counter = 0; let result = Node::::list(node_1.config(), |_| { node_counter += 1; CallbackProgression::Stop }); assert_that!(result, is_ok); assert_that!(node_counter, eq 1); } #[test] fn i_am_not_dead() { let node = NodeBuilder::new().create::().unwrap(); let mut nodes = vec![]; let result = Node::::list(node.config(), |node_state| { nodes.push(node_state); CallbackProgression::Continue }); assert_that!(result, is_ok); assert_that!(nodes, len 1); if let NodeState::Alive(node_view) = &nodes[0] { assert_that!(node_view.id(), eq node.id()); } else { test_fail!("Process internal nodes shall be always detected as alive."); } } #[test] fn node_wait_returns_tick_on_timeout() { let node = NodeBuilder::new().create::().unwrap(); let start = Instant::now(); let event = node.wait(TIMEOUT); assert_that!(start.elapsed(), time_at_least TIMEOUT); assert_that!(event, eq NodeEvent::Tick); } #[instantiate_tests()] mod ipc {} #[instantiate_tests()] mod local {} }