diff --git a/Cargo.toml b/Cargo.toml index 5864f9a..24f9a00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,11 +42,9 @@ srv-rs = { path = ".", features = ["libresolv"] } criterion = "0.8.1" futures = "0.3" hyper = "0.13" -tokio = { version = "0.2", features = ["rt-threaded", "macros"] } +tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] } tempfile = "3.24.0" -owo-colors = "4.2.3" hickory-proto = "0.25" -rayon = "1.11.0" [[bench]] name = "client" @@ -55,7 +53,3 @@ harness = false [[bench]] name = "resolver" harness = false - -[[test]] -name = "run_integration_tests" -harness = false diff --git a/benches/client.rs b/benches/client.rs index 3224845..3ad527c 100644 --- a/benches/client.rs +++ b/benches/client.rs @@ -8,7 +8,7 @@ const SRV_DESCRIPTION: &str = SRV_NAME; /// Benchmark the performance of the client. #[allow(clippy::missing_panics_doc)] pub fn criterion_benchmark(c: &mut Criterion) { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); + let runtime = tokio::runtime::Runtime::new().unwrap(); let client = SrvClient::::new(SRV_NAME); let rfc2782_client = SrvClient::::new(SRV_NAME).policy(Rfc2782); diff --git a/benches/resolver.rs b/benches/resolver.rs index 0c365bf..292a48a 100644 --- a/benches/resolver.rs +++ b/benches/resolver.rs @@ -4,7 +4,7 @@ use srv_rs::resolver::{libresolv::LibResolv, SrvResolver}; /// Benchmark the performance of the resolver. #[allow(clippy::missing_panics_doc)] pub fn criterion_benchmark(c: &mut Criterion) { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); + let runtime = tokio::runtime::Runtime::new().unwrap(); let libresolv = LibResolv; let mut group = c.benchmark_group(format!("resolve {}", srv_rs::EXAMPLE_SRV)); diff --git a/tests/harness/mod.rs b/tests/harness/mod.rs deleted file mode 100644 index e8dce1d..0000000 --- a/tests/harness/mod.rs +++ /dev/null @@ -1,207 +0,0 @@ -use std::ffi::OsStr; -use std::path::Path; -use std::process::{Command, ExitCode, Output}; - -use owo_colors::OwoColorize; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; - -use crate::harness::dns_server::DnsServer; - -pub mod dns_server; - -/// Test harness implementation. -pub struct TestHarness; - -impl TestHarness { - /// Pass the test name as an argument to the test binary to run only that test. - /// This is used internally by the test harness itself, not humans. - const TEST_ARG: &str = "--test-name="; - - /// Setup the test harness. - /// Run all the tests or run a single test if a test name is provided. - /// Returns `ExitCode::SUCCESS` if all tests pass, `ExitCode::FAILURE` otherwise. - pub fn setup(tests: &[&Test]) -> ExitCode { - // Run a single test if one is specified - let args: Vec = std::env::args().collect(); - if let Some(test_name) = args.get(1).and_then(|s| s.strip_prefix(Self::TEST_ARG)) { - let test = tests - .iter() - .find(|t| t.name == test_name) - .unwrap_or_else(|| panic!("unknown test: {}", test_name)); - let _dns = - DnsServer::spawn(test.config.dns_records).expect("failed to start DNS server"); - (test.run)(); - return ExitCode::SUCCESS; - } - - // General case: run all tests in parallel, isolated processes - let self_exe = std::env::current_exe().unwrap(); - let results: Vec<(&str, std::io::Result)> = tests - .par_iter() - .map(|test| { - let arg = format!("{}{}", Self::TEST_ARG, test.name); - let test_output = Self::execute_test_in_sandbox(test, &self_exe, &[&arg]); - (test.name, test_output) - }) - .collect(); - - // Process the test results and print a summary - let mut passed = 0; - for (test_name, test_result) in results { - passed += usize::from(Self::process_test_result(test_name, test_result)); - } - println!("{}{passed}", "passed: ".green()); - let failed = tests.len() - passed; - if failed > 0 { - println!("{}{failed}", "failed: ".red()); - ExitCode::FAILURE - } else { - println!("{}", "all tests passed".green()); - ExitCode::SUCCESS - } - } - - /// Execute a test in an isolated sandbox environment. - fn execute_test_in_sandbox( - test: &Test, - binary: impl AsRef, - args: &[&str], - ) -> std::io::Result { - let tempdir = tempfile::tempdir()?; - let mut cmd = Command::new("bwrap"); - cmd.args(["--unshare-all"]) // Unshare every namespace (net, user, uts, pid, etc.) - .args(["--cap-add", "CAP_NET_ADMIN"]) // Required to bring up loopback interface - .args(["--cap-add", "CAP_NET_BIND_SERVICE"]) // Required for test DNS server - .args(["--dev-bind", "/", "/"]) // This can be narrowed - .arg("--die-with-parent"); // Die if the test harness exits unexpectedly - - // Mount any test files into the environment - for f in test.config.mock_files { - let mock_path = tempdir - .path() - .join(Path::new(f.desired_path).file_name().unwrap()); - std::fs::write(&mock_path, f.contents).unwrap(); - cmd.args(["--ro-bind", &mock_path.to_string_lossy(), f.desired_path]); - } - - cmd.arg("--").arg(binary).args(args).output() - } - - /// Process a test result: print output and return whether the test passed. - fn process_test_result(test_name: &str, result: std::io::Result) -> bool { - let success = match result { - Ok(out) => { - for line in String::from_utf8_lossy(&out.stdout).lines() { - println!("{test_name}: stdout: {line}"); - } - for line in String::from_utf8_lossy(&out.stderr).lines() { - eprintln!("{}", format!("{test_name}: stderr: {line}").yellow()); - } - out.status.success() - } - Err(e) => { - eprintln!("{}", format!("{test_name}: error: {e}").red()); - false - } - }; - - if success { - println!("{}{test_name}\n", "passed: ".green()); - } else { - println!("{}{test_name}\n", "failed: ".red()); - } - success - } -} - -/// Integration test definition struct. -/// Create one of these to each new integration test. -pub struct Test { - /// Unique name of the test - pub name: &'static str, - /// Function to run the test - pub run: fn(), - /// Test configuration - pub config: &'static TestConfig, -} - -/// Test configuration struct. -/// Use this to define the state of the world for a test. -#[non_exhaustive] -pub struct TestConfig { - /// Files to mock in the test environment - pub mock_files: &'static [MockFile], - /// DNS records to mock in the test environment - pub dns_records: &'static [MockSrv], -} - -/// Static SRV record definition for use in test configurations. -#[derive(Clone, Debug)] -pub struct MockSrv { - /// The SRV name (e.g., "_http._tcp.example.com") - pub name: &'static str, - /// Priority value - pub priority: u16, - /// Weight value - pub weight: u16, - /// Port number - pub port: u16, - /// Target hostname - pub target: &'static str, - /// TTL in seconds - pub ttl: u32, -} - -impl MockSrv { - /// Create a new SRV record. - pub const fn new( - name: &'static str, - priority: u16, - weight: u16, - port: u16, - target: &'static str, - ttl: u32, - ) -> Self { - Self { - name, - priority, - weight, - port, - target, - ttl, - } - } -} - -impl TestConfig { - /// Run this from within a test to validate the test configuration. - pub fn validate(&self) { - for f in self.mock_files { - let contents = std::fs::read(f.desired_path).unwrap(); - assert_eq!( - contents, f.contents, - "mock file {} contents mismatch", - f.desired_path - ); - } - } -} - -/// Defines how to mock a file in the test environment. -pub struct MockFile { - /// The path to the file in the test environment. - pub desired_path: &'static str, - /// The contents of the file in the test environment. - pub contents: &'static [u8], -} - -impl MockFile { - /// Create a new mocked file. - #[must_use] - pub const fn new(desired_path: &'static str, contents: &'static [u8]) -> Self { - Self { - desired_path, - contents, - } - } -} diff --git a/tests/run_integration_tests.rs b/tests/run_integration_tests.rs deleted file mode 100644 index 92da861..0000000 --- a/tests/run_integration_tests.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::harness::{Test, TestHarness}; - -mod harness; -mod tests; - -/// Complete list of integration tests to run. -static TESTS: &[&Test] = &[ - &tests::TEST_TRIVIAL_WITH_DEFAULT_CONFIG, - &tests::TEST_SIMPLE_LOOKUP_SRV_SINGLE, - &tests::TEST_SIMPLE_LOOKUP_SRV_MULTIPLE, -]; - -fn main() -> std::process::ExitCode { - TestHarness::setup(TESTS) -} diff --git a/tests/sandbox/components.rs b/tests/sandbox/components.rs new file mode 100644 index 0000000..f5fc756 --- /dev/null +++ b/tests/sandbox/components.rs @@ -0,0 +1,88 @@ +//! Sandbox components and the [`SandboxComponent`] trait. + +use std::path::{Path, PathBuf}; + +/// Minimal mock DNS server for testing SRV record resolution. +pub mod dns; +use dns::MockDns; + +/// Extend the sandbox environment with optional components (e.g., DNS, naming, certificate authorities, etc.) +pub trait SandboxComponent { + /// A component may require additional capabilities and mounts to run correctly inside the sandbox. + /// This method returns a list of those requirements. + fn configure_sandbox(&self, tempdir: &Path) -> Vec; + + /// Start the component inside the sandbox and return a handle to it. + fn start(&self) -> Box; +} + +impl SandboxComponent for MockDns { + fn configure_sandbox(&self, tempdir: &Path) -> Vec { + let mut reqs = vec![ + // Required to bring up the loopback interface. + SandboxRequirement::Capability(Capability::NetAdmin), + // Required to bind a socket to port 53. + SandboxRequirement::Capability(Capability::NetBindService), + ]; + for &(desired_path, contents) in Self::config_files() { + let host_path = tempdir.join(Path::new(desired_path).file_name().unwrap()); + std::fs::write(&host_path, contents).expect("failed to write mock file to tempdir"); + reqs.push(SandboxRequirement::BindMountReadOnly { + host_path, + desired_path: PathBuf::from(desired_path), + }); + } + reqs + } + + /// Start the DNS server and return a handle to it. + fn start(&self) -> Box { + // Validate that required configuration files were mounted correctly. + for &(desired_path, contents) in Self::config_files() { + let actual = std::fs::read(desired_path) + .unwrap_or_else(|e| panic!("failed to read mock file {}: {}", desired_path, e)); + assert_eq!( + actual, contents, + "mock file {desired_path} contents mismatch", + ); + } + + // Start the DNS server. + Box::new(self.spawn().expect("failed to start mock DNS server")) + } +} + +/// Requirements that a component may need to run correctly inside the sandbox. +pub enum SandboxRequirement { + /// Add a [`Capability`] to the sandbox. + Capability(Capability), + /// Read-only bind mount the host path to the desired path in the sandbox. + BindMountReadOnly { + host_path: PathBuf, + desired_path: PathBuf, + }, +} + +/// Capabilities that may be added to the sandbox. +/// New capabilities are added to this enum as needed. +pub enum Capability { + /// `CAP_NET_ADMIN` + NetAdmin, + /// `CAP_NET_BIND_SERVICE` + NetBindService, +} + +impl From<&Capability> for &'static str { + fn from(value: &Capability) -> Self { + match value { + Capability::NetAdmin => "CAP_NET_ADMIN", + Capability::NetBindService => "CAP_NET_BIND_SERVICE", + } + } +} + +impl std::fmt::Display for Capability { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.into()) + } +} diff --git a/tests/harness/dns_server.rs b/tests/sandbox/components/dns.rs similarity index 56% rename from tests/harness/dns_server.rs rename to tests/sandbox/components/dns.rs index b88008c..fe9e96c 100644 --- a/tests/harness/dns_server.rs +++ b/tests/sandbox/components/dns.rs @@ -1,5 +1,3 @@ -//! Minimal mock DNS server for testing SRV record resolution. - use std::io; use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; use std::process::Command; @@ -15,51 +13,62 @@ use hickory_proto::{ serialize::binary::{BinDecodable, BinEncodable}, }; -use crate::harness::MockSrv; - -/// A minimal DNS server that responds to SRV queries. -pub struct DnsServer { +/// A minimal mock DNS server that responds to SRV queries. +pub struct MockDns { records: Vec, - socket: UdpSocket, - shutdown_handle: ShutdownHandle, } -impl DnsServer { +impl MockDns { + /// Address to bind the DNS server to. + pub const BIND_ADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 53); + + /// Files needed for sandboxed DNS resolution via loopback. + pub const fn config_files() -> &'static [(&'static str, &'static [u8])] { + &[ + ("/etc/resolv.conf", b"nameserver 127.0.0.1\n"), + ("/etc/hosts", b"127.0.0.1 localhost\n"), + ("/etc/nsswitch.conf", b"hosts: files dns\n"), + ] + } + + /// Create a DNS server with the given SRV records. + pub fn new(records: &[MockSrv]) -> Self { + Self { + records: records.to_vec(), + } + } + /// Start the server in a background thread. - pub fn spawn(srv_records: &[MockSrv]) -> io::Result { + /// Brings up the loopback interface, binds to [`Self::BIND_ADDR`], and spawns a + /// thread that answers SRV queries with the configured records. + pub fn spawn(&self) -> io::Result { let output = Command::new("ip") .args(["link", "set", "lo", "up"]) .output()?; - if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); panic!("failed to bring up loopback interface: {}", stderr); } - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 53); - let socket = UdpSocket::bind(addr)?; + let socket = UdpSocket::bind(Self::BIND_ADDR)?; socket.set_read_timeout(Some(Duration::from_millis(1000)))?; - let shutdown_handle = ShutdownHandle(Arc::new(AtomicBool::new(false))); - let this = Self { - records: srv_records.to_vec(), - socket, - shutdown_handle: shutdown_handle.clone(), - }; - let join_handle = std::thread::spawn(move || this.run()); - println!("mock DNS server started on {addr}"); + let shutdown = Arc::new(AtomicBool::new(false)); + let shutdown_clone = Arc::clone(&shutdown); + let records = self.records.clone(); + let join_handle = std::thread::spawn(move || Self::run(&records, &socket, &shutdown_clone)); Ok(DnsServerHandle { - shutdown_handle, + shutdown, join_handle: Some(join_handle), }) } - /// Run the server, blocking the current thread. + /// Run the server loop, blocking the current thread. /// Returns when shutdown is triggered or an unrecoverable error occurs. - pub fn run(&self) -> io::Result<()> { + fn run(records: &[MockSrv], socket: &UdpSocket, shutdown: &AtomicBool) -> io::Result<()> { let mut buf = [0u8; 512]; - while !self.shutdown_handle.is_shutdown() { - let (len, src) = match self.socket.recv_from(&mut buf) { + while !shutdown.load(Ordering::Relaxed) { + let (len, src) = match socket.recv_from(&mut buf) { Ok(result) => result, Err(e) if e.kind() == io::ErrorKind::WouldBlock => continue, Err(e) if e.kind() == io::ErrorKind::TimedOut => continue, @@ -67,15 +76,15 @@ impl DnsServer { Err(e) => return Err(e), }; - if let Ok(response) = self.handle_query(&buf[..len]) { - let _ = self.socket.send_to(&response, src); + if let Ok(response) = Self::handle_query(records, &buf[..len]) { + let _ = socket.send_to(&response, src); } } Ok(()) } - fn handle_query(&self, query_bytes: &[u8]) -> Result, ()> { + fn handle_query(records: &[MockSrv], query_bytes: &[u8]) -> Result, ()> { let query = Message::from_bytes(query_bytes).map_err(|_| ())?; assert!( query @@ -96,8 +105,7 @@ impl DnsServer { for question in query.queries() { response.add_query(question.clone()); let qname = Self::normalize_name(&question.name().to_string()); - let answers = self - .records + let answers = records .iter() .filter(|srv| Self::normalize_name(srv.name) == qname) .filter_map(|srv| Self::create_srv_record(srv, question.name().clone()).ok()); @@ -124,33 +132,55 @@ impl DnsServer { } } +/// Static SRV record definition for use in test configurations. +#[derive(Clone, Debug)] +pub struct MockSrv { + /// The SRV name (e.g., `_http._tcp.example.com`) + pub name: &'static str, + /// Priority value + pub priority: u16, + /// Weight value + pub weight: u16, + /// Port number + pub port: u16, + /// Target hostname + pub target: &'static str, + /// TTL in seconds + pub ttl: u32, +} + +impl MockSrv { + /// Create a new SRV record. + pub const fn new( + name: &'static str, + priority: u16, + weight: u16, + port: u16, + target: &'static str, + ttl: u32, + ) -> Self { + Self { + name, + priority, + weight, + port, + target, + ttl, + } + } +} + /// Handle for the mock DNS server that shuts it down when dropped. pub struct DnsServerHandle { - shutdown_handle: ShutdownHandle, + shutdown: Arc, join_handle: Option>>, } impl Drop for DnsServerHandle { fn drop(&mut self) { - self.shutdown_handle.shutdown(); + self.shutdown.store(true, Ordering::Relaxed); if let Some(handle) = self.join_handle.take() { let _ = handle.join(); } } } - -/// Handle for shutting down a running mock DNS server. -#[derive(Clone)] -pub struct ShutdownHandle(Arc); - -impl ShutdownHandle { - /// Signal the server to shut down. - pub fn shutdown(&self) { - self.0.store(true, Ordering::Relaxed); - } - - /// Returns `true` if a shutdown has been requested. - fn is_shutdown(&self) -> bool { - self.0.load(Ordering::Relaxed) - } -} diff --git a/tests/sandbox/mod.rs b/tests/sandbox/mod.rs new file mode 100644 index 0000000..0b427df --- /dev/null +++ b/tests/sandbox/mod.rs @@ -0,0 +1,118 @@ +// Shared test infrastructure — not every test binary uses every item. +#![allow(dead_code)] + +use std::{ + io::{stderr, stdout, Write}, + process::Command, +}; + +pub mod components; + +use components::{SandboxComponent, SandboxRequirement}; + +/// A sandbox test environment. +pub struct Sandbox { + /// Optional components to extend the sandbox environment (e.g., DNS, naming, certificate authorities, etc.) + components: Vec>, +} + +impl Sandbox { + /// Environment variable set within the sandboxed child process. + const INSIDE_SANDBOX: &str = "SRV_RS_SANDBOX"; + + /// Create a new, empty [`Sandbox`]. + pub const fn new() -> Self { + Self { + components: Vec::new(), + } + } + + /// Add a [`SandboxComponent`] to the sandbox. + pub fn component(mut self, component: impl SandboxComponent + 'static) -> Self { + self.components.push(Box::new(component)); + self + } + + /// Execute the given test body within the sandbox. + pub fn run(self, test_body: impl FnOnce()) { + if std::env::var(Self::INSIDE_SANDBOX).is_ok() { + // Child: we're inside the sandbox; run the test body + let _started_components: Vec<_> = self.components.iter().map(|c| c.start()).collect(); + test_body(); + } else { + // Parent: execute the test binary in the sandbox + self.execute_in_sandbox(); + } + } + + /// Execute the given test body within the sandbox using a Tokio runtime. + pub fn run_with_tokio>( + self, + test_body: impl FnOnce() -> F, + ) { + self.run(|| { + tokio::runtime::Runtime::new() + .expect("failed to create Tokio runtime") + .block_on(test_body()); + }); + } + + /// Execute the current test binary inside a sandboxed environment. + fn execute_in_sandbox(self) { + let self_exe = std::env::current_exe().expect("failed to determine test binary path"); + let tempdir = tempfile::tempdir().expect("failed to create tempdir for sandbox"); + let test_name = std::thread::current() + .name() + .expect("sandbox must be run from within a #[test] function") + .to_string(); + + // Process sandbox components into bwrap arguments + let component_args: Vec = self + .components + .iter() + .flat_map(|c| c.configure_sandbox(tempdir.path())) + .flat_map(|add| add.to_bwrap_args()) + .collect(); + + // Build and execute a bubblewrap command + let output = Command::new("bwrap") + .args(["--unshare-all"]) // Unshare every namespace + .args(["--dev-bind", "/", "/"]) // TODO: this can be narrowed + .arg("--die-with-parent") // Die if the parent exits + .args(&component_args) // Add component-specific arguments + .arg("--") + .arg(&self_exe) + .args([&test_name, "--exact", "--nocapture"]) + .env(Self::INSIDE_SANDBOX, "1") + .output() + .expect("failed to execute bwrap"); + + // Forward test output and success/failure to the Rust test harness + let _ = stdout().write_all(&output.stdout); + let _ = stderr().write_all(&output.stderr); + assert!( + output.status.success(), + "test `{test_name}` failed inside sandbox (exit code: {:?})", + output.status.code() + ); + } +} + +impl SandboxRequirement { + /// Translate the requirement into a list of bwrap command line arguments. + fn to_bwrap_args(&self) -> Vec { + match self { + Self::Capability(cap) => vec![String::from("--cap-add"), cap.to_string()], + Self::BindMountReadOnly { + host_path: mock_path, + desired_path, + } => { + vec![ + String::from("--ro-bind"), + mock_path.to_string_lossy().to_string(), + desired_path.to_string_lossy().to_string(), + ] + } + } + } +} diff --git a/tests/test_simple_lookup_srv_multiple.rs b/tests/test_simple_lookup_srv_multiple.rs new file mode 100644 index 0000000..22f8541 --- /dev/null +++ b/tests/test_simple_lookup_srv_multiple.rs @@ -0,0 +1,52 @@ +//! A test that looks up multiple SRV records. + +mod sandbox; + +use sandbox::components::dns::{MockDns, MockSrv}; +use sandbox::Sandbox; +use srv_rs::resolver::{libresolv::LibResolv, SrvResolver}; +use srv_rs::SrvRecord; + +#[test] +fn simple_lookup_srv_multiple() { + Sandbox::new() + .component(MockDns::new(&[ + MockSrv::new( + "_http._tcp.multi.local.", + 10, + 100, + 8080, + "primary.multi.local.", + 300, + ), + MockSrv::new( + "_http._tcp.multi.local.", + 20, + 50, + 8081, + "secondary.multi.local.", + 300, + ), + MockSrv::new( + "_http._tcp.multi.local.", + 10, + 25, + 8082, + "backup.multi.local.", + 300, + ), + ])) + .run_with_tokio(|| test_simple_lookup_srv_multiple(LibResolv)); +} + +async fn test_simple_lookup_srv_multiple(resolver: impl SrvResolver) { + let (records, _valid_until) = resolver + .get_srv_records("_http._tcp.multi.local.") + .await + .expect("SRV lookup failed"); + assert_eq!(records.len(), 3, "expected 3 SRV records"); + assert!( + records.iter().map(SrvRecord::priority).is_sorted(), + "records should be sorted by priority", + ); +} diff --git a/tests/test_simple_lookup_srv_single.rs b/tests/test_simple_lookup_srv_single.rs new file mode 100644 index 0000000..e9c08bb --- /dev/null +++ b/tests/test_simple_lookup_srv_single.rs @@ -0,0 +1,35 @@ +//! A test that looks up a single SRV record. + +mod sandbox; + +use sandbox::components::dns::{MockDns, MockSrv}; +use sandbox::Sandbox; +use srv_rs::resolver::{libresolv::LibResolv, SrvResolver}; +use srv_rs::SrvRecord; + +#[test] +fn simple_lookup_srv_single() { + Sandbox::new() + .component(MockDns::new(&[MockSrv::new( + "_http._tcp.test.local.", + 10, + 100, + 8080, + "server1.test.local.", + 300, + )])) + .run_with_tokio(|| test_simple_lookup_srv_single(LibResolv)); +} + +async fn test_simple_lookup_srv_single(resolver: impl SrvResolver) { + let (records, _valid_until) = resolver + .get_srv_records_unordered("_http._tcp.test.local.") + .await + .expect("SRV lookup failed"); + assert_eq!(records.len(), 1, "expected 1 SRV record"); + let record = records.first().unwrap(); + assert_eq!(record.priority(), 10); + assert_eq!(record.weight(), 100); + assert_eq!(record.port(), 8080); + assert_eq!(record.target().to_string(), "server1.test.local"); +} diff --git a/tests/test_trivial_with_default_config.rs b/tests/test_trivial_with_default_config.rs new file mode 100644 index 0000000..886877d --- /dev/null +++ b/tests/test_trivial_with_default_config.rs @@ -0,0 +1,13 @@ +//! A trivial test that checks that the sandbox harness is working. + +mod sandbox; + +use sandbox::Sandbox; + +#[test] +fn test_trivial() { + Sandbox::new().run(|| { + print!("stdout check"); + eprint!("stderr check"); + }); +} diff --git a/tests/tests/mod.rs b/tests/tests/mod.rs deleted file mode 100644 index 6aceb5a..0000000 --- a/tests/tests/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::harness::{MockFile, TestConfig}; - -/// Files to use for all tests unless otherwise specified. -static DEFAULT_MOCK_FILES: &[MockFile] = &[ - MockFile::new("/etc/resolv.conf", b"nameserver 127.0.0.1\n"), - MockFile::new("/etc/hosts", b"127.0.0.1 localhost\n"), - MockFile::new("/etc/nsswitch.conf", b"hosts: files dns\n"), -]; - -/// Configuration to use for all tests unless otherwise specified. -static DEFAULT_TEST_CONFIG: TestConfig = TestConfig { - mock_files: DEFAULT_MOCK_FILES, - dns_records: &[], -}; - -mod test_simple_lookup_srv_multiple; -mod test_simple_lookup_srv_single; -mod test_trivial_with_default_config; - -pub use test_simple_lookup_srv_multiple::TEST_SIMPLE_LOOKUP_SRV_MULTIPLE; -pub use test_simple_lookup_srv_single::TEST_SIMPLE_LOOKUP_SRV_SINGLE; -pub use test_trivial_with_default_config::TEST_TRIVIAL_WITH_DEFAULT_CONFIG; diff --git a/tests/tests/test_simple_lookup_srv_multiple.rs b/tests/tests/test_simple_lookup_srv_multiple.rs deleted file mode 100644 index 3376702..0000000 --- a/tests/tests/test_simple_lookup_srv_multiple.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! A test that looks up multiple SRV records. - -use srv_rs::resolver::{libresolv::LibResolv, SrvResolver}; - -use crate::{ - harness::{MockSrv, Test, TestConfig}, - tests::DEFAULT_MOCK_FILES, -}; - -pub static TEST_SIMPLE_LOOKUP_SRV_MULTIPLE: Test = Test { - name: "test_simple_lookup_srv_multiple", - run: test_simple_lookup_srv_multiple, - config: &TestConfig { - mock_files: DEFAULT_MOCK_FILES, - dns_records: &[ - MockSrv::new( - "_http._tcp.multi.local.", - 10, - 100, - 8080, - "primary.multi.local.", - 300, - ), - MockSrv::new( - "_http._tcp.multi.local.", - 20, - 50, - 8081, - "secondary.multi.local.", - 300, - ), - MockSrv::new( - "_http._tcp.multi.local.", - 10, - 25, - 8082, - "backup.multi.local.", - 300, - ), - ], - }, -}; - -fn test_simple_lookup_srv_multiple() { - TEST_SIMPLE_LOOKUP_SRV_MULTIPLE.config.validate(); - let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let (records, _valid_until) = LibResolv - .get_srv_records("_http._tcp.multi.local.") - .await - .expect("SRV lookup failed"); - - assert_eq!(records.len(), 3, "expected 3 SRV records"); - - assert!( - records.iter().map(|r| r.priority).is_sorted(), - "records should be sorted by priority: {:?}", - records.iter().map(|r| r.priority).collect::>() - ); - - println!("SRV lookup returned {} records:", records.len()); - for record in &records { - println!("SRV lookup successful: {record:?}",); - } - }); -} diff --git a/tests/tests/test_simple_lookup_srv_single.rs b/tests/tests/test_simple_lookup_srv_single.rs deleted file mode 100644 index 4cd2c36..0000000 --- a/tests/tests/test_simple_lookup_srv_single.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! A test that looks up a single SRV record. - -use srv_rs::resolver::{libresolv::LibResolv, SrvResolver}; - -use crate::{ - harness::{MockSrv, Test, TestConfig}, - tests::DEFAULT_MOCK_FILES, -}; - -pub static TEST_SIMPLE_LOOKUP_SRV_SINGLE: Test = Test { - name: "test_simple_lookup_srv_single", - run: test_simple_lookup_srv_single, - config: &TestConfig { - mock_files: DEFAULT_MOCK_FILES, - dns_records: &[MockSrv::new( - "_http._tcp.test.local.", - 10, - 100, - 8080, - "server1.test.local.", - 300, - )], - }, -}; - -fn test_simple_lookup_srv_single() { - TEST_SIMPLE_LOOKUP_SRV_SINGLE.config.validate(); - let mut rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let (records, _valid_until) = LibResolv - .get_srv_records_unordered("_http._tcp.test.local.") - .await - .expect("SRV lookup failed"); - - assert_eq!(records.len(), 1, "expected 1 SRV record"); - let record = records.first().unwrap(); - assert_eq!(record.priority, 10); - assert_eq!(record.weight, 100); - assert_eq!(record.port, 8080); - assert_eq!(record.target, "server1.test.local"); - println!("SRV lookup successful: {record:?}"); - }); -} diff --git a/tests/tests/test_trivial_with_default_config.rs b/tests/tests/test_trivial_with_default_config.rs deleted file mode 100644 index e1f4826..0000000 --- a/tests/tests/test_trivial_with_default_config.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! A trivial test that just checks that the test harness is working. - -use crate::{harness::Test, tests::DEFAULT_TEST_CONFIG}; - -pub static TEST_TRIVIAL_WITH_DEFAULT_CONFIG: Test = Test { - name: "test_trivial_with_default_config", - run: test_trivial_with_default_config, - config: &DEFAULT_TEST_CONFIG, -}; - -fn test_trivial_with_default_config() { - TEST_TRIVIAL_WITH_DEFAULT_CONFIG.config.validate(); - print!("stdout check"); - eprint!("stderr check"); -}