#![warn(rust_2018_idioms, clippy::dbg_macro, clippy::print_stdout)]
use once_cell::sync::OnceCell;
use std::{
env,
ffi::{OsStr, OsString},
fmt::Debug,
fs::read_to_string,
io::Write,
path::{Path, PathBuf},
process::{Command, Output},
};
use tempfile::NamedTempFile;
pub fn test_php_scripts(exe_path: impl AsRef<Path>, scripts: &[&dyn AsRef<Path>]) {
let condition = |output: Output| output.status.success();
let scripts = scripts
.into_iter()
.map(|s| (*s, &condition as _))
.collect::<Vec<_>>();
test_php_scripts_with_condition(exe_path, &*scripts);
}
pub fn test_php_scripts_with_condition(
exe_path: impl AsRef<Path>,
scripts: &[(&dyn AsRef<Path>, &dyn Fn(Output) -> bool)],
) {
let context = php_context();
let lib_path = get_lib_path(exe_path);
let mut out_ini_temp_file = NamedTempFile::new().unwrap();
let out_ini_file = out_ini_temp_file.as_file_mut();
out_ini_file
.write_all(context.ini_content.as_bytes())
.unwrap();
out_ini_file
.write_fmt(format_args!("extension={}\n", lib_path.to_str().unwrap()))
.unwrap();
for (script, condition) in scripts {
let script = script.as_ref();
let mut cmd = Command::new(&context.php_bin);
let args = &[
"-n",
"-c",
out_ini_temp_file.path().to_str().unwrap(),
script.to_str().unwrap(),
];
cmd.args(args);
let output = cmd.output().unwrap();
let path = script.to_str().unwrap();
let mut stdout = String::from_utf8(output.stdout.clone()).unwrap();
if stdout.is_empty() {
stdout.push_str("<empty>");
}
let mut stderr = String::from_utf8(output.stderr.clone()).unwrap();
if stderr.is_empty() {
stderr.push_str("<empty>");
}
println!(
"===== command =====\n{} {}\n===== stdout ======\n{}\n===== stderr ======\n{}",
&context.php_bin,
args.join(" "),
stdout,
stderr,
);
#[cfg(target_os = "linux")]
if output.status.code().is_none() {
use std::os::unix::process::ExitStatusExt;
println!(
"===== signal ======\nExitStatusExt is None, the signal is: {:?}",
output.status.signal()
);
}
if !condition(output) {
panic!("test php file `{}` failed", path);
}
}
}
struct Context {
php_bin: String,
ini_content: String,
}
fn php_context() -> &'static Context {
static CONTEXT: OnceCell<Context> = OnceCell::new();
&CONTEXT.get_or_init(|| {
let mut ini_content = String::new();
let php_config = env::var("PHP_CONFIG").unwrap_or("php-config".to_string());
let php_bin = execute_command(&[php_config.as_str(), "--php-binary"]);
let ini_file = execute_command(&[
php_bin.as_str(),
"-d",
"display_errors=stderr",
"-r",
"echo php_ini_loaded_file();",
]);
let ini_files = execute_command(&[
php_bin.as_str(),
"-d",
"display_errors=stderr",
"-r",
"echo php_ini_scanned_files();",
]);
if !ini_file.is_empty() {
ini_content.push_str(&read_to_string(ini_file).unwrap());
}
if !ini_files.is_empty() {
for file in ini_files.split(',') {
let file = file.trim();
if !file.is_empty() {
ini_content.push_str(&read_to_string(file).unwrap());
}
}
}
Context {
php_bin,
ini_content,
}
})
}
fn execute_command<S: AsRef<OsStr> + Debug>(argv: &[S]) -> String {
let mut command = Command::new(&argv[0]);
command.args(&argv[1..]);
let output = command
.output()
.expect(&format!("Execute command {:?} failed", &argv))
.stdout;
String::from_utf8(output).unwrap().trim().to_owned()
}
fn get_lib_path(exe_path: impl AsRef<Path>) -> PathBuf {
let exe_path = exe_path.as_ref();
let exe_stem = exe_path
.file_stem()
.expect("failed to get current exe stem")
.to_str()
.expect("failed to convert to utf-8 str");
let target_dir = exe_path
.parent()
.expect("failed to get current exe directory");
let mut ext_name = OsString::new();
ext_name.push("lib");
ext_name.push(exe_stem.replace('-', "_"));
#[cfg(target_os = "linux")]
ext_name.push(".so");
#[cfg(target_os = "macos")]
ext_name.push(".dylib");
#[cfg(target_os = "windows")]
ext_name.push(".dll");
target_dir.join(ext_name)
}