diff options
Diffstat (limited to 'crates/rocie-server/tests/_testenv')
| -rw-r--r-- | crates/rocie-server/tests/_testenv/init.rs | 210 | ||||
| -rw-r--r-- | crates/rocie-server/tests/_testenv/log.rs | 72 | ||||
| -rw-r--r-- | crates/rocie-server/tests/_testenv/mod.rs | 23 |
3 files changed, 305 insertions, 0 deletions
diff --git a/crates/rocie-server/tests/_testenv/init.rs b/crates/rocie-server/tests/_testenv/init.rs new file mode 100644 index 0000000..5309fea --- /dev/null +++ b/crates/rocie-server/tests/_testenv/init.rs @@ -0,0 +1,210 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::{ + env, + ffi::OsStr, + fmt::Write, + fs, io, mem, + path::{Path, PathBuf}, + process::{self, Stdio}, + thread::sleep, + time::Duration, +}; + +use rocie_client::apis::configuration::Configuration; + +use crate::{_testenv::Paths, testenv::TestEnv}; + +fn target_dir() -> PathBuf { + // Tests exe is in target/debug/deps, the *rocie-server* exe is in target/debug + env::current_exe() + .expect("./target/debug/deps/rocie-server-*") + .parent() + .expect("./target/debug/deps") + .parent() + .expect("./target/debug") + .parent() + .expect("./target") + .to_path_buf() +} + +fn test_dir(name: &'static str, port: u32) -> PathBuf { + target_dir().join("tests").join(name).join(port.to_string()) +} + +fn prepare_files_and_dirs(test_dir: &Path) -> io::Result<Paths> { + fs::create_dir_all(test_dir)?; + + let db_path = test_dir.join("database.sqlite"); + + { + // Remove all files, so that the test run stays pure + for entry in fs::read_dir(test_dir).unwrap() { + let entry = entry.unwrap(); + let entry_ft = entry.file_type().unwrap(); + + if entry_ft.is_dir() { + fs::remove_dir_all(entry.path())?; + } else if entry_ft.is_file() { + fs::remove_file(entry.path())?; + } else { + panic!("Unknown file: {} ({entry_ft:#?})", entry.path().display()); + } + } + } + + Ok(Paths { + db: db_path, + test_dir: test_dir.to_owned(), + }) +} + +fn find_server_exe() -> PathBuf { + let target = target_dir().join("debug"); + + let exe_name = if cfg!(windows) { + "rocie-server.exe" + } else { + "rocie-server" + }; + + target.join(exe_name) +} + +fn rocie_base_path(port: &str) -> String { + format!("http://127.0.0.1:{port}") +} + +fn rocie_server_args<'a>(paths: &'a Paths, port: &'a str) -> [&'a OsStr; 5] { + [ + OsStr::new("serve"), + OsStr::new("--db-path"), + paths.db.as_os_str(), + OsStr::new("--port"), + OsStr::new(port), + ] +} + +impl TestEnv { + pub(crate) fn new(name: &'static str, port: u32) -> TestEnv { + let test_dir = test_dir(name, port); + + let paths = prepare_files_and_dirs(&test_dir) + .inspect_err(|err| panic!("Error during test dir preparation: {err}")) + .unwrap(); + + let server_process = { + let server_exe = find_server_exe(); + let mut cmd = process::Command::new(&server_exe); + + cmd.current_dir(&paths.test_dir); + + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + cmd.args(rocie_server_args(&paths, port.to_string().as_str())); + + let child = cmd.spawn().expect("server spawn"); + + // Give the server time to start. + // TODO(@bpeetz): Use a better synchronization primitive <2025-09-11> + sleep(Duration::from_millis(240)); + + child + }; + + let config = { + let mut inner = Configuration::new(); + inner.base_path = rocie_base_path(port.to_string().as_str()); + inner.user_agent = Some(String::from("Rocie test driver")); + inner + }; + + let me = TestEnv { + name, + test_dir, + paths, + server_process: Some(server_process), + config, + port: port.to_string(), + }; + + me.log(format!("Starting test `{name}` on port `{port}`")); + + me + } +} + +impl Drop for TestEnv { + fn drop(&mut self) { + /// Format an error message for when the server did not exit successfully. + fn format_exit(args: &[&OsStr], output: &process::Output) -> String { + let mut base = String::new(); + + { + let args = args + .iter() + .map(|s| s.to_str().unwrap()) + .collect::<Vec<_>>() + .join(" "); + + if output.status.success() { + writeln!(base, "`rocie-server {args}` did exit successfully.") + .expect("In-memory"); + } else { + writeln!(base, "`rocie-server {args}` did not exit successfully.") + .expect("In-memory"); + } + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + if !stdout.is_empty() { + writeln!(base, "Stdout:\n---\n{stdout}\n---").expect("In-memory"); + } + if !stderr.is_empty() { + writeln!(base, "Stderr:\n---\n{stderr}\n---").expect("In-memory"); + } + + base + } + + { + // Stop the server process via SIGTERM. + let mut kill = process::Command::new("kill") + .args([ + "-s", + "TERM", + &self.server_process.as_ref().unwrap().id().to_string(), + ]) + .spawn() + .unwrap(); + + eprintln!("Killing the server process"); + + kill.wait().unwrap(); + } + + let output = mem::take(&mut self.server_process) + .expect("Is some at this point") + .wait_with_output() + .expect("server exit output"); + + eprintln!( + "{}", + format_exit( + rocie_server_args(&self.paths, &self.port).as_slice(), + &output + ) + ); + } +} diff --git a/crates/rocie-server/tests/_testenv/log.rs b/crates/rocie-server/tests/_testenv/log.rs new file mode 100644 index 0000000..9a07e78 --- /dev/null +++ b/crates/rocie-server/tests/_testenv/log.rs @@ -0,0 +1,72 @@ +use crate::testenv::TestEnv; + +macro_rules! request { + ($env:expr, $fn:ident ($($arg:expr),* $(,)?)) => {{ + match request!(@call, $env, $fn, $($arg),*) { + Ok(ok) => { + $env.log(format!("> {ok:?}")); + + ok + }, + + Err(err) => { + $env.log(format!(">! {err:?}")); + + panic!("Server request failed."); + } + } + + }}; + + (@expect_error $reason:literal $env:expr, $fn:ident ($($arg:expr),* $(,)?)) => {{ + match request!(@call, $env, $fn, $($arg),*) { + Err(err) => { + $env.log(format!("> `{err}` ({})", $reason)); + + err + }, + + Ok(ok) => { + $env.log(format!(">? {ok:?}")); + + panic!("Server request succeeded, but should fail."); + } + } + }}; + + + (@call, $env:expr, $fn:ident, $($arg:expr),*) => {{ + $env.log( + format!( + "< {}({})", + stringify!($fn), + request!(@format, $fn, $($arg),*) + ) + ); + + $fn(&$env.config, $($arg),*).await + }}; + + (@format, $fn:ident, $($arg:expr),*) => {{ + use std::fmt::Write; + + let mut base = String::new(); + $( + write!(base, "{:?}, ", $arg) + .expect("In memory write"); + )* + + base.trim().trim_end_matches(',').to_owned() + }}; +} + +pub(crate) use request; + +impl TestEnv { + pub(crate) fn log<A>(&self, message: A) + where + A: AsRef<str>, + { + eprintln!("{}: {}", self.name, message.as_ref()); + } +} diff --git a/crates/rocie-server/tests/_testenv/mod.rs b/crates/rocie-server/tests/_testenv/mod.rs new file mode 100644 index 0000000..a37925e --- /dev/null +++ b/crates/rocie-server/tests/_testenv/mod.rs @@ -0,0 +1,23 @@ +//! This code was taken from *fd* at 30-06-2025. + +use std::{path::PathBuf, process}; + +use rocie_client::apis::configuration::Configuration; + +mod init; +pub(crate) mod log; + +/// Environment for the integration tests. +pub(crate) struct TestEnv { + pub(crate) name: &'static str, + pub(crate) port: String, + pub(crate) test_dir: PathBuf, + pub(crate) paths: Paths, + pub(crate) server_process: Option<process::Child>, + pub(crate) config: Configuration, +} + +pub(crate) struct Paths { + pub(crate) db: PathBuf, + pub(crate) test_dir: PathBuf, +} |
