1use crate::{context::Context, utils::spawn_command};
13use fastcgi_client::{Client, Params, Request};
14use libc::{atexit, kill, pid_t, SIGTERM};
15use once_cell::sync::OnceCell;
16use std::{
17 fs,
18 mem::{forget, ManuallyDrop},
19 path::{Path, PathBuf},
20 process::Child,
21 sync::Mutex,
22 time::Duration,
23};
24use tempfile::NamedTempFile;
25use tokio::{io, net::TcpStream, runtime::Handle, task::block_in_place};
26
27static FPM_HANDLE: OnceCell<Mutex<FpmHandle>> = OnceCell::new();
28
29struct FpmHandle {
30 lib_path: PathBuf,
31 fpm_child: Child,
32 fpm_conf_file: ManuallyDrop<NamedTempFile>,
33}
34
35pub fn setup(lib_path: impl AsRef<Path>) {
37 let lib_path = lib_path.as_ref().to_owned();
38
39 let handle = FPM_HANDLE.get_or_init(|| {
40 unsafe {
42 atexit(teardown);
43 }
44
45 let rt = tokio::runtime::Builder::new_multi_thread()
47 .worker_threads(3)
48 .enable_all()
49 .build()
50 .unwrap();
51 let guard = rt.enter();
52 forget(guard);
53 forget(rt);
54
55 let context = Context::get_global();
57 let php_fpm = context.find_php_fpm().unwrap();
58 let fpm_conf_file = context.create_tmp_fpm_conf_file();
59
60 let argv = [
61 &*php_fpm,
62 "-F",
63 "-n",
64 "-d",
65 &format!("extension={}", lib_path.display()),
66 "-y",
67 fpm_conf_file.path().to_str().unwrap(),
68 ];
69 eprintln!("===== setup php-fpm =====\n{}", argv.join(" "));
70
71 let child = spawn_command(&argv, Some(Duration::from_secs(3)));
72 let log = fs::read_to_string("/tmp/.php-fpm.log").unwrap();
73 eprintln!("===== php-fpm log =====\n{}", log);
74 Mutex::new(FpmHandle {
77 lib_path: lib_path.clone(),
78 fpm_child: child,
79 fpm_conf_file: ManuallyDrop::new(fpm_conf_file),
80 })
81 });
82
83 assert_eq!(handle.lock().unwrap().lib_path, &*lib_path);
84}
85
86extern "C" fn teardown() {
87 let mut fpm_handle = FPM_HANDLE.get().unwrap().lock().unwrap();
88
89 unsafe {
90 ManuallyDrop::drop(&mut fpm_handle.fpm_conf_file);
91
92 let id = fpm_handle.fpm_child.id();
93 kill(id as pid_t, SIGTERM);
94 }
95}
96
97pub fn test_fpm_request(
99 method: &str, root: impl AsRef<Path>, request_uri: &str, content_type: Option<String>,
100 body: Option<Vec<u8>>,
101) {
102 assert!(FPM_HANDLE.get().is_some(), "must call `setup()` first");
103
104 block_in_place(move || {
105 Handle::current().block_on(async move {
106 let root = root.as_ref();
107 let script_name = request_uri.split('?').next().unwrap();
108
109 let mut tmp = root.to_path_buf();
110 tmp.push(script_name.trim_start_matches('/'));
111 let script_filename = tmp.as_path().to_str().unwrap();
112
113 let stream = TcpStream::connect(("127.0.0.1", 9000)).await.unwrap();
114 let local_addr = stream.local_addr().unwrap();
115 let peer_addr = stream.peer_addr().unwrap();
116 let local_ip = local_addr.ip().to_string();
117 let local_port = local_addr.port();
118 let peer_ip = peer_addr.ip().to_string();
119 let peer_port = peer_addr.port();
120
121 let client = Client::new(stream);
122 let mut params = Params::default()
123 .request_method(method)
124 .script_name(request_uri)
125 .script_filename(script_filename)
126 .request_uri(request_uri)
127 .document_uri(script_name)
128 .remote_addr(&local_ip)
129 .remote_port(local_port)
130 .server_addr(&peer_ip)
131 .server_port(peer_port)
132 .server_name("phper-test");
133 if let Some(content_type) = &content_type {
134 params = params.content_type(content_type);
135 }
136 if let Some(body) = &body {
137 params = params.content_length(body.len());
138 }
139
140 let response = if let Some(body) = body {
141 client
142 .execute_once(Request::new(params, body.as_ref()))
143 .await
144 } else {
145 client
146 .execute_once(Request::new(params, &mut io::empty()))
147 .await
148 };
149
150 let output = response.unwrap();
151 let stdout = output.stdout.unwrap_or_default();
152 let stderr = output.stderr.unwrap_or_default();
153
154 let no_error = stderr.is_empty();
155
156 let f = |out: Vec<u8>| {
157 String::from_utf8(out)
158 .map(|out| {
159 if out.is_empty() {
160 "<empty>".to_owned()
161 } else {
162 out
163 }
164 })
165 .unwrap_or_else(|_| "<not utf8 string>".to_owned())
166 };
167
168 eprintln!(
169 "===== request =====\n{}\n===== stdout ======\n{}\n===== stderr ======\n{}",
170 request_uri,
171 f(stdout),
172 f(stderr),
173 );
174
175 assert!(no_error, "request not success: {}", request_uri);
176 });
177 });
178}