Skip to content

Commit

Permalink
feat: split nginx test util into optional module
Browse files Browse the repository at this point in the history
  • Loading branch information
JyJyJcr committed Jan 1, 2025
1 parent f3b36f3 commit e0c066c
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ default = ["vendored"]
# 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" }
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ pub mod http;
/// This module provides an interface into the NGINX logger framework.
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
Expand Down
338 changes: 338 additions & 0 deletions src/test_util.rs
Original file line number Diff line number Diff line change
@@ -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<Cow<Path>> {
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<Vec<PathBuf>> {
#[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<PathBuf> {
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<PathBuf> {
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<Output> {
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<Output> {
self.cmd(&["-s", "stop"])
}

/// start the nginx binary
pub fn start(&mut self) -> Result<Output> {
self.cmd(&[])
}

/// make sure we stop existing nginx and start new master process
/// intentinally ignore failure in stop
pub fn restart(&mut self) -> Result<Output> {
let _ = self.stop();
self.start()
}

/// replace main config with another config
pub fn copy_main_config<P: AsRef<Path>>(&mut self, conf_path_from: P) -> Result<u64> {
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<P: AsRef<Path>, Q: AsRef<Path>>(
&mut self,
conf_path_from: P,
conf_path_rel_to: Q,
) -> Result<u64> {
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<Q: AsRef<Path>>(&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<P: AsRef<Path>, Q: AsRef<Path>>(
&mut self,
module_path_from: P,
module_path_rel_to: Q,
) -> Result<u64> {
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
}
}

0 comments on commit e0c066c

Please sign in to comment.