From aa6dec016fbcc61721b97647c5e6af8b5af92af3 Mon Sep 17 00:00:00 2001 From: JyJyJcr <82190170+JyJyJcr@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:44:33 +0900 Subject: [PATCH 1/3] feat: split nginx test util into optional module --- Cargo.toml | 3 + src/lib.rs | 6 + src/test_util.rs | 338 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 src/test_util.rs diff --git a/Cargo.toml b/Cargo.toml index d7383dd..bca158c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,9 @@ std = ["alloc"] # This could be disabled with `--no-default-features` to minimize the dependency tree # when building against an existing copy of the NGINX with the NGX_OBJS variable. vendored = ["nginx-sys/vendored"] +# test utility +test_util = [] + [badges] maintenance = { status = "experimental" } diff --git a/src/lib.rs b/src/lib.rs index 52f8e88..b10c8c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,12 @@ pub mod http; #[cfg(feature = "std")] pub mod log; +/// The test utility module. +/// +/// This module provides utilities for integration tests with bundled NGINX. +#[cfg(feature = "test_util")] +pub mod test_util; + /// Define modules exported by this library. /// /// These are normally generated by the Nginx module system, but need to be diff --git a/src/test_util.rs b/src/test_util.rs new file mode 100644 index 0000000..7355fdc --- /dev/null +++ b/src/test_util.rs @@ -0,0 +1,338 @@ +use std::borrow::Cow; +use std::ffi::CStr; +use std::fs; +use std::fs::read_dir; +use std::io::Result; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::process::Output; + +use crate::ffi::{ + NGX_CONF_PATH, NGX_CONF_PREFIX, NGX_ERROR_LOG_PATH, NGX_HTTP_CLIENT_TEMP_PATH, NGX_HTTP_FASTCGI_TEMP_PATH, + NGX_HTTP_LOG_PATH, NGX_HTTP_PROXY_TEMP_PATH, NGX_HTTP_SCGI_TEMP_PATH, NGX_HTTP_UWSGI_TEMP_PATH, NGX_LOCK_PATH, + NGX_PID_PATH, NGX_PREFIX, NGX_SBIN_PATH, +}; + +/// Convert a CStr to a PathBuf +pub fn cstr_to_path(val: &std::ffi::CStr) -> Option> { + if val.is_empty() { + return None; + } + + #[cfg(unix)] + { + let str = std::ffi::OsStr::from_bytes(val.to_bytes()); + Some(Cow::Borrowed(str.as_ref())) + } + #[cfg(not(unix))] + { + let str = std::str::from_utf8(val.to_bytes()).ok()?; + Some(&str.as_ref()) + } +} + +fn target_dir_cands() -> Option> { + #[cfg(target_os = "macos")] + { + match std::env::var("DYLD_FALLBACK_LIBRARY_PATH") { + Ok(cands) => Some(cands.split(':').map(PathBuf::from).collect()), + Err(_) => None, + } + } + #[cfg(target_os = "linux")] + { + match std::env::var("LD_LIBRARY_PATH") { + Ok(cands) => Some(cands.split(':').map(PathBuf::from).collect()), + Err(_) => None, + } + } +} + +/// search path and return the path to the target +pub fn target_path(target_name: &str) -> std::io::Result { + if let Some(cands) = target_dir_cands() { + for dir in cands { + if let Ok(iter) = read_dir(dir) { + for entry in iter.flatten() { + if entry.file_name() == target_name { + return Ok(entry.path()); + } + } + } + } + } + Err(std::io::ErrorKind::NotFound.into()) +} + +/// harness to test nginx +#[allow(dead_code)] +pub struct Nginx { + // these paths have options to change them from default paths (in prefix dir) + // most of them are not used, but keep them for future uses + prefix: PathBuf, + sbin_path: PathBuf, + modules_prefix: PathBuf, // and only this path is not embedded in bindings.rs, since the module root is same to the prefix + conf_path: PathBuf, + conf_prefix: PathBuf, + error_log_path: PathBuf, + pid_path: PathBuf, + lock_path: PathBuf, + http_log_path: PathBuf, + http_client_body_temp_path: PathBuf, + http_proxy_temp_path: PathBuf, + http_fastcgi_temp_path: PathBuf, + http_uwsgi_temp_path: PathBuf, + http_scgi_temp_path: PathBuf, + // here all path are absolute +} + +/// nginx harness builder +pub struct NginxBuilder { + prefix: PathBuf, + sbin_path: PathBuf, + modules_prefix: PathBuf, + conf_path: PathBuf, + conf_prefix: PathBuf, + error_log_path: PathBuf, + pid_path: PathBuf, + lock_path: PathBuf, + http_log_path: PathBuf, + http_client_body_temp_path: PathBuf, + http_proxy_temp_path: PathBuf, + http_fastcgi_temp_path: PathBuf, + http_uwsgi_temp_path: PathBuf, + http_scgi_temp_path: PathBuf, + // in builder path could be relative +} + +impl Default for NginxBuilder { + fn default() -> Self { + fn conv(raw_path: &CStr) -> Option { + cstr_to_path(raw_path).map(|p| p.to_path_buf()) + } + Self { + prefix: conv(NGX_PREFIX).expect("installation prefix"), + sbin_path: conv(NGX_SBIN_PATH).expect("nginx executable path"), + modules_prefix: conv(NGX_PREFIX).expect("module prefix"), + conf_path: conv(NGX_CONF_PATH).expect("configuration file path"), + conf_prefix: conv(NGX_CONF_PREFIX).expect("configuration file prefix"), + error_log_path: conv(NGX_ERROR_LOG_PATH).expect("error log file path"), + pid_path: conv(NGX_PID_PATH).expect("pid file path"), + lock_path: conv(NGX_LOCK_PATH).expect("lock file path"), + http_log_path: conv(NGX_HTTP_LOG_PATH).expect("http log file path"), + http_client_body_temp_path: conv(NGX_HTTP_CLIENT_TEMP_PATH).expect("client body temp file path"), + http_proxy_temp_path: conv(NGX_HTTP_PROXY_TEMP_PATH).expect("proxy temp file path"), + http_fastcgi_temp_path: conv(NGX_HTTP_FASTCGI_TEMP_PATH).expect("fastcgi temp file path"), + http_uwsgi_temp_path: conv(NGX_HTTP_UWSGI_TEMP_PATH).expect("uwsgi temp file path"), + http_scgi_temp_path: conv(NGX_HTTP_SCGI_TEMP_PATH).expect("scgi temp file path"), + } + } +} + +impl NginxBuilder { + /// set alternative configuration path + pub fn conf_path(mut self, path: PathBuf) -> Self { + self.conf_path = path; + self + } + + /// build nginx harness + pub fn build(self) -> Nginx { + let prefix = self.prefix; + + let add_prefix = |p: PathBuf| -> PathBuf { + if p.is_relative() { + prefix.join(p) + } else { + p.to_path_buf() + } + }; + + let sbin_path = add_prefix(self.sbin_path); + let modules_path = add_prefix(self.modules_prefix); + let conf_path = add_prefix(self.conf_path); + let conf_prefix = add_prefix(self.conf_prefix); + let error_log_path = add_prefix(self.error_log_path); + let pid_path = add_prefix(self.pid_path); + let lock_path = add_prefix(self.lock_path); + let http_log_path = add_prefix(self.http_log_path); + let http_client_body_temp_path = add_prefix(self.http_client_body_temp_path); + let http_proxy_temp_path = add_prefix(self.http_proxy_temp_path); + let http_fastcgi_temp_path = add_prefix(self.http_fastcgi_temp_path); + let http_uwsgi_temp_path = add_prefix(self.http_uwsgi_temp_path); + let http_scgi_temp_path = add_prefix(self.http_scgi_temp_path); + + Nginx { + prefix, + sbin_path, + modules_prefix: modules_path, + conf_path, + conf_prefix, + error_log_path, + pid_path, + lock_path, + http_log_path, + http_client_body_temp_path, + http_proxy_temp_path, + http_fastcgi_temp_path, + http_uwsgi_temp_path, + http_scgi_temp_path, + } + } +} + +impl Nginx { + /// execute nginx process with arguments + pub fn cmd(&mut self, args: &[&str]) -> Result { + let result = Command::new(&self.sbin_path).args(args).output(); + + match result { + Err(e) => Err(e), + + Ok(output) => { + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + Ok(output) + } + } + } + + /// complete stop the nginx binary + pub fn stop(&mut self) -> Result { + self.cmd(&["-s", "stop"]) + } + + /// start the nginx binary + pub fn start(&mut self) -> Result { + self.cmd(&[]) + } + + /// make sure we stop existing nginx and start new master process + /// intentinally ignore failure in stop + pub fn restart(&mut self) -> Result { + let _ = self.stop(); + self.start() + } + + /// replace main config with another config + pub fn copy_main_config>(&mut self, conf_path_from: P) -> Result { + let conf_path_to = &self.conf_path; + println!( + "copying main config from: {} to: {}", + conf_path_from.as_ref().display(), + conf_path_to.display() + ); // replace with logging + fs::copy(conf_path_from, conf_path_to) + } + + /// replace config with another config + pub fn copy_config, Q: AsRef>( + &mut self, + conf_path_from: P, + conf_path_rel_to: Q, + ) -> Result { + if conf_path_rel_to.as_ref().is_relative() { + let conf_path_to = self.conf_prefix.join(conf_path_rel_to.as_ref()); + println!( + "copying config from: {} to: {}", + conf_path_from.as_ref().display(), + conf_path_to.display() + ); // replace with logging + fs::copy(conf_path_from, conf_path_to) + } else { + panic!("absolute path"); + } + } + /// create config from &str + pub fn create_config_from_str>(&mut self, conf_path_rel_to: Q, conf_content: &str) -> Result<()> { + if conf_path_rel_to.as_ref().is_relative() { + let conf_path_to = self.conf_prefix.join(conf_path_rel_to.as_ref()); + println!( + "creating config to: {} content: {}", + conf_path_to.display(), + conf_content + ); // replace with logging + fs::write(conf_path_to, conf_content) + } else { + panic!("absolute path"); + } + } + /// copy or replace module + pub fn copy_module, Q: AsRef>( + &mut self, + module_path_from: P, + module_path_rel_to: Q, + ) -> Result { + if module_path_rel_to.as_ref().is_relative() { + let module_path_to = self.modules_prefix.join(module_path_rel_to.as_ref()); + println!( + "copying module from: {} to: {}", + module_path_from.as_ref().display(), + module_path_to.display() + ); // replace with logging + fs::copy(module_path_from, module_path_to) + } else { + panic!("absolute path"); + } + } + + /// get prefix of nginx instance + pub fn prefix(&self) -> &Path { + &self.prefix + } + /// get bin path to nginx instance + pub fn bin_path(&self) -> &Path { + &self.sbin_path + } + /// get module prefix + pub fn modules_prefix(&self) -> &Path { + &self.modules_prefix + } + /// get configuration file path + pub fn conf_path(&self) -> &Path { + &self.conf_path + } + /// get configuration file prefix + pub fn conf_prefix(&self) -> &Path { + &self.conf_prefix + } + /// get error log file path + pub fn error_log_path(&self) -> &Path { + &self.error_log_path + } + /// get pid file path + pub fn pid_path(&self) -> &Path { + &self.pid_path + } + /// get lock file path + pub fn lock_path(&self) -> &Path { + &self.lock_path + } + /// get http log file path + pub fn http_log_path(&self) -> &Path { + &self.http_log_path + } + /// get client body temp file path + pub fn http_client_body_temp_path(&self) -> &Path { + &self.http_client_body_temp_path + } + /// get proxy temp file path + pub fn http_proxy_temp_path(&self) -> &Path { + &self.http_proxy_temp_path + } + /// get fastcgi temp file path + pub fn http_fastcgi_temp_path(&self) -> &Path { + &self.http_fastcgi_temp_path + } + /// get uwsgi temp file path + pub fn http_uwsgi_temp_path(&self) -> &Path { + &self.http_uwsgi_temp_path + } + /// get scgi temp file path + pub fn http_scgi_temp_path(&self) -> &Path { + &self.http_scgi_temp_path + } +} From e6042006463ebdb60f191b76f1db3ca06da2f3af Mon Sep 17 00:00:00 2001 From: JyJyJcr <82190170+JyJyJcr@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:44:33 +0900 Subject: [PATCH 2/3] fix: stop when dropped --- src/test_util.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/test_util.rs b/src/test_util.rs index 7355fdc..8985abc 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -86,6 +86,14 @@ pub struct Nginx { http_uwsgi_temp_path: PathBuf, http_scgi_temp_path: PathBuf, // here all path are absolute + status: Status, +} + +#[derive(PartialEq, Eq)] +enum Status { + Unknown, + Running, + Stopped, } /// nginx harness builder @@ -179,6 +187,7 @@ impl NginxBuilder { http_fastcgi_temp_path, http_uwsgi_temp_path, http_scgi_temp_path, + status: Status::Unknown, } } } @@ -202,12 +211,19 @@ impl Nginx { /// complete stop the nginx binary pub fn stop(&mut self) -> Result { + self.status = Status::Stopped; self.cmd(&["-s", "stop"]) } /// start the nginx binary pub fn start(&mut self) -> Result { - self.cmd(&[]) + let output = self.cmd(&[]); + if let Ok(output) = &output { + if output.status.success() { + self.status = Status::Running; + } + } + output } /// make sure we stop existing nginx and start new master process @@ -336,3 +352,12 @@ impl Nginx { &self.http_scgi_temp_path } } + +impl Drop for Nginx { + fn drop(&mut self) { + // exec stop if running or unknown + if self.status != Status::Stopped { + let _ = self.stop(); + } + } +} From a3cec59b2d5091b6b2b768a463b1193c1cd2b1d0 Mon Sep 17 00:00:00 2001 From: JyJyJcr <82190170+JyJyJcr@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:44:33 +0900 Subject: [PATCH 3/3] chore: test use test_util --- Cargo.lock | 1 + Cargo.toml | 1 + tests/log_test.rs | 103 ++-------------------------------------------- 3 files changed, 5 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96260b6..dfb3c3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,7 @@ name = "ngx" version = "0.5.0" dependencies = [ "nginx-sys", + "ngx", "target-triple", ] diff --git a/Cargo.toml b/Cargo.toml index bca158c..c1fab40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,3 +48,4 @@ maintenance = { status = "experimental" } [dev-dependencies] target-triple = "0.1.2" +ngx = { path ="./", features = ["test_util"] } diff --git a/tests/log_test.rs b/tests/log_test.rs index d219eab..3ff27bd 100644 --- a/tests/log_test.rs +++ b/tests/log_test.rs @@ -1,111 +1,14 @@ -use std::fs; -use std::io::Result; -#[cfg(unix)] -use std::os::unix::ffi::OsStrExt; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::process::Output; - -use ngx::ffi::{NGX_CONF_PATH, NGX_PREFIX, NGX_SBIN_PATH}; - -/// Convert a CStr to a PathBuf -pub fn cstr_to_path(val: &std::ffi::CStr) -> Option { - if val.is_empty() { - return None; - } - - #[cfg(unix)] - let str = std::ffi::OsStr::from_bytes(val.to_bytes()); - #[cfg(not(unix))] - let str = std::str::from_utf8(val.to_bytes()).ok()?; - - Some(PathBuf::from(str)) -} - -/// harness to test nginx -pub struct Nginx { - pub install_path: PathBuf, - pub config_path: PathBuf, -} - -impl Default for Nginx { - /// create nginx with default - fn default() -> Nginx { - let install_path = cstr_to_path(NGX_PREFIX).expect("installation prefix"); - Nginx::new(install_path) - } -} - -impl Nginx { - pub fn new>(path: P) -> Nginx { - let install_path = path.as_ref(); - let config_path = cstr_to_path(NGX_CONF_PATH).expect("configuration path"); - let config_path = install_path.join(config_path); - - Nginx { - install_path: install_path.into(), - config_path, - } - } - - /// get bin path to nginx instance - pub fn bin_path(&mut self) -> PathBuf { - let bin_path = cstr_to_path(NGX_SBIN_PATH).expect("binary path"); - self.install_path.join(bin_path) - } - - /// start nginx process with arguments - pub fn cmd(&mut self, args: &[&str]) -> Result { - let bin_path = self.bin_path(); - let result = Command::new(bin_path).args(args).output(); - - match result { - Err(e) => Err(e), - - Ok(output) => { - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - Ok(output) - } - } - } - - /// complete stop the nginx binary - pub fn stop(&mut self) -> Result { - self.cmd(&["-s", "stop"]) - } - - /// start the nginx binary - pub fn start(&mut self) -> Result { - self.cmd(&[]) - } - - // make sure we stop existing nginx and start new master process - // intentinally ignore failure in stop - pub fn restart(&mut self) -> Result { - let _ = self.stop(); - self.start() - } - - // replace config with another config - pub fn replace_config>(&mut self, from: P) -> Result { - println!("copying config from: {:?} to: {:?}", from.as_ref(), self.config_path); // replace with logging - fs::copy(from, &self.config_path) - } -} - #[cfg(test)] mod tests { use std::env; - use super::*; + use ngx::test_util::NginxBuilder; const TEST_NGINX_CONFIG: &str = "tests/nginx.conf"; #[test] fn test() { - let mut nginx = Nginx::default(); + let mut nginx = NginxBuilder::default().build(); let current_dir = env::current_dir().expect("Unable to get current directory"); let test_config_path = current_dir.join(TEST_NGINX_CONFIG); @@ -118,7 +21,7 @@ mod tests { ); nginx - .replace_config(&test_config_path) + .copy_main_config(&test_config_path) .unwrap_or_else(|_| panic!("Unable to load config file: {}", test_config_path.to_string_lossy())); let output = nginx.restart().expect("Unable to restart NGINX"); assert!(output.status.success());