diff --git a/src/ip/address/tests/address.rs b/src/ip/address/tests/address.rs index 609ec6e..6d9bbda 100644 --- a/src/ip/address/tests/address.rs +++ b/src/ip/address/tests/address.rs @@ -1,132 +1,137 @@ // SPDX-License-Identifier: MIT -use crate::tests::{assert_alias_output, exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; -#[test] -fn test_address_show() { - let dummy_name = "atest-dummy1"; +const DUMMY_NAME: &str = "test-dummy"; - with_dummy_iface(dummy_name, || { - let expected_output = exec_cmd(&["ip", "address", "show", dummy_name]); - let our_output = ip_rs_exec_cmd(&["address", "show", dummy_name]); +fn with_dummy_iface(test: T) +where + T: FnOnce(&NetnsGuard), +{ + with_netns(|ns| { + ns.exec_cmd(&["ip", "link", "add", DUMMY_NAME, "type", "dummy"]); + ns.exec_cmd(&["ip", "link", "set", DUMMY_NAME, "up"]); + + ns.exec_cmd(&[ + "ip", + "addr", + "add", + "192.168.1.1/24", + "dev", + DUMMY_NAME, + ]); + ns.exec_cmd(&[ + "ip", + "addr", + "add", + "192.168.1.2/24", + "dev", + DUMMY_NAME, + ]); + ns.exec_cmd(&["ip", "addr", "add", "ff::ab:cd/64", "dev", DUMMY_NAME]); + ns.exec_cmd(&[ + "ip", + "addr", + "add", + "2001:db8:beef::1/64", + "dev", + DUMMY_NAME, + "valid_lft", + "21384", + "preferred_lft", + "21384", + "scope", + "global", + "mngtmpaddr", + "proto", + "kernel_ra", + ]); + ns.exec_cmd(&[ + "ip", + "addr", + "add", + "2001:db8:beef::2/64", + "dev", + DUMMY_NAME, + "valid_lft", + "21381", + "preferred_lft", + "21381", + "scope", + "global", + "home", + "proto", + "kernel_ra", + ]); + + // Wait 2 seconds for interface to be up and addresses to be assigned + std::thread::sleep(std::time::Duration::from_secs(2)); + + test(ns); + }); +} - pretty_assertions::assert_eq!(expected_output, our_output); +#[test] +fn test_address_show() { + with_dummy_iface(|ns| { + ns.assert_eq_output(&["address", "show", DUMMY_NAME]); }); } #[test] fn test_address_detailed_show() { - let dummy_name = "atest-dummy2"; - - with_dummy_iface(dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "address", "show", dummy_name]); - let our_output = ip_rs_exec_cmd(&["-d", "address", "show", dummy_name]); - - pretty_assertions::assert_eq!(expected_output, our_output); + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-d", "address", "show", DUMMY_NAME]); }); } #[test] fn test_address_show_json() { - let dummy_name = "atest-dummy3"; - - with_dummy_iface(dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-j", "address", "show", dummy_name]); - let our_output = ip_rs_exec_cmd(&["-j", "address", "show", dummy_name]); - - pretty_assertions::assert_eq!(expected_output, our_output); + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-j", "address", "show", DUMMY_NAME]); }); } #[test] fn test_address_detailed_show_json() { - let dummy_name = "atest-dummy4"; - - with_dummy_iface(dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "address", "show", dummy_name]); - let our_output = - ip_rs_exec_cmd(&["-d", "-j", "address", "show", dummy_name]); - - pretty_assertions::assert_eq!(expected_output, our_output); + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "address", "show", DUMMY_NAME]); }); } #[test] fn test_address_alias_a_s() { - assert_alias_output(&["address", "show", "lo"], &["a", "s", "lo"]); + with_netns(|ns| { + ns.assert_alias_output(&["address", "show", "lo"], &["a", "s", "lo"]); + }); } #[test] fn test_address_alias_addr_show() { - assert_alias_output(&["address", "show", "lo"], &["addr", "show", "lo"]); + with_netns(|ns| { + ns.assert_alias_output( + &["address", "show", "lo"], + &["addr", "show", "lo"], + ); + }); } #[test] fn test_address_alias_address_s() { - assert_alias_output(&["address", "show", "lo"], &["address", "s", "lo"]); + with_netns(|ns| { + ns.assert_alias_output( + &["address", "show", "lo"], + &["address", "s", "lo"], + ); + }); } #[test] fn test_address_alias_add_ls() { - assert_alias_output(&["address", "show", "lo"], &["add", "ls", "lo"]); -} - -fn with_dummy_iface(dummy_name: &str, test: T) -where - T: FnOnce() + std::panic::UnwindSafe, -{ - exec_cmd(&["ip", "link", "add", dummy_name, "type", "dummy"]); - exec_cmd(&["ip", "link", "set", dummy_name, "up"]); - - exec_cmd(&["ip", "addr", "add", "192.168.1.1/24", "dev", dummy_name]); - exec_cmd(&["ip", "addr", "add", "192.168.1.2/24", "dev", dummy_name]); - exec_cmd(&["ip", "addr", "add", "ff::ab:cd/64", "dev", dummy_name]); - exec_cmd(&[ - "ip", - "addr", - "add", - "2001:db8:beef::1/64", - "dev", - dummy_name, - "valid_lft", - "21384", - "preferred_lft", - "21384", - "scope", - "global", - "mngtmpaddr", - "proto", - "kernel_ra", - ]); - exec_cmd(&[ - "ip", - "addr", - "add", - "2001:db8:beef::2/64", - "dev", - dummy_name, - "valid_lft", - "21381", - "preferred_lft", - "21381", - "scope", - "global", - "home", - "proto", - "kernel_ra", - ]); - - // Wait 2 seconds for interface to be up and addresses to be assigned - std::thread::sleep(std::time::Duration::from_secs(2)); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + ns.assert_alias_output( + &["address", "show", "lo"], + &["add", "ls", "lo"], + ); }); - - // clean up - exec_cmd(&["ip", "link", "del", dummy_name]); - assert!(result.is_ok()) } diff --git a/src/ip/link/ifaces/vxlan.rs b/src/ip/link/ifaces/vxlan.rs index 2ab3d8f..d30573a 100644 --- a/src/ip/link/ifaces/vxlan.rs +++ b/src/ip/link/ifaces/vxlan.rs @@ -179,107 +179,107 @@ impl From<&[InfoVxlan]> for CliLinkInfoDataVxlan { impl std::fmt::Display for CliLinkInfoDataVxlan { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "id {} ", self.id)?; + write!(f, "id {}", self.id)?; if let Some(v) = self.group { - write!(f, "group {v} ")?; + write!(f, " group {v}")?; } if let Some(v) = self.group6 { - write!(f, "group6 {v} ")?; + write!(f, " group6 {v}")?; } if let Some(v) = self.local { - write!(f, "local {v} ")?; + write!(f, " local {v}")?; } if let Some(v) = self.local6 { - write!(f, "local6 {v} ")?; + write!(f, " local6 {v}")?; } if let Some(v) = &self.link_name { - write!(f, "dev {v} ")?; + write!(f, " dev {v}")?; } else if let Some(v) = self.link { - write!(f, "dev if{v} ")?; + write!(f, " dev if{v}")?; } if let Some((low, high)) = self.port_range { - write!(f, "srcport {low} {high} ")?; + write!(f, " srcport {low} {high}")?; } if self.port > 0 { - write!(f, "dstport {} ", self.port)?; + write!(f, " dstport {}", self.port)?; } if self.ttl == 0 { if self.ttl_inherit { - write!(f, "ttl inherit ")?; + write!(f, " ttl inherit")?; } else { - write!(f, "ttl auto ")?; + write!(f, " ttl auto")?; } } else { - write!(f, "ttl {} ", self.ttl)?; + write!(f, " ttl {}", self.ttl)?; } if self.tos > 0 { - write!(f, "tos {} ", self.tos)?; + write!(f, " tos {}", self.tos)?; } if self.label > 0 { - write!(f, "label 0x{:x} ", self.label)?; + write!(f, " label 0x{:x}", self.label)?; } if !self.learning { - write!(f, "nolearning ")?; + write!(f, " nolearning")?; } if let Some(v) = self.df.as_ref() && v != "unset" { - write!(f, "df {v} ")?; + write!(f, " df {v}")?; } - write!(f, "ageing {} ", self.ageing)?; + write!(f, " ageing {}", self.ageing)?; if self.limit > 0 { - write!(f, "limit {} ", self.limit)?; + write!(f, " limit {}", self.limit)?; } if self.proxy { - write!(f, "proxy ")?; + write!(f, " proxy")?; } if self.rsc { - write!(f, "rsc ")?; + write!(f, " rsc")?; } if self.l2miss { - write!(f, "l2miss ")?; + write!(f, " l2miss")?; } if self.l3miss { - write!(f, "l3miss ")?; + write!(f, " l3miss")?; } if self.collect_metadata { - write!(f, "collect_md ")?; + write!(f, " collect_md")?; } if !self.udp_csum { - write!(f, "noudpcsum ")?; + write!(f, " noudpcsum")?; } if self.udp_zero_csum6_tx { - write!(f, "udp6zerocsumtx ")?; + write!(f, " udp6zerocsumtx")?; } if self.udp_zero_csum6_rx { - write!(f, "udp6zerocsumrx ")?; + write!(f, " udp6zerocsumrx")?; } if self.remcsum_tx { - write!(f, "remcsumtx ")?; + write!(f, " remcsumtx")?; } if self.remcsum_rx { - write!(f, "remcsumrx ")?; + write!(f, " remcsumrx")?; } if self.gbp { - write!(f, "gbp ")?; + write!(f, " gbp")?; } if self.gpe { - write!(f, "gpe ")?; + write!(f, " gpe")?; } if self.remcsum_no_partial { - write!(f, "remcsum_nopartial ")?; + write!(f, " remcsum_nopartial")?; } if self.vnifilter { - write!(f, "vnifilter ")?; + write!(f, " vnifilter")?; } if !self.localbypass { - write!(f, "nolocalbypass ")?; + write!(f, " nolocalbypass")?; } if self.label_policy > 0 { - write!(f, "label_policy {} ", self.label_policy)?; + write!(f, " label_policy {}", self.label_policy)?; } if self.reserved_bits > 0 { - write!(f, "reserved_bits 0x{:x}", self.reserved_bits)?; + write!(f, " reserved_bits 0x{:x}", self.reserved_bits)?; } Ok(()) } diff --git a/src/ip/link/show.rs b/src/ip/link/show.rs index b83108a..cec3cf7 100644 --- a/src/ip/link/show.rs +++ b/src/ip/link/show.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -use std::{collections::HashMap, os::fd::AsRawFd}; +use std::{collections::HashMap, io::ErrorKind, os::fd::AsRawFd}; use futures_util::stream::{StreamExt, TryStreamExt}; use iproute_rs::{ @@ -331,7 +331,15 @@ async fn resolve_netns_names( for netns in netnses { let netns = netns?; let name = netns.file_name().into_string().unwrap_or_default(); - let file = std::fs::File::open(netns.path())?; + let file_res = std::fs::File::open(netns.path()); + // Skip netnses that are not found (might be deleted) + if file_res + .as_ref() + .is_err_and(|e| e.kind() == ErrorKind::NotFound) + { + continue; + } + let file = file_res?; if let Some(id) = get_netns_id_from_fd(&mut handle, file.as_raw_fd() as u32).await diff --git a/src/ip/link/tests/bond.rs b/src/ip/link/tests/bond.rs index d4436ff..2829045 100644 --- a/src/ip/link/tests/bond.rs +++ b/src/ip/link/tests/bond.rs @@ -1,91 +1,52 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const BOND_NAME: &str = "test-bond"; +const DUMMY_NAME: &str = "test-bnd-dummy"; #[test] fn test_link_detailed_show_bond() { - let bond_name = "test-bnd0"; - let dummy_name = "test-bnd-dummy0"; - - with_bond_iface(bond_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", bond_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", bond_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_bond_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", BOND_NAME]); + }); } #[test] fn test_link_detailed_show_json_bond() { - let bond_name = "test-bond1"; - let dummy_name = "test-bnd-dummy1"; - with_bond_iface(bond_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", bond_name]); - - let our_output = - ip_rs_exec_cmd(&["-d", "-j", "link", "show", bond_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_bond_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "link", "show", BOND_NAME]); + }); } #[test] fn test_link_detailed_show_bond_port() { - let bond_name = "test-bond2"; - let dummy_name = "test-bnd-dummy2"; - - with_bond_iface(bond_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", dummy_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", dummy_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_bond_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", DUMMY_NAME]); + }); } #[test] fn test_link_detailed_show_json_bond_port() { - let bond_name = "test-bond3"; - let dummy_name = "test-bnd-dummy3"; - with_bond_iface(bond_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", dummy_name]); - - let our_output = - ip_rs_exec_cmd(&["-d", "-j", "link", "show", dummy_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); + with_bond_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "link", "show", DUMMY_NAME]); }) } -fn with_bond_iface(bond_name: &str, dummy_name: &str, test: T) +fn with_bond_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - // create bridge using dummy interface - exec_cmd(&["ip", "link", "add", dummy_name, "type", "dummy"]); - exec_cmd(&["ip", "link", "add", bond_name, "type", "bond"]); - exec_cmd(&["ip", "link", "set", "dev", dummy_name, "master", bond_name]); + with_netns(|ns| { + ns.exec_cmd(&["ip", "link", "add", DUMMY_NAME, "type", "dummy"]); + ns.exec_cmd(&["ip", "link", "add", BOND_NAME, "type", "bond"]); + ns.exec_cmd(&[ + "ip", "link", "set", "dev", DUMMY_NAME, "master", BOND_NAME, + ]); - exec_cmd(&["ip", "link", "set", dummy_name, "up"]); - exec_cmd(&["ip", "link", "set", bond_name, "up"]); + ns.exec_cmd(&["ip", "link", "set", DUMMY_NAME, "up"]); + ns.exec_cmd(&["ip", "link", "set", BOND_NAME, "up"]); - // Wait 1 second for bridge ID to be stable - std::thread::sleep(std::time::Duration::from_secs(1)); - - let result = std::panic::catch_unwind(|| { - test(); - }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", dummy_name]); - let _ = exec_cmd(&["ip", "link", "del", bond_name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } + test(ns); + }) } diff --git a/src/ip/link/tests/bridge.rs b/src/ip/link/tests/bridge.rs index 919adb9..280ba56 100644 --- a/src/ip/link/tests/bridge.rs +++ b/src/ip/link/tests/bridge.rs @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const BRIDGE_NAME: &str = "test-br"; +const DUMMY_NAME: &str = "test-dummy"; /// Normalize timer values in output to avoid test flakiness /// Timer values can vary slightly between consecutive calls due to kernel /// timing -fn normalize_timers(output: &str) -> String { +fn normalize_timers(output: String) -> String { let timer_names = [ "hello_timer", "tcn_timer", @@ -16,7 +19,7 @@ fn normalize_timers(output: &str) -> String { "forward_delay_timer", ]; - let mut result = output.to_string(); + let mut result = output; for timer_name in timer_names { // Find and replace timer values like "gc_timer 0.05" with "gc_timer // 0.00" @@ -53,7 +56,7 @@ fn normalize_timers(output: &str) -> String { } /// Normalize timer values in JSON output to avoid test flakiness -fn normalize_timers_json(output: &str) -> String { +fn normalize_timers_json(output: String) -> String { let timer_names = [ "hello_timer", "tcn_timer", @@ -64,7 +67,7 @@ fn normalize_timers_json(output: &str) -> String { "forward_delay_timer", ]; - let mut result = output.to_string(); + let mut result = output; for timer_name in timer_names { // Find and replace JSON timer values like "\"gc_timer\":5" or // "\"gc_timer\":0.05" with "\"gc_timer\":0" @@ -97,109 +100,73 @@ fn normalize_timers_json(output: &str) -> String { #[test] fn test_link_detailed_show_bridge() { - let br_name = "test-br0"; - let dummy_name = "test-dummy0"; - - with_bridge_iface(br_name, dummy_name, || { - let expected_output = exec_cmd(&["ip", "-d", "link", "show", br_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", br_name]); - - pretty_assertions::assert_eq!( - normalize_timers(&expected_output), - normalize_timers(&our_output) + with_bridge_iface(|ns| { + ns.assert_eq_output_map( + &["-d", "link", "show", BRIDGE_NAME], + normalize_timers, ); - }) + }); } #[test] fn test_link_detailed_show_json_bridge() { - let br_name = "test-br1"; - let dummy_name = "test-dummy1"; - with_bridge_iface(br_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", br_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "-j", "link", "show", br_name]); - - pretty_assertions::assert_eq!( - normalize_timers_json(&expected_output), - normalize_timers_json(&our_output) + with_bridge_iface(|ns| { + ns.assert_eq_output_map( + &["-d", "-j", "link", "show", BRIDGE_NAME], + normalize_timers_json, ); - }) + }); } #[test] fn test_link_detailed_show_bridge_port() { - let br_name = "test-br2"; - let dummy_name = "test-dummy2"; - - with_bridge_iface(br_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", dummy_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", dummy_name]); - - pretty_assertions::assert_eq!( - normalize_timers(&expected_output), - normalize_timers(&our_output) + with_bridge_iface(|ns| { + ns.assert_eq_output_map( + &["-d", "link", "show", DUMMY_NAME], + normalize_timers, ); - }) + }); } #[test] fn test_link_detailed_show_json_bridge_port() { - let br_name = "test-br3"; - let dummy_name = "test-dummy3"; - with_bridge_iface(br_name, dummy_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", dummy_name]); - - let our_output = - ip_rs_exec_cmd(&["-d", "-j", "link", "show", dummy_name]); - - pretty_assertions::assert_eq!( - normalize_timers_json(&expected_output), - normalize_timers_json(&our_output) + with_bridge_iface(|ns| { + ns.assert_eq_output_map( + &["-d", "-j", "link", "show", DUMMY_NAME], + normalize_timers_json, ); - }) + }); } -/// Since all test cases are running simultaneously, please make sure `br_name` -/// and `dummy_name` are unique among tests. -fn with_bridge_iface(br_name: &str, dummy_name: &str, test: T) +fn with_bridge_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - // create bridge using dummy interface - exec_cmd(&["ip", "link", "add", dummy_name, "type", "dummy"]); - exec_cmd(&[ - "ip", - "link", - "add", - br_name, - "type", - "bridge", - "stp_state", - "0", - ]); - exec_cmd(&["ip", "link", "set", "dev", dummy_name, "master", br_name]); - - exec_cmd(&["ip", "link", "set", dummy_name, "up"]); - exec_cmd(&["ip", "link", "set", br_name, "up"]); - - // Wait 1 second for bridge ID to be stable - std::thread::sleep(std::time::Duration::from_secs(1)); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + ns.exec_cmd(&["ip", "link", "add", DUMMY_NAME, "type", "dummy"]); + ns.exec_cmd(&[ + "ip", + "link", + "add", + BRIDGE_NAME, + "type", + "bridge", + "stp_state", + "0", + ]); + ns.exec_cmd(&[ + "ip", + "link", + "set", + "dev", + DUMMY_NAME, + "master", + BRIDGE_NAME, + ]); + + ns.exec_cmd(&["ip", "link", "set", DUMMY_NAME, "up"]); + ns.exec_cmd(&["ip", "link", "set", BRIDGE_NAME, "up"]); + + test(ns); }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", dummy_name]); - let _ = exec_cmd(&["ip", "link", "del", br_name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/link/tests/color.rs b/src/ip/link/tests/color.rs index 0e1a1e4..ff15c16 100644 --- a/src/ip/link/tests/color.rs +++ b/src/ip/link/tests/color.rs @@ -1,38 +1,29 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::with_netns; const COLOR_CLEAR: &str = "\x1b[0m"; #[test] fn test_ip_link_show_color_always() { - let expected_output = exec_cmd(&["ip", "-c=always", "link", "show", "lo"]); - - let our_output = ip_rs_exec_cmd(&["-c=always", "link", "show", "lo"]); - - assert!(our_output.contains(COLOR_CLEAR)); - - assert_eq!(expected_output, our_output); + with_netns(|ns| { + let outputs = ns.assert_eq_output(&["-c=always", "link", "show", "lo"]); + assert!(outputs.actual.contains(COLOR_CLEAR)); + }); } #[test] fn test_ip_link_show_color_auto_without_terminal() { - let expected_output = exec_cmd(&["ip", "-c=auto", "link", "show", "lo"]); - - let our_output = ip_rs_exec_cmd(&["-c=auto", "link", "show", "lo"]); - - assert!(!our_output.contains(COLOR_CLEAR)); - - assert_eq!(expected_output, our_output); + with_netns(|ns| { + let outputs = ns.assert_eq_output(&["-c=auto", "link", "show", "lo"]); + assert!(!outputs.actual.contains(COLOR_CLEAR)); + }); } #[test] fn test_ip_link_show_color_never() { - let expected_output = exec_cmd(&["ip", "-c=never", "link", "show", "lo"]); - - let our_output = ip_rs_exec_cmd(&["-c=never", "link", "show", "lo"]); - - assert!(!our_output.contains(COLOR_CLEAR)); - - assert_eq!(expected_output, our_output); + with_netns(|ns| { + let outputs = ns.assert_eq_output(&["-c=never", "link", "show", "lo"]); + assert!(!outputs.actual.contains(COLOR_CLEAR)); + }); } diff --git a/src/ip/link/tests/dummy.rs b/src/ip/link/tests/dummy.rs index 6b0a605..33abdb8 100644 --- a/src/ip/link/tests/dummy.rs +++ b/src/ip/link/tests/dummy.rs @@ -1,83 +1,53 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const DUMMY_NAME: &str = "test-dummy"; #[test] fn test_link_show_dummy() { - let ifname = "tdmy0"; - - with_dummy_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_dummy_iface(|ns| { + ns.assert_eq_output(&["link", "show", DUMMY_NAME]); + }); } #[test] fn test_link_detailed_show_dummy() { - let ifname = "tdmy1"; - - with_dummy_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "-d", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", DUMMY_NAME]); + }); } #[test] fn test_link_show_dummy_json() { - let ifname = "tdmy2"; - - with_dummy_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-j", "link", "show", DUMMY_NAME]); + }); } #[test] fn test_link_detailed_show_dummy_json() { - let ifname = "tdmy3"; - - with_dummy_iface(ifname, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_dummy_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "link", "show", DUMMY_NAME]); + }); } -fn with_dummy_iface(name: &str, test: T) +fn with_dummy_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - ip_rs_exec_cmd(&[ - "link", - "add", - name, - "address", - "12:26:8a:bb:b4:2c", - "type", - "dummy", - ]); - exec_cmd(&["ip", "link", "set", name, "up"]); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + ns.ip_rs_exec_cmd(&[ + "link", + "add", + DUMMY_NAME, + "address", + "12:26:8a:bb:b4:2c", + "type", + "dummy", + ]); + ns.exec_cmd(&["ip", "link", "set", DUMMY_NAME, "up"]); + + test(ns); }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/link/tests/loopback.rs b/src/ip/link/tests/loopback.rs index 36adfd0..3687a5c 100644 --- a/src/ip/link/tests/loopback.rs +++ b/src/ip/link/tests/loopback.rs @@ -1,41 +1,45 @@ // SPDX-License-Identifier: MIT -use crate::tests::{assert_alias_output, exec_cmd, ip_rs_exec_cmd}; +use crate::tests::with_netns; #[test] fn test_link_show_lo() { - let expected_output = exec_cmd(&["ip", "link", "show", "lo"]); - - let our_output = ip_rs_exec_cmd(&["link", "show", "lo"]); - - pretty_assertions::assert_eq!(expected_output, our_output); + with_netns(|ns| { + ns.assert_eq_output(&["link", "show", "lo"]); + }); } #[test] fn test_link_show_lo_json() { - let expected_output = exec_cmd(&["ip", "-j", "link", "show", "lo"]); - - let our_output = ip_rs_exec_cmd(&["-j", "link", "show", "lo"]); - - pretty_assertions::assert_eq!(expected_output, our_output); + with_netns(|ns| { + ns.assert_eq_output(&["-j", "link", "show", "lo"]); + }); } #[test] fn test_link_alias_l_l() { - assert_alias_output(&["link", "show", "lo"], &["l", "l", "lo"]); + with_netns(|ns| { + ns.assert_alias_output(&["link", "show", "lo"], &["l", "l", "lo"]); + }); } #[test] fn test_link_alias_lin_show() { - assert_alias_output(&["link", "show", "lo"], &["lin", "show", "lo"]); + with_netns(|ns| { + ns.assert_alias_output(&["link", "show", "lo"], &["lin", "show", "lo"]); + }); } #[test] fn test_link_alias_link_ls() { - assert_alias_output(&["link", "show", "lo"], &["link", "ls", "lo"]); + with_netns(|ns| { + ns.assert_alias_output(&["link", "show", "lo"], &["link", "ls", "lo"]); + }); } #[test] fn test_link_alias_li_l() { - assert_alias_output(&["link", "show", "lo"], &["li", "l", "lo"]); + with_netns(|ns| { + ns.assert_alias_output(&["link", "show", "lo"], &["li", "l", "lo"]); + }); } diff --git a/src/ip/link/tests/nlmon.rs b/src/ip/link/tests/nlmon.rs index c39810e..79eb996 100644 --- a/src/ip/link/tests/nlmon.rs +++ b/src/ip/link/tests/nlmon.rs @@ -1,75 +1,45 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const NLMON_NAME: &str = "test-nlmon"; #[test] fn test_link_show_nlmon() { - let ifname = "tnlm0"; - - with_nlmon_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_nlmon_iface(|ns| { + ns.assert_eq_output(&["link", "show", NLMON_NAME]); + }); } #[test] fn test_link_detailed_show_nlmon() { - let ifname = "tnlm1"; - - with_nlmon_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "-d", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_nlmon_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", NLMON_NAME]); + }); } #[test] fn test_link_show_nlmon_json() { - let ifname = "tnlm2"; - - with_nlmon_iface(ifname, || { - let expected_output = exec_cmd(&["ip", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_nlmon_iface(|ns| { + ns.assert_eq_output(&["-j", "link", "show", NLMON_NAME]); + }); } #[test] fn test_link_detailed_show_nlmon_json() { - let ifname = "tnlm3"; - - with_nlmon_iface(ifname, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_nlmon_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "link", "show", NLMON_NAME]); + }); } -fn with_nlmon_iface(name: &str, test: T) +fn with_nlmon_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - ip_rs_exec_cmd(&["link", "add", name, "type", "nlmon"]); - exec_cmd(&["ip", "link", "set", name, "up"]); + with_netns(|ns| { + ns.ip_rs_exec_cmd(&["link", "add", NLMON_NAME, "type", "nlmon"]); + ns.exec_cmd(&["ip", "link", "set", NLMON_NAME, "up"]); - let result = std::panic::catch_unwind(|| { - test(); + test(ns); }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/link/tests/veth.rs b/src/ip/link/tests/veth.rs index 520a95f..831a016 100644 --- a/src/ip/link/tests/veth.rs +++ b/src/ip/link/tests/veth.rs @@ -1,80 +1,55 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const VETH_NAME: &str = "test-veth"; +const VETH_PEER_NAME: &str = "test-veth-peer"; #[test] fn test_link_show_veth() { - let ifname = "tveth0"; - let peer = "tveth0_peer"; - - with_veth_iface(ifname, peer, || { - let expected_output = exec_cmd(&["ip", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_veth_iface(|ns| { + ns.assert_eq_output(&["link", "show", VETH_NAME]); + }); } #[test] fn test_link_detailed_show_veth() { - let ifname = "tveth1"; - let peer = "tveth1_peer"; - - with_veth_iface(ifname, peer, || { - let expected_output = exec_cmd(&["ip", "-d", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_veth_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", VETH_NAME]); + }); } #[test] fn test_link_show_veth_json() { - let ifname = "tveth2"; - let peer = "tveth2_peer"; - - with_veth_iface(ifname, peer, || { - let expected_output = exec_cmd(&["ip", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_veth_iface(|ns| { + ns.assert_eq_output(&["-j", "link", "show", VETH_NAME]); + }); } #[test] fn test_link_detailed_show_veth_json() { - let ifname = "tveth3"; - let peer = "tveth3_peer"; - - with_veth_iface(ifname, peer, || { - let expected_output = - exec_cmd(&["ip", "-d", "-j", "link", "show", ifname]); - - let our_output = ip_rs_exec_cmd(&["-d", "-j", "link", "show", ifname]); - - pretty_assertions::assert_eq!(expected_output, our_output); - }) + with_veth_iface(|ns| { + ns.assert_eq_output(&["-d", "-j", "link", "show", VETH_NAME]); + }); } -fn with_veth_iface(name: &str, peer: &str, test: T) +fn with_veth_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - ip_rs_exec_cmd(&["link", "add", name, "type", "veth", "peer", peer]); - exec_cmd(&["ip", "link", "set", name, "up"]); - exec_cmd(&["ip", "link", "set", peer, "up"]); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + ns.ip_rs_exec_cmd(&[ + "link", + "add", + VETH_NAME, + "type", + "veth", + "peer", + VETH_PEER_NAME, + ]); + ns.exec_cmd(&["ip", "link", "set", VETH_NAME, "up"]); + ns.exec_cmd(&["ip", "link", "set", VETH_PEER_NAME, "up"]); + + test(ns); }); - - // clean up (deleting veth removes both ends) - let _ = exec_cmd(&["ip", "link", "del", name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/link/tests/vlan.rs b/src/ip/link/tests/vlan.rs index cee4cd8..7596896 100644 --- a/src/ip/link/tests/vlan.rs +++ b/src/ip/link/tests/vlan.rs @@ -1,216 +1,114 @@ // SPDX-License-Identifier: MIT -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const VLAN_NAME: &str = "test-vlan"; #[test] fn test_link_show_vlan() { - let vlan_name = "tvlan20"; - - with_vlan_iface(vlan_name, &[], || { - let expected_output = exec_cmd(&["ip", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["link", "show", vlan_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&[], |ns| { + ns.assert_eq_output(&["link", "show", VLAN_NAME]); + }); } #[test] fn test_link_detailed_show_vlan() { - let vlan_name = "tvlan10"; - - with_vlan_iface(vlan_name, &[], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&[], |ns| { + ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + }); } #[test] fn test_vlan_protocol() { - let vlan_name = "tvlan_proto"; - - with_vlan_iface(vlan_name, &["protocol", "802.1ad"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("protocol 802.1ad")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["protocol", "802.1ad"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("protocol 802.1ad")); + }); } #[test] fn test_vlan_reorder_hdr_on() { - let vlan_name = "tvlan_rh_on"; - - with_vlan_iface(vlan_name, &["reorder_hdr", "on"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("REORDER_HDR")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["reorder_hdr", "on"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("REORDER_HDR")); + }); } #[test] fn test_vlan_reorder_hdr_off() { - let vlan_name = "tvlan_rh_off"; - - with_vlan_iface(vlan_name, &["reorder_hdr", "off"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(!expected_output.contains("REORDER_HDR")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["reorder_hdr", "off"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(!outputs.expected.contains("REORDER_HDR")); + }); } #[test] fn test_vlan_gvrp_on() { - let vlan_name = "tvlan_gvrp_on"; - - with_vlan_iface(vlan_name, &["gvrp", "on"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("GVRP")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["gvrp", "on"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("GVRP")); + }); } #[test] fn test_vlan_gvrp_off() { - let vlan_name = "tvlan_gvrp_off"; - - with_vlan_iface(vlan_name, &["gvrp", "off"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(!expected_output.contains("GVRP")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["gvrp", "off"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(!outputs.expected.contains("GVRP")); + }); } #[test] fn test_vlan_mvrp_on() { - let vlan_name = "tvlan_mvrp_on"; - - with_vlan_iface(vlan_name, &["mvrp", "on"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("MVRP")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["mvrp", "on"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("MVRP")); + }); } #[test] fn test_vlan_mvrp_off() { - let vlan_name = "tvlan_mvrp_off"; - - with_vlan_iface(vlan_name, &["mvrp", "off"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(!expected_output.contains("MVRP")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["mvrp", "off"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(!outputs.expected.contains("MVRP")); + }); } #[test] fn test_vlan_loose_binding_on() { - let vlan_name = "tvlan_lb_on"; - - with_vlan_iface(vlan_name, &["loose_binding", "on"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("LOOSE_BINDING")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["loose_binding", "on"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("LOOSE_BINDING")); + }); } #[test] fn test_vlan_loose_binding_off() { - let vlan_name = "tvlan_lb_off"; - - with_vlan_iface(vlan_name, &["loose_binding", "off"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(!expected_output.contains("LOOSE_BINDING")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["loose_binding", "off"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(!outputs.expected.contains("LOOSE_BINDING")); + }); } #[test] fn test_vlan_bridge_binding_on() { - let vlan_name = "tvlan_bb_on"; - - with_vlan_iface(vlan_name, &["bridge_binding", "on"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("BRIDGE_BINDING")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["bridge_binding", "on"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(outputs.expected.contains("BRIDGE_BINDING")); + }); } #[test] fn test_vlan_bridge_binding_off() { - let vlan_name = "tvlan_bb_off"; - - with_vlan_iface(vlan_name, &["bridge_binding", "off"], || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(!expected_output.contains("BRIDGE_BINDING")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vlan_iface(&["bridge_binding", "off"], |ns| { + let outputs = ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + assert!(!outputs.expected.contains("BRIDGE_BINDING")); + }); } #[test] fn test_vlan_all_flags_on() { - let vlan_name = "tvn_all_flg"; - with_vlan_iface( - vlan_name, &[ "protocol", "802.1ad", @@ -223,68 +121,56 @@ fn test_vlan_all_flags_on() { "bridge_binding", "on", ], - || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vlan_name]); - - assert!(expected_output.contains("protocol 802.1ad")); - assert!(expected_output.contains("REORDER_HDR")); - assert!(expected_output.contains("GVRP")); - assert!(expected_output.contains("LOOSE_BINDING")); - assert!(expected_output.contains("BRIDGE_BINDING")); - - pretty_assertions::assert_eq!(&expected_output, &our_output); + |ns| { + let outputs = + ns.assert_eq_output(&["-d", "link", "show", VLAN_NAME]); + + assert!(outputs.expected.contains("protocol 802.1ad")); + assert!(outputs.expected.contains("REORDER_HDR")); + assert!(outputs.expected.contains("GVRP")); + assert!(outputs.expected.contains("LOOSE_BINDING")); + assert!(outputs.expected.contains("BRIDGE_BINDING")); }, - ) + ); } -fn with_vlan_iface(vlan_name: &str, opts: &[&str], test: T) +fn with_vlan_iface(opts: &[&str], test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - let parent_name = format!("p{vlan_name}"); - - // create parent dummy interface using ip-rs - ip_rs_exec_cmd(&[ - "link", - "add", - &parent_name, - "address", - "0e:d1:49:08:27:84", - "type", - "dummy", - ]); - exec_cmd(&["ip", "link", "set", &parent_name, "up"]); - - let mut args = vec![ - "link", - "add", - "link", - &parent_name, - "name", - vlan_name, - "type", - "vlan", - "id", - "100", - ]; - - args.extend_from_slice(opts); - - ip_rs_exec_cmd(&args); - exec_cmd(&["ip", "link", "set", vlan_name, "up"]); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + let parent_name = format!("p{VLAN_NAME}"); + + // create parent dummy interface using ip-rs + ns.ip_rs_exec_cmd(&[ + "link", + "add", + &parent_name, + "address", + "0e:d1:49:08:27:84", + "type", + "dummy", + ]); + ns.exec_cmd(&["ip", "link", "set", &parent_name, "up"]); + + let mut args = vec![ + "link", + "add", + "link", + &parent_name, + "name", + VLAN_NAME, + "type", + "vlan", + "id", + "100", + ]; + + args.extend_from_slice(opts); + + ns.ip_rs_exec_cmd(&args); + ns.exec_cmd(&["ip", "link", "set", VLAN_NAME, "up"]); + + test(ns); }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", vlan_name]); - let _ = exec_cmd(&["ip", "link", "del", &parent_name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/link/tests/vxlan.rs b/src/ip/link/tests/vxlan.rs index 133a363..243e36e 100644 --- a/src/ip/link/tests/vxlan.rs +++ b/src/ip/link/tests/vxlan.rs @@ -2,73 +2,55 @@ use rand::RngExt; -use crate::tests::{exec_cmd, ip_rs_exec_cmd}; +use crate::tests::{NetnsGuard, with_netns}; + +const VXLAN_NAME: &str = "tvxln"; #[test] fn test_link_show_vxlan() { - let vxlan_name = "tvxln20"; - - with_vxlan_iface(vxlan_name, || { - let expected_output = exec_cmd(&["ip", "link", "show", vxlan_name]); - - let our_output = ip_rs_exec_cmd(&["link", "show", vxlan_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vxlan_iface(|ns| { + ns.assert_eq_output(&["link", "show", VXLAN_NAME]); + }); } #[test] fn test_link_detailed_show_vxlan() { - let vxlan_name = "tvxln10"; - - with_vxlan_iface(vxlan_name, || { - let expected_output = - exec_cmd(&["ip", "-d", "link", "show", vxlan_name]); - - let our_output = ip_rs_exec_cmd(&["-d", "link", "show", vxlan_name]); - - pretty_assertions::assert_eq!(&expected_output, &our_output); - }) + with_vxlan_iface(|ns| { + ns.assert_eq_output(&["-d", "link", "show", VXLAN_NAME]); + }); } -fn with_vxlan_iface(vxlan_name: &str, test: T) +fn with_vxlan_iface(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(&NetnsGuard), { - let mut rng = rand::rng(); - let vlan_id: u32 = rng.random_range(1000..10000); - let dstport: u16 = rng.random_range(20000..60000); - - exec_cmd(&[ - "ip", - "link", - "add", - vxlan_name, - "address", - "16:00:14:8a:28:cb", - "type", - "vxlan", - "id", - &vlan_id.to_string(), - "dstport", - &dstport.to_string(), - "mcroute", - "df", - "inherit", - "ttl", - "inherit", - ]); - - exec_cmd(&["ip", "link", "set", vxlan_name, "up"]); - - let result = std::panic::catch_unwind(|| { - test(); + with_netns(|ns| { + let mut rng = rand::rng(); + let vlan_id: u32 = rng.random_range(1000..10000); + let dstport: u16 = rng.random_range(20000..60000); + + ns.exec_cmd(&[ + "ip", + "link", + "add", + VXLAN_NAME, + "address", + "16:00:14:8a:28:cb", + "type", + "vxlan", + "id", + &vlan_id.to_string(), + "dstport", + &dstport.to_string(), + "mcroute", + "df", + "inherit", + "ttl", + "inherit", + ]); + + ns.exec_cmd(&["ip", "link", "set", VXLAN_NAME, "up"]); + + test(ns); }); - - // clean up - let _ = exec_cmd(&["ip", "link", "del", vxlan_name]); - - if let Err(e) = result { - std::panic::resume_unwind(e); - } } diff --git a/src/ip/tests/cmd.rs b/src/ip/tests/cmd.rs deleted file mode 100644 index 0eed899..0000000 --- a/src/ip/tests/cmd.rs +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT - -pub(crate) fn exec_cmd(args: &[&str]) -> String { - let output = std::process::Command::new(args[0]) - .args(&args[1..]) - .output() - .unwrap_or_else(|e| panic!("failed to execute command {args:?}: {e}")); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("Command failed: {args:?}\nstderr: {stderr}"); - } - - String::from_utf8(output.stdout) - .expect("Failed to convert command output to String") -} - -pub(crate) fn assert_alias_output(expected_args: &[&str], alias_args: &[&str]) { - let expected_output = ip_rs_exec_cmd(expected_args); - let our_output = ip_rs_exec_cmd(alias_args); - pretty_assertions::assert_eq!(expected_output, our_output); -} - -pub(crate) fn ip_rs_exec_cmd(args: &[&str]) -> String { - let mut cur_exec_path = - std::env::current_exe().expect("No current exec path"); - - cur_exec_path.pop(); - cur_exec_path.pop(); - - let output = std::process::Command::new( - cur_exec_path.join("ip").to_str().expect("Not UTF-8 string"), - ) - .args(args) - .output() - .unwrap_or_else(|e| { - panic!("failed to execute ip-rs command {args:?}: {e}") - }); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("Command failed: {args:?}\nstderr: {stderr}"); - } - - String::from_utf8(output.stdout) - .expect("Failed to convert command output to String") -} diff --git a/src/ip/tests/mod.rs b/src/ip/tests/mod.rs index b32ac2a..b3bbc21 100644 --- a/src/ip/tests/mod.rs +++ b/src/ip/tests/mod.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -mod cmd; +mod netns; -pub(crate) use self::cmd::{assert_alias_output, exec_cmd, ip_rs_exec_cmd}; +pub(crate) use self::netns::{NetnsGuard, with_netns}; diff --git a/src/ip/tests/netns.rs b/src/ip/tests/netns.rs new file mode 100644 index 0000000..5d4652e --- /dev/null +++ b/src/ip/tests/netns.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT + +use std::{ + hash::{DefaultHasher, Hash, Hasher as _}, + process::Command, +}; + +pub struct Outputs { + #[allow(dead_code)] + pub expected: String, + pub actual: String, +} + +pub struct NetnsGuard { + pub name: String, +} + +impl NetnsGuard { + fn new() -> Self { + let mut hasher = DefaultHasher::new(); + std::thread::current().id().hash(&mut hasher); + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + .hash(&mut hasher); + + let name = format!("test_ns_{}", hasher.finish(),); + assert!( + Command::new("ip") + .args(["netns", "add", &name]) + .status() + .expect("failed to create netns") + .success() + ); + + Self { name } + } + + pub fn exec_cmd(&self, args: &[&str]) -> String { + let mut full_args = vec!["netns", "exec", &self.name]; + full_args.extend_from_slice(args); + let output = Command::new("ip") + .args(&full_args) + .output() + .unwrap_or_else(|e| { + panic!("failed to execute command {args:?}: {e}") + }); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("Command failed: {args:?}\nstderr: {stderr}"); + } + + String::from_utf8(output.stdout) + .expect("Failed to convert command output to String") + } + + pub fn ip_rs_exec_cmd(&self, args: &[&str]) -> String { + let mut cur_exec_path = + std::env::current_exe().expect("No current exec path"); + cur_exec_path.pop(); + cur_exec_path.pop(); + + let ip_rs_pathbuf = cur_exec_path.join("ip"); + let ip_rs_path = ip_rs_pathbuf.to_str().expect("Not UTF-8 string"); + + let mut full_args = vec!["netns", "exec", &self.name]; + full_args.push(ip_rs_path); + full_args.extend_from_slice(args); + + let output = Command::new("ip") + .args(&full_args) + .output() + .unwrap_or_else(|e| { + panic!("failed to execute ip-rs command {args:?}: {e}") + }); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!("Command failed: {args:?}\nstderr: {stderr}"); + } + + String::from_utf8(output.stdout) + .expect("Failed to convert command output to String") + } + + pub fn assert_alias_output( + &self, + expected_args: &[&str], + alias_args: &[&str], + ) { + let expected_output = self.ip_rs_exec_cmd(expected_args); + let our_output = self.ip_rs_exec_cmd(alias_args); + pretty_assertions::assert_eq!(expected_output, our_output); + } + + pub fn assert_eq_output_map( + &self, + args: &[&str], + map: impl Fn(String) -> String, + ) -> Outputs { + let mut ip_args = vec!["ip"]; + ip_args.extend_from_slice(args); + + let expected_output = map(self.exec_cmd(&ip_args)); + let our_output = map(self.ip_rs_exec_cmd(args)); + + pretty_assertions::assert_eq!(&expected_output, &our_output); + + Outputs { + expected: expected_output, + actual: our_output, + } + } + + pub fn assert_eq_output(&self, args: &[&str]) -> Outputs { + self.assert_eq_output_map(args, std::convert::identity) + } +} + +impl Drop for NetnsGuard { + fn drop(&mut self) { + Command::new("ip") + .args(["netns", "del", &self.name]) + .status() + .ok(); + } +} + +pub(crate) fn with_netns(f: impl FnOnce(&NetnsGuard) -> T) -> T { + let guard = NetnsGuard::new(); + f(&guard) +}