diff --git a/Cargo.lock b/Cargo.lock index 51a6e496..cb5e4895 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,26 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + [[package]] name = "cookie" version = "0.18.1" @@ -336,6 +356,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "deranged" version = "0.3.11" @@ -376,6 +402,15 @@ dependencies = [ "syn", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.11" @@ -600,6 +635,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.2" @@ -871,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1158,6 +1199,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1298,10 +1349,12 @@ name = "phper-test" version = "0.15.1" dependencies = [ "cargo_metadata", + "env_logger", "fastcgi-client", "libc", "log", "phper-macros", + "rust-ini", "tempfile", "tokio", ] @@ -1488,6 +1541,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1895,6 +1959,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -2068,6 +2141,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/examples/complex/build.rs b/examples/complex/build.rs index 0c111d7a..a024aa00 100644 --- a/examples/complex/build.rs +++ b/examples/complex/build.rs @@ -9,11 +9,5 @@ // See the Mulan PSL v2 for more details. fn main() { - phper_build::register_configures(); - - #[cfg(target_os = "macos")] - { - println!("cargo:rustc-link-arg=-undefined"); - println!("cargo:rustc-link-arg=dynamic_lookup"); - } + phper_build::register_all(); } diff --git a/examples/complex/tests/integration.rs b/examples/complex/tests/integration.rs index 1a343492..c3831a61 100644 --- a/examples/complex/tests/integration.rs +++ b/examples/complex/tests/integration.rs @@ -8,25 +8,29 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper_test::{cli::test_php_scripts, utils::get_lib_path}; +use phper_test::{cargo::CargoBuilder, cli::test_php_script, log}; use std::{ env, path::{Path, PathBuf}, + sync::LazyLock, }; +pub static DYLIB_PATH: LazyLock = LazyLock::new(|| { + log::setup(); + let result = CargoBuilder::new() + .current_dir(env!("CARGO_MANIFEST_DIR")) + .build() + .unwrap(); + result.get_cdylib().unwrap() +}); + +pub static TESTS_PHP_DIR: LazyLock = LazyLock::new(|| { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("php") +}); + #[test] fn test_php() { - test_php_scripts( - get_lib_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - "complex", - ), - &[&Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("php") - .join("test.php")], - ); + test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("test.php")); } diff --git a/examples/http-client/tests/integration.rs b/examples/http-client/tests/integration.rs index 0e8fa8df..c3831a61 100644 --- a/examples/http-client/tests/integration.rs +++ b/examples/http-client/tests/integration.rs @@ -8,25 +8,29 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper_test::{cli::test_php_scripts, utils::get_lib_path}; +use phper_test::{cargo::CargoBuilder, cli::test_php_script, log}; use std::{ env, path::{Path, PathBuf}, + sync::LazyLock, }; +pub static DYLIB_PATH: LazyLock = LazyLock::new(|| { + log::setup(); + let result = CargoBuilder::new() + .current_dir(env!("CARGO_MANIFEST_DIR")) + .build() + .unwrap(); + result.get_cdylib().unwrap() +}); + +pub static TESTS_PHP_DIR: LazyLock = LazyLock::new(|| { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("php") +}); + #[test] fn test_php() { - test_php_scripts( - get_lib_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - "http_client", - ), - &[&Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests") - .join("php") - .join("test.php")], - ); + test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("test.php")); } diff --git a/examples/http-server/tests/integration.rs b/examples/http-server/tests/integration.rs index 341466b8..fa28decd 100644 --- a/examples/http-server/tests/integration.rs +++ b/examples/http-server/tests/integration.rs @@ -10,25 +10,29 @@ use axum::http::header::CONTENT_TYPE; use hyper::StatusCode; -use phper_test::{cli::test_long_term_php_script_with_condition, utils::get_lib_path}; +use phper_test::{cargo::CargoBuilder, cli::test_long_term_php_script_with_condition, log}; use reqwest::blocking::Client; use std::{ env, path::{Path, PathBuf}, + sync::LazyLock, thread::sleep, time::Duration, }; +pub static DYLIB_PATH: LazyLock = LazyLock::new(|| { + log::setup(); + let result = CargoBuilder::new() + .current_dir(env!("CARGO_MANIFEST_DIR")) + .build() + .unwrap(); + result.get_cdylib().unwrap() +}); + #[test] fn test_php() { test_long_term_php_script_with_condition( - get_lib_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - "http_server", - ), + &*DYLIB_PATH, Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("php") diff --git a/examples/logging/tests/integration.rs b/examples/logging/tests/integration.rs index e46cba8a..658073d2 100644 --- a/examples/logging/tests/integration.rs +++ b/examples/logging/tests/integration.rs @@ -8,55 +8,92 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use phper_test::{cli::test_php_scripts_with_condition, utils::get_lib_path}; +use phper_test::{cargo::CargoBuilder, cli::test_php_script_with_condition, log}; use std::{ env, path::{Path, PathBuf}, str, + sync::LazyLock, }; -#[test] -fn test_php() { - let base_dir = Path::new(env!("CARGO_MANIFEST_DIR")) +pub static DYLIB_PATH: LazyLock = LazyLock::new(|| { + log::setup(); + let result = CargoBuilder::new() + .current_dir(env!("CARGO_MANIFEST_DIR")) + .build() + .unwrap(); + result.get_cdylib().unwrap() +}); + +pub static TESTS_PHP_DIR: LazyLock = LazyLock::new(|| { + Path::new(env!("CARGO_MANIFEST_DIR")) .join("tests") - .join("php"); - - test_php_scripts_with_condition( - get_lib_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("target"), - "logging", - ), - &[ - (&base_dir.join("test_php_say.php"), &|output| { - let stdout = str::from_utf8(&output.stdout).unwrap(); - stdout == "Hello, world!" && output.status.success() - }), - (&base_dir.join("test_php_notice.php"), &|output| { - let stdout = str::from_utf8(&output.stdout).unwrap(); - stdout.contains("Notice:") - && stdout.contains("Something happened: just for test") - && output.status.success() - }), - (&base_dir.join("test_php_warning.php"), &|output| { - let stdout = str::from_utf8(&output.stdout).unwrap(); - stdout.contains("Warning:") - && stdout.contains("Something warning: just for test") - && output.status.success() - }), - (&base_dir.join("test_php_error.php"), &|output| { - let stdout = str::from_utf8(&output.stdout).unwrap(); - stdout.contains("Fatal error:") - && stdout.contains("Something gone failed: just for test") - }), - (&base_dir.join("test_php_deprecated.php"), &|output| { - let stdout = str::from_utf8(&output.stdout).unwrap(); - stdout.contains("Deprecated:") - && stdout.contains("Something deprecated: just for test") - && output.status.success() - }), - ], + .join("php") +}); + +#[test] +fn test_php_say() { + test_php_script_with_condition( + &*DYLIB_PATH, + TESTS_PHP_DIR.join("test_php_say.php"), + |output| { + let stdout = str::from_utf8(&output.stdout).unwrap(); + stdout == "Hello, world!" && output.status.success() + }, + ); +} + +#[test] +fn test_php_notice() { + test_php_script_with_condition( + &*DYLIB_PATH, + TESTS_PHP_DIR.join("test_php_notice.php"), + |output| { + let stdout = str::from_utf8(&output.stdout).unwrap(); + stdout.contains("Notice:") + && stdout.contains("Something happened: just for test") + && output.status.success() + }, + ); +} + +#[test] +fn test_php_warning() { + test_php_script_with_condition( + &*DYLIB_PATH, + TESTS_PHP_DIR.join("test_php_warning.php"), + |output| { + let stdout = str::from_utf8(&output.stdout).unwrap(); + stdout.contains("Warning:") + && stdout.contains("Something warning: just for test") + && output.status.success() + }, + ); +} + +#[test] +fn test_php_error() { + test_php_script_with_condition( + &*DYLIB_PATH, + TESTS_PHP_DIR.join("test_php_error.php"), + |output| { + let stdout = str::from_utf8(&output.stdout).unwrap(); + stdout.contains("Fatal error:") + && stdout.contains("Something gone failed: just for test") + }, + ); +} + +#[test] +fn test_php_deprecated() { + test_php_script_with_condition( + &*DYLIB_PATH, + TESTS_PHP_DIR.join("test_php_deprecated.php"), + |output| { + let stdout = str::from_utf8(&output.stdout).unwrap(); + stdout.contains("Deprecated:") + && stdout.contains("Something deprecated: just for test") + && output.status.success() + }, ); } diff --git a/phper-test/Cargo.toml b/phper-test/Cargo.toml index 0f945320..a30bddbd 100644 --- a/phper-test/Cargo.toml +++ b/phper-test/Cargo.toml @@ -21,10 +21,12 @@ license = { workspace = true } [dependencies] cargo_metadata = "0.20.0" +env_logger = { version = "0.11.8", features = ["kv"] } fastcgi-client = "0.9.0" libc = "0.2.169" log = { version = "0.4.27", features = ["kv"] } phper-macros = { workspace = true } +rust-ini = "0.21.1" tempfile = "3.17.1" tokio = { version = "1.43.0", features = ["net"] } diff --git a/phper-test/etc/php-fpm.conf b/phper-test/etc/php-fpm.conf deleted file mode 100644 index 22a97281..00000000 --- a/phper-test/etc/php-fpm.conf +++ /dev/null @@ -1,20 +0,0 @@ -; Copyright (c) 2022 PHPER Framework Team -; PHPER is licensed under Mulan PSL v2. -; You can use this software according to the terms and conditions of the Mulan -; PSL v2. You may obtain a copy of Mulan PSL v2 at: -; http://license.coscl.org.cn/MulanPSL2 -; THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -; KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -; NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -; See the Mulan PSL v2 for more details. - -[global] -error_log = /tmp/.php-fpm.log - -[www] -user = $USER -group = $USER -listen = 127.0.0.1:9000 -pm = static -pm.max_children = 6 -pm.max_requests = 500 diff --git a/phper-test/src/cli.rs b/phper-test/src/cli.rs index b4757bce..4f2d34fd 100644 --- a/phper-test/src/cli.rs +++ b/phper-test/src/cli.rs @@ -24,9 +24,9 @@ use std::{ /// - `lib_path` is the path of extension lib. /// /// - `script` is the path of your php test script. -pub fn test_php_script(lib_path: impl AsRef, scripts: impl AsRef) { +pub fn test_php_script(lib_path: impl AsRef, script: impl AsRef) { let condition = |output: Output| output.status.success(); - let scripts = Some(scripts); + let scripts = Some(script); let scripts = scripts .iter() .map(|s| (s as _, &condition as _)) @@ -34,12 +34,21 @@ pub fn test_php_script(lib_path: impl AsRef, scripts: impl AsRef) { test_php_scripts_with_condition(lib_path, &scripts); } -/// Check your extension by executing the php script, if the all executing -/// return success, than the test is pass. +/// Check your extension by executing multiple php scripts with success +/// condition. /// -/// - `lib_path` is the path of extension lib. +/// This function executes multiple PHP scripts and checks if all of them return +/// success status. It's a convenience wrapper around +/// `test_php_scripts_with_condition` with a default success condition. +/// +/// # Arguments /// -/// - `scripts` is the path of your php test scripts. +/// * `lib_path` - The path to the extension library file +/// * `scripts` - A slice of references to PHP script paths to execute +/// +/// # Panics +/// +/// Panics if any script execution fails or returns non-success status. pub fn test_php_scripts(lib_path: impl AsRef, scripts: &[&dyn AsRef]) { let condition = |output: Output| output.status.success(); let scripts = scripts @@ -49,19 +58,88 @@ pub fn test_php_scripts(lib_path: impl AsRef, scripts: &[&dyn AsRef] test_php_scripts_with_condition(lib_path, &scripts); } -/// Script and condition pair. +/// Check your extension by executing a single php script with custom condition. +/// +/// This function allows you to specify a custom condition function to check the +/// execution result of a PHP script, providing more flexibility than the +/// default success-only check. +/// +/// # Arguments +/// +/// * `lib_path` - The path to the extension library file +/// * `script` - The path to the PHP script to execute +/// * `condition` - A function that takes the command output and returns true if +/// the test passes +/// +/// # Panics +/// +/// Panics if the script execution fails or the condition function returns +/// false. +/// +/// # Examples +/// +/// ```rust,no_run +/// use phper_test::cli::test_php_script_with_condition; +/// use std::process::Output; +/// +/// // Test that script outputs specific text +/// let condition = +/// |output: Output| String::from_utf8_lossy(&output.stdout).contains("expected output"); +/// test_php_script_with_condition("/path/to/extension.so", "test.php", condition); +/// ``` +pub fn test_php_script_with_condition( + lib_path: impl AsRef, script: impl AsRef, condition: impl Fn(Output) -> bool, +) { + let scripts = Some(script); + let scripts = scripts + .iter() + .map(|s| (s as _, &condition as _)) + .collect::>(); + test_php_scripts_with_condition(lib_path, &scripts); +} + +/// Type alias for script and condition pair used in batch testing. +/// +/// The first element is a reference to a path-like object representing the PHP +/// script, and the second element is a function that validates the execution +/// output. pub type ScriptCondition<'a> = (&'a dyn AsRef, &'a dyn Fn(Output) -> bool); -/// Check your extension by executing the php script, if the all your specified -/// checkers are pass, than the test is pass. +/// Check your extension by executing multiple php scripts with custom +/// conditions. +/// +/// This is the most flexible testing function that allows you to specify +/// different validation conditions for each script. It executes each script +/// with the extension loaded and validates the results using the provided +/// condition functions. +/// +/// # Arguments +/// +/// * `lib_path` - The path to the extension library file +/// * `scripts` - A slice of tuples containing script paths and their validation +/// conditions +/// +/// # Panics +/// +/// Panics if any script execution fails or if any condition function returns +/// false. The panic message will include the path of the failing script. +/// +/// # Examples /// -/// - `exec_path` is the path of the make executable, which will be used to -/// detect the path of extension lib. +/// ```rust,no_run +/// use phper_test::cli::{ScriptCondition, test_php_scripts_with_condition}; +/// use std::process::Output; /// -/// - `scripts` is the slice of the tuple, format is `(path of your php test -/// script, checker function or closure)`. +/// let success_condition = |output: Output| output.status.success(); +/// let custom_condition = +/// |output: Output| String::from_utf8_lossy(&output.stdout).contains("custom check"); /// -/// See [example logging integration test](https://github.com/phper-framework/phper/blob/master/examples/logging/tests/integration.rs). +/// let scripts: &[ScriptCondition] = &[ +/// (&"test1.php", &success_condition), +/// (&"test2.php", &custom_condition), +/// ]; +/// test_php_scripts_with_condition("/path/to/extension.so", scripts); +/// ``` pub fn test_php_scripts_with_condition( lib_path: impl AsRef, scripts: &[ScriptCondition<'_>], ) { diff --git a/phper-test/src/context.rs b/phper-test/src/context.rs index f0941ece..1f2c045e 100644 --- a/phper-test/src/context.rs +++ b/phper-test/src/context.rs @@ -9,11 +9,11 @@ // See the Mulan PSL v2 for more details. use crate::utils; +use ini::Ini; use std::{ env, ffi::OsStr, fs::read_to_string, - io::Write, ops::{Deref, DerefMut}, path::Path, process::Command, @@ -111,11 +111,23 @@ impl Context { }) } - pub fn create_tmp_fpm_conf_file(&self) -> NamedTempFile { + pub fn create_tmp_fpm_conf_file(&self, port: u16, error_log: &Path) -> NamedTempFile { let mut tmp = NamedTempFile::new().unwrap(); let file = tmp.as_file_mut(); - file.write_all(include_bytes!("../etc/php-fpm.conf")) - .unwrap(); + + let mut conf = Ini::new(); + conf.with_section(Some("global")) + .set("error_log", error_log.display().to_string()); + conf.with_section(Some("www")) + .set("user", "$USER") + .set("group", "$USER") + .set("listen", format!("127.0.0.1:{port}")) + .set("pm", "static") + .set("pm.max_children", "6") + .set("pm.max_requests", "500"); + + conf.write_to(file).unwrap(); + tmp } } diff --git a/phper-test/src/fpm.rs b/phper-test/src/fpm.rs index 96360373..9f0de6cb 100644 --- a/phper-test/src/fpm.rs +++ b/phper-test/src/fpm.rs @@ -16,6 +16,7 @@ use log::debug; use std::{ borrow::Cow, fs, + net::TcpListener, path::Path, process::Child, sync::{Mutex, Once, OnceLock}, @@ -39,9 +40,28 @@ pub struct FpmHandle { fpm_child: Child, /// Temporary configuration file for PHP-FPM fpm_conf_file: Mutex>, + /// The port number that PHP-FPM is listening on + port: u16, } impl FpmHandle { + /// Finds an available port on localhost. + /// + /// # Returns + /// + /// An available port number + /// + /// # Panics + /// + /// Panics if no available port can be found + fn find_available_port() -> u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to an available port") + .local_addr() + .expect("Failed to get local address") + .port() + } + /// Sets up and starts a PHP-FPM process for testing. /// /// This method creates a singleton FpmHandle instance that manages a @@ -63,17 +83,18 @@ impl FpmHandle { /// - PHP-FPM binary cannot be found /// - FPM process fails to start /// - FpmHandle has already been initialized - pub fn setup(lib_path: impl AsRef) -> &'static FpmHandle { + pub fn setup(lib_path: impl AsRef, log_path: impl AsRef) -> &'static FpmHandle { if FPM_HANDLE.get().is_some() { panic!("FPM_HANDLE has set"); } let lib_path = lib_path.as_ref().to_owned(); + let port = Self::find_available_port(); // Run php-fpm. let context = Context::get_global(); let php_fpm = context.find_php_fpm().unwrap(); - let fpm_conf_file = context.create_tmp_fpm_conf_file(); + let fpm_conf_file = context.create_tmp_fpm_conf_file(port, log_path.as_ref()); let argv = [ &*php_fpm, @@ -84,16 +105,16 @@ impl FpmHandle { "-y", &fpm_conf_file.path().display().to_string(), ]; - debug!(argv:% = argv.join(" "); "setup php-fpm"); + debug!(argv:% = argv.join(" "), port:% = port; "setup php-fpm"); let child = spawn_command(&argv, Some(Duration::from_secs(3))); - let log = fs::read_to_string("/tmp/.php-fpm.log").unwrap(); + let log = fs::read_to_string(log_path.as_ref()).unwrap(); debug!(log:%; "php-fpm log"); - // fs::remove_file("/tmp/.php-fpm.log").unwrap(); let handle = FpmHandle { fpm_child: child, fpm_conf_file: Mutex::new(Some(fpm_conf_file)), + port, }; // shutdown hook. @@ -135,7 +156,7 @@ impl FpmHandle { /// /// Panics if: /// - FpmHandle has not been initialized via `setup()` first - /// - Cannot connect to the FPM process on port 9000 + /// - Cannot connect to the FPM process on port /// - The PHP script execution results in errors (stderr is not empty) pub async fn test_fpm_request( &self, method: &str, root: impl AsRef, request_uri: &str, @@ -148,7 +169,7 @@ impl FpmHandle { tmp.push(script_name.trim_start_matches('/')); let script_filename = tmp.as_path().to_str().unwrap(); - let stream = TcpStream::connect(("127.0.0.1", 9000)).await.unwrap(); + let stream = TcpStream::connect(("127.0.0.1", self.port)).await.unwrap(); let local_addr = stream.local_addr().unwrap(); let peer_addr = stream.peer_addr().unwrap(); let local_ip = local_addr.ip().to_string(); diff --git a/phper-test/src/lib.rs b/phper-test/src/lib.rs index 9b98bbab..e3609076 100644 --- a/phper-test/src/lib.rs +++ b/phper-test/src/lib.rs @@ -18,4 +18,5 @@ pub mod cargo; pub mod cli; mod context; pub mod fpm; +pub mod log; pub mod utils; diff --git a/phper-test/src/log.rs b/phper-test/src/log.rs new file mode 100644 index 00000000..43a9e94d --- /dev/null +++ b/phper-test/src/log.rs @@ -0,0 +1,71 @@ +// Copyright (c) 2025 PHPER Framework Team +// PHPER is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! Logging utilities for phper tests. +//! +//! This module provides a centralized logging setup specifically designed for +//! test environments. It configures the env_logger with custom formatting to +//! display key-value pairs in a structured format. + +use env_logger::fmt::Formatter; +use log::kv::{self, Key, Value}; +use std::sync::Once; + +/// Sets up the logger for test environments. +/// +/// This function initializes the env_logger with custom formatting that +/// displays key-value pairs in a structured format with separators. The setup +/// is guaranteed to run only once using `std::sync::Once`. +/// +/// # Features +/// - Uses default environment configuration +/// - Enables test mode formatting +/// - Custom key-value formatter that displays each pair with visual separators +pub fn setup() { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + env_logger::Builder::from_default_env() + .default_format() + .is_test(true) + .format_key_values(|buf, args| { + use std::io::Write as _; + + /// A visitor implementation for formatting key-value pairs. + /// + /// This visitor formats each key-value pair with visual + /// separators, making log output more readable + /// in test environments. + struct Visitor<'a>(&'a mut Formatter); + + impl<'kvs> kv::VisitSource<'kvs> for Visitor<'kvs> { + /// Visits and formats a single key-value pair. + /// + /// # Arguments + /// * `key` - The key of the key-value pair + /// * `value` - The value of the key-value pair + /// + /// # Returns + /// Returns `Ok(())` on successful formatting, or a + /// `kv::Error` if formatting fails. + fn visit_pair( + &mut self, key: Key<'kvs>, value: Value<'kvs>, + ) -> Result<(), kv::Error> { + writeln!(self.0).unwrap(); + writeln!(self.0, "===== {} =====", key).unwrap(); + writeln!(self.0, "{}", value).unwrap(); + Ok(()) + } + } + args.visit(&mut Visitor(buf)).unwrap(); + Ok(()) + }) + .init(); + }); +} diff --git a/tests/integration/tests/cli.rs b/tests/integration/tests/cli.rs index 81507e39..a8c0c94d 100644 --- a/tests/integration/tests/cli.rs +++ b/tests/integration/tests/cli.rs @@ -10,95 +10,80 @@ mod common; -use crate::common::{DYLIB_PATH, TESTS_PHP_DIR, setup}; +use crate::common::{DYLIB_PATH, TESTS_PHP_DIR}; use phper_test::cli::test_php_script; #[test] fn test_phpinfo() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("phpinfo.php")); } #[test] fn test_arguments() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("arguments.php")); } #[test] fn test_arrays() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("arrays.php")); } #[test] fn test_classes() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("classes.php")); } #[test] fn test_functions() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("functions.php")); } #[test] fn test_objects() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("objects.php")); } #[test] fn test_strings() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("strings.php")); } #[test] fn test_values() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("values.php")); } #[test] fn test_constants() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("constants.php")); } #[test] fn test_ini() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("ini.php")); } #[test] fn test_references() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("references.php")); } #[test] fn test_errors() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("errors.php")); } #[test] fn test_reflection() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("reflection.php")); } #[test] fn test_typehints() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("typehints.php")); } #[test] fn test_enums() { - setup(); test_php_script(&*DYLIB_PATH, TESTS_PHP_DIR.join("enums.php")); } diff --git a/tests/integration/tests/common/mod.rs b/tests/integration/tests/common/mod.rs index edf31f40..c6e446e3 100644 --- a/tests/integration/tests/common/mod.rs +++ b/tests/integration/tests/common/mod.rs @@ -8,15 +8,14 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -use env_logger::fmt::Formatter; -use log::kv::{self, Key, Value}; -use phper_test::{cargo::CargoBuilder, fpm::FpmHandle}; +use phper_test::{cargo::CargoBuilder, fpm::FpmHandle, log}; use std::{ path::{Path, PathBuf}, - sync::{LazyLock, Once}, + sync::LazyLock, }; pub static DYLIB_PATH: LazyLock = LazyLock::new(|| { + log::setup(); let result = CargoBuilder::new() .current_dir(env!("CARGO_MANIFEST_DIR")) .build() @@ -31,30 +30,5 @@ pub static TESTS_PHP_DIR: LazyLock = LazyLock::new(|| { }); #[allow(dead_code)] -pub static FPM_HANDLE: LazyLock<&FpmHandle> = LazyLock::new(|| FpmHandle::setup(&*DYLIB_PATH)); - -pub fn setup() { - static ONCE: Once = Once::new(); - ONCE.call_once(|| { - env_logger::Builder::from_default_env() - .default_format() - .is_test(true) - .format_key_values(|buf, args| { - use std::io::Write as _; - struct Visitor<'a>(&'a mut Formatter); - impl<'kvs> kv::VisitSource<'kvs> for Visitor<'kvs> { - fn visit_pair( - &mut self, key: Key<'kvs>, value: Value<'kvs>, - ) -> Result<(), kv::Error> { - writeln!(self.0).unwrap(); - writeln!(self.0, "===== {} =====", key).unwrap(); - writeln!(self.0, "{}", value).unwrap(); - Ok(()) - } - } - args.visit(&mut Visitor(buf)).unwrap(); - Ok(()) - }) - .init(); - }); -} +pub static FPM_HANDLE: LazyLock<&FpmHandle> = + LazyLock::new(|| FpmHandle::setup(&*DYLIB_PATH, "/tmp/.php-fpm.log")); diff --git a/tests/integration/tests/fpm.rs b/tests/integration/tests/fpm.rs index b36b97a1..8c7afa39 100644 --- a/tests/integration/tests/fpm.rs +++ b/tests/integration/tests/fpm.rs @@ -10,11 +10,10 @@ mod common; -use crate::common::{FPM_HANDLE, TESTS_PHP_DIR, setup}; +use crate::common::{FPM_HANDLE, TESTS_PHP_DIR}; #[tokio::test] async fn test_phpinfo() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/phpinfo.php", None, None) .await; @@ -22,7 +21,6 @@ async fn test_phpinfo() { #[tokio::test] async fn test_arguments() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/arguments.php", None, None) .await; @@ -30,7 +28,6 @@ async fn test_arguments() { #[tokio::test] async fn test_arrays() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/arrays.php", None, None) .await; @@ -38,7 +35,6 @@ async fn test_arrays() { #[tokio::test] async fn test_classes() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/classes.php", None, None) .await; @@ -46,7 +42,6 @@ async fn test_classes() { #[tokio::test] async fn test_functions() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/functions.php", None, None) .await; @@ -54,7 +49,6 @@ async fn test_functions() { #[tokio::test] async fn test_objects() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/objects.php", None, None) .await; @@ -62,7 +56,6 @@ async fn test_objects() { #[tokio::test] async fn test_strings() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/strings.php", None, None) .await; @@ -70,7 +63,6 @@ async fn test_strings() { #[tokio::test] async fn test_values() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/values.php", None, None) .await; @@ -78,7 +70,6 @@ async fn test_values() { #[tokio::test] async fn test_constants() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/constants.php", None, None) .await; @@ -86,7 +77,6 @@ async fn test_constants() { #[tokio::test] async fn test_ini() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/ini.php", None, None) .await; @@ -94,7 +84,6 @@ async fn test_ini() { #[tokio::test] async fn test_references() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/references.php", None, None) .await; @@ -102,7 +91,6 @@ async fn test_references() { #[tokio::test] async fn test_errors() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/errors.php", None, None) .await; @@ -110,7 +98,6 @@ async fn test_errors() { #[tokio::test] async fn test_reflection() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/reflection.php", None, None) .await; @@ -118,7 +105,6 @@ async fn test_reflection() { #[tokio::test] async fn test_typehints() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/typehints.php", None, None) .await; @@ -126,7 +112,6 @@ async fn test_typehints() { #[tokio::test] async fn test_enums() { - setup(); FPM_HANDLE .test_fpm_request("GET", &*TESTS_PHP_DIR, "/enums.php", None, None) .await;