phper_test/
fpm.rs

1// Copyright (c) 2022 PHPER Framework Team
2// PHPER is licensed under Mulan PSL v2.
3// You can use this software according to the terms and conditions of the Mulan
4// PSL v2. You may obtain a copy of Mulan PSL v2 at:
5//          http://license.coscl.org.cn/MulanPSL2
6// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
7// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9// See the Mulan PSL v2 for more details.
10
11//! Test tools for php fpm program.
12use 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
35/// Start php-fpm process and tokio runtime.
36pub 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        // shutdown hook.
41        unsafe {
42            atexit(teardown);
43        }
44
45        // Run tokio runtime.
46        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        // Run php-fpm.
56        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        // fs::remove_file("/tmp/.php-fpm.log").unwrap();
75
76        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
97/// Start php-fpm and test the url request.
98pub 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}