summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Zelenka <bukka@php.net>2018-06-07 17:21:54 +0100
committerJakub Zelenka <bukka@php.net>2018-06-12 17:59:28 +0100
commitea592e6b6c43b7c5ebedf63254b8088f741e276c (patch)
treed07c7b34a369bfb915d8c5c274b58e8e656a9cde
parent62554efffeddb3149254911c529d9ab8d31ff5c3 (diff)
downloadphp-git-ea592e6b6c43b7c5ebedf63254b8088f741e276c.tar.gz
Rewrite FPM tests
-rw-r--r--sapi/fpm/tests/002.phpt51
-rw-r--r--sapi/fpm/tests/003.phpt54
-rw-r--r--sapi/fpm/tests/004.phpt62
-rw-r--r--sapi/fpm/tests/005.phpt60
-rw-r--r--sapi/fpm/tests/006.phpt60
-rw-r--r--sapi/fpm/tests/007.phpt71
-rw-r--r--sapi/fpm/tests/008.phpt100
-rw-r--r--sapi/fpm/tests/009.phpt53
-rw-r--r--sapi/fpm/tests/010.phpt87
-rw-r--r--sapi/fpm/tests/011.phpt53
-rw-r--r--sapi/fpm/tests/012.phpt79
-rw-r--r--sapi/fpm/tests/013.phpt54
-rw-r--r--sapi/fpm/tests/014.phpt54
-rw-r--r--sapi/fpm/tests/015.phpt89
-rw-r--r--sapi/fpm/tests/016.phpt101
-rw-r--r--sapi/fpm/tests/017.phpt67
-rw-r--r--sapi/fpm/tests/019.phpt79
-rw-r--r--sapi/fpm/tests/020.phpt75
-rw-r--r--sapi/fpm/tests/021-uds-acl.phpt102
-rw-r--r--sapi/fpm/tests/022-cve-2016-5385.phpt81
-rw-r--r--sapi/fpm/tests/bug68381-log-level-warning.phpt40
-rw-r--r--sapi/fpm/tests/bug68391-conf-include-order.phpt53
-rw-r--r--sapi/fpm/tests/bug68420-ipv4-all-addresses.phpt42
-rw-r--r--sapi/fpm/tests/bug68421-ipv6-access-log.phpt47
-rw-r--r--sapi/fpm/tests/bug68423-multi-pool-all-pms.phpt55
-rw-r--r--sapi/fpm/tests/bug68428-ipv6-allowed-clients.phpt45
-rw-r--r--sapi/fpm/tests/bug68442-signal-reload.phpt47
-rw-r--r--sapi/fpm/tests/bug68458-pm-no-start-server.phpt42
-rw-r--r--sapi/fpm/tests/bug72573-http-proxy.phpt66
-rw-r--r--sapi/fpm/tests/fastcgi_finish_request_basic.phpt45
-rw-r--r--sapi/fpm/tests/fcgi.inc190
-rw-r--r--sapi/fpm/tests/include.inc131
-rw-r--r--sapi/fpm/tests/logtool.inc476
-rw-r--r--sapi/fpm/tests/main-global-prefix.phpt50
-rw-r--r--sapi/fpm/tests/main-version.phpt (renamed from sapi/fpm/tests/001.phpt)4
-rw-r--r--sapi/fpm/tests/pool-apparmor-basic.phpt (renamed from sapi/fpm/tests/apparmor.phpt)49
-rw-r--r--sapi/fpm/tests/pool-prefix.phpt54
-rw-r--r--sapi/fpm/tests/proc-no-start-server.phpt45
-rw-r--r--sapi/fpm/tests/proc-user-ignored.phpt46
-rw-r--r--sapi/fpm/tests/response.inc281
-rw-r--r--sapi/fpm/tests/skipapparmor.inc30
-rw-r--r--sapi/fpm/tests/skipif.inc18
-rw-r--r--sapi/fpm/tests/socket-invalid-allowed-clients.phpt48
-rw-r--r--sapi/fpm/tests/socket-ipv4-allowed-clients.phpt45
-rw-r--r--sapi/fpm/tests/socket-ipv4-basic.phpt37
-rw-r--r--sapi/fpm/tests/socket-ipv6-any.phpt44
-rw-r--r--sapi/fpm/tests/socket-ipv6-basic.phpt40
-rw-r--r--sapi/fpm/tests/socket-uds-acl.phpt87
-rw-r--r--sapi/fpm/tests/socket-uds-basic.phpt40
-rw-r--r--sapi/fpm/tests/status-basic.phpt50
-rw-r--r--sapi/fpm/tests/status.inc199
-rw-r--r--sapi/fpm/tests/tester.inc1183
52 files changed, 3381 insertions, 1680 deletions
diff --git a/sapi/fpm/tests/002.phpt b/sapi/fpm/tests/002.phpt
deleted file mode 100644
index 5ad9e4bd5c..0000000000
--- a/sapi/fpm/tests/002.phpt
+++ /dev/null
@@ -1,51 +0,0 @@
---TEST--
-FPM: Startup and connect
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = 127.0.0.1:$port
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- $i = 0;
- while (($i++ < 60) && !($fp = @fsockopen('127.0.0.1', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Done\n";
- fclose($fp);
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-Done
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/003.phpt b/sapi/fpm/tests/003.phpt
deleted file mode 100644
index 8accbd0d10..0000000000
--- a/sapi/fpm/tests/003.phpt
+++ /dev/null
@@ -1,54 +0,0 @@
---TEST--
-FPM: Test IPv6 support
---SKIPIF--
-<?php include "skipif.inc";
- @stream_socket_client('tcp://[::1]:0', $errno);
- if ($errno != 111) die('skip IPv6 not supported.');
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = [::1]:$port
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- $i = 0;
- while (($i++ < 60) && !($fp = fsockopen('[::1]', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Done\n";
- fclose($fp);
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-Done
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/004.phpt b/sapi/fpm/tests/004.phpt
deleted file mode 100644
index 4375f8d0d0..0000000000
--- a/sapi/fpm/tests/004.phpt
+++ /dev/null
@@ -1,62 +0,0 @@
---TEST--
-FPM: Test IPv4/IPv6 support
---SKIPIF--
-<?php include "skipif.inc";
- @stream_socket_client('tcp://[::1]:0', $errno);
- if ($errno != 111) die('skip IPv6 not supported.');
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = [::]:$port
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- $i = 0;
- while (($i++ < 60) && !($fp = @fsockopen('127.0.0.1', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Done IPv4\n";
- fclose($fp);
- }
- while (($i++ < 60) && !($fp = @fsockopen('[::1]', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Done IPv6\n";
- fclose($fp);
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-Done IPv4
-Done IPv6
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/005.phpt b/sapi/fpm/tests/005.phpt
deleted file mode 100644
index 6c8210ec8e..0000000000
--- a/sapi/fpm/tests/005.phpt
+++ /dev/null
@@ -1,60 +0,0 @@
---TEST--
-FPM: Test IPv4 allowed clients
---SKIPIF--
-<?php include "skipif.inc";
- @stream_socket_client('tcp://[::1]:0', $errno);
- if ($errno != 111) die('skip IPv6 not supported.');
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = [::]:$port
-listen.allowed_clients = 127.0.0.1
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- run_request('127.0.0.1', $port);
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
- try {
- run_request('[::1]', $port);
- echo "IPv6 ok\n";
- } catch (Exception $e) {
- echo "IPv6 error\n";
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-IPv4 ok
-IPv6 error
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/006.phpt b/sapi/fpm/tests/006.phpt
deleted file mode 100644
index e552087335..0000000000
--- a/sapi/fpm/tests/006.phpt
+++ /dev/null
@@ -1,60 +0,0 @@
---TEST--
-FPM: Test IPv6 allowed clients (bug #68428)
---SKIPIF--
-<?php include "skipif.inc";
- @stream_socket_client('tcp://[::1]:0', $errno);
- if ($errno != 111) die('skip IPv6 not supported.');
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = [::]:$port
-listen.allowed_clients = ::1
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- run_request('127.0.0.1', $port);
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
- try {
- run_request('[::1]', $port);
- echo "IPv6 ok\n";
- } catch (Exception $e) {
- echo "IPv6 error\n";
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-IPv4 error
-IPv6 ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/007.phpt b/sapi/fpm/tests/007.phpt
deleted file mode 100644
index 6329af209a..0000000000
--- a/sapi/fpm/tests/007.phpt
+++ /dev/null
@@ -1,71 +0,0 @@
---TEST--
-FPM: Test IPv6 all addresses and access_log (bug #68421)
---SKIPIF--
-<?php include "skipif.inc";
- @stream_socket_client('tcp://[::1]:0', $errno);
- if ($errno != 111) die('skip IPv6 not supported.');
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = [::]:$port
-access.log = $accfile
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
- try {
- var_dump(strpos(run_request('[::1]', $port), 'pong'));
- echo "IPv6 ok\n";
- } catch (Exception $e) {
- echo "IPv6 error\n";
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-
- echo file_get_contents($accfile);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-IPv4 ok
-int(%d)
-IPv6 ok
-127.0.0.1 %s "GET /ping" 200
-::1 %s "GET /ping" 200
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
- $accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
- @unlink($accfile);
-?>
diff --git a/sapi/fpm/tests/008.phpt b/sapi/fpm/tests/008.phpt
deleted file mode 100644
index d5fe05ba76..0000000000
--- a/sapi/fpm/tests/008.phpt
+++ /dev/null
@@ -1,100 +0,0 @@
---TEST--
-FPM: Test multi pool (dynamic + ondemand + static) (bug #68423)
---SKIPIF--
-<?php
-include "skipif.inc";
-
-$cfg = <<<EOT
-[global]
-error_log = /dev/null
-[poold_ondemand]
-listen=127.0.0.1:9000
-pm = ondemand
-pm.max_children = 2
-pm.process_idle_timeout = 10
-EOT;
-
-if (test_fpm_conf($cfg, $msg) == false) {
- die("skip " . $msg);
-}
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port1 = 9000+PHP_INT_SIZE;
-$port2 = 9001+PHP_INT_SIZE;
-$port3 = 9002+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[pool_dynamic]
-listen = 127.0.0.1:$port1
-ping.path = /ping
-ping.response = pong-dynamic
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-[poold_ondemand]
-listen = 127.0.0.1:$port2
-ping.path = /ping
-ping.response = pong-on-demand
-pm = ondemand
-pm.max_children = 2
-pm.process_idle_timeout = 10
-[pool_static]
-listen = 127.0.0.1:$port3
-ping.path = /ping
-ping.response = pong-static
-pm = static
-pm.max_children = 2
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('127.0.0.1', $port1), 'pong-dynamic'));
- echo "Dynamic ok\n";
- } catch (Exception $e) {
- echo "Dynamic error\n";
- }
- try {
- var_dump(strpos(run_request('127.0.0.1', $port2), 'pong-on-demand'));
- echo "OnDemand ok\n";
- } catch (Exception $e) {
- echo "OnDemand error\n";
- }
- try {
- var_dump(strpos(run_request('127.0.0.1', $port3), 'pong-static'));
- echo "Static ok\n";
- } catch (Exception $e) {
- echo "Static error\n";
- }
-
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-Dynamic ok
-int(%d)
-OnDemand ok
-int(%d)
-Static ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/009.phpt b/sapi/fpm/tests/009.phpt
deleted file mode 100644
index 34cdffcf83..0000000000
--- a/sapi/fpm/tests/009.phpt
+++ /dev/null
@@ -1,53 +0,0 @@
---TEST--
-FPM: Test Unix Domain Socket
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$socket = dirname(__FILE__).'/php-fpm.sock';
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = $socket
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
- echo "UDS ok\n";
- } catch (Exception $e) {
- echo "UDS error\n";
- }
-
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-UDS ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/010.phpt b/sapi/fpm/tests/010.phpt
deleted file mode 100644
index 49e1a07923..0000000000
--- a/sapi/fpm/tests/010.phpt
+++ /dev/null
@@ -1,87 +0,0 @@
---TEST--
-FPM: Test status page
---SKIPIF--
-<?php include "skipif.inc"; ?>
---XFAIL--
-randomly intermittently failing all the time in CI, with diff:
-017+ active processes: 0
-018+ total processes: 1
-017- active processes: 1
-018- total processes: 2
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = 127.0.0.1:$port
-pm.status_path = /status
-pm = static
-pm.max_children = 1
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- echo run_request('127.0.0.1', $port, '/status');
-
- $html = run_request('127.0.0.1', $port, '/status', 'html');
- var_dump(strpos($html, 'text/html') && strpos($html, 'DOCTYPE') && strpos($html, 'PHP-FPM Status Page'));
-
- $json = run_request('127.0.0.1', $port, '/status', 'json');
- var_dump(strpos($json, 'application/json') && strpos($json, '"pool":"unconfined"'));
-
- $xml = run_request('127.0.0.1', $port, '/status', 'xml');
- var_dump(strpos($xml, 'text/xml') && strpos($xml, '<?xml'));
-
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
-
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-X-Powered-By: PHP/%s
-Expires: %s
-Cache-Control: %s
-Content-type: text/plain%s
-
-pool: unconfined
-process manager: static
-start time: %s
-start since: %d
-accepted conn: 1
-listen queue: 0
-max listen queue: 0
-listen queue len: %d
-idle processes: 0
-active processes: 1
-total processes: 1
-max active processes: 1
-max children reached: 0
-slow requests: 0
-
-bool(true)
-bool(true)
-bool(true)
-IPv4 ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/011.phpt b/sapi/fpm/tests/011.phpt
deleted file mode 100644
index 0b849f873b..0000000000
--- a/sapi/fpm/tests/011.phpt
+++ /dev/null
@@ -1,53 +0,0 @@
---TEST--
-FPM: Test IPv4 all addresses (bug #68420)
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = $port
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
-
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-IPv4 ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/012.phpt b/sapi/fpm/tests/012.phpt
deleted file mode 100644
index d96c53081c..0000000000
--- a/sapi/fpm/tests/012.phpt
+++ /dev/null
@@ -1,79 +0,0 @@
---TEST--
-FPM: Test reload configuration (bug #68442)
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$pidfile = dirname(__FILE__).'/php-fpm.pid';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-pid = $pidfile
-[unconfined]
-listen = 127.0.0.1:$port
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
-
- $pid=file_get_contents($pidfile);
- if ($pid) {
- exec("kill -USR2 $pid");
- } else {
- die("PID not found\n");
- }
- fpm_display_log($tail, 5);
-
- try {
- var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
- echo "IPv4 ok\n";
- } catch (Exception $e) {
- echo "IPv4 error\n";
- }
-
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-IPv4 ok
-[%d-%s-%d %d:%d:%d] NOTICE: Reloading in progress ...
-[%d-%s-%d %d:%d:%d] NOTICE: reloading: %s
-[%d-%s-%d %d:%d:%d] NOTICE: using inherited socket fd=%d, "127.0.0.1:%d"
-[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-int(%d)
-IPv4 ok
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
- $pidfile = dirname(__FILE__).'/php-fpm.pid';
- @unlink($pidfile);
-?>
diff --git a/sapi/fpm/tests/013.phpt b/sapi/fpm/tests/013.phpt
deleted file mode 100644
index 641eb448c4..0000000000
--- a/sapi/fpm/tests/013.phpt
+++ /dev/null
@@ -1,54 +0,0 @@
---TEST--
-FPM: Test for log_level in fpm_unix_init_main #68381
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-log_level = warning
-[unconfined]
-listen = 127.0.0.1:$port
-user = foo
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- $i = 0;
- while (($i++ < 60) && !($fp = @fsockopen('127.0.0.1', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Started\n";
- fclose($fp);
- }
- proc_terminate($fpm);
- if (!feof($tail)) {
- echo stream_get_contents($tail);
- }
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-Started
-Done
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/014.phpt b/sapi/fpm/tests/014.phpt
deleted file mode 100644
index e243ef68dc..0000000000
--- a/sapi/fpm/tests/014.phpt
+++ /dev/null
@@ -1,54 +0,0 @@
---TEST--
-FPM: Test for pm.start_servers default calculation message being a notice and not a warning #68458
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-log_level = warning
-[unconfined]
-listen = 127.0.0.1:$port
-user = foo
-pm = dynamic
-pm.max_children = 5
-;pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- $i = 0;
- while (($i++ < 60) && !($fp = @fsockopen('127.0.0.1', $port))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Started\n";
- fclose($fp);
- }
- proc_terminate($fpm);
- if (!feof($tail)) {
- echo stream_get_contents($tail);
- }
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-Started
-Done
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/015.phpt b/sapi/fpm/tests/015.phpt
deleted file mode 100644
index a3c7ad3eee..0000000000
--- a/sapi/fpm/tests/015.phpt
+++ /dev/null
@@ -1,89 +0,0 @@
---TEST--
-FPM: Test various messages on start, from master and childs
---SKIPIF--
-<?php include "skipif.inc"; ?>
---XFAIL--
-randomly intermittently failing all the time in CI,
-ERROR: unable to read what child say: Bad file descriptor (9)
-catch_workers_output = yes seems not reliable
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$port1 = 9000+PHP_INT_SIZE;
-$port2 = 9001+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-log_level = notice
-[pool1]
-listen = 127.0.0.1:$port1
-listen.allowed_clients=127.0.0.1
-user = foo
-pm = dynamic
-pm.max_children = 5
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-catch_workers_output = yes
-[pool2]
-listen = 127.0.0.1:$port2
-listen.allowed_clients=xxx
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 1
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-catch_workers_output = yes
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- $i = 0;
- while (($i++ < 60) && !($fp = @fsockopen('127.0.0.1', $port1))) {
- usleep(50000);
- }
- if ($fp) {
- echo "Started\n";
- fclose($fp);
- }
- for ($i=0 ; $i<10 ; $i++) {
- try {
- run_request('127.0.0.1', $port1);
- } catch (Exception $e) {
- echo "Error 1\n";
- }
- }
- try {
- run_request('127.0.0.1', $port2);
- } catch (Exception $e) {
- echo "Error 2\n";
- }
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-Started
-Error 2
-[%s] NOTICE: [pool pool1] pm.start_servers is not set. It's been set to 2.
-[%s] NOTICE: [pool pool1] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Wrong IP address 'xxx' in listen.allowed_clients"
-[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: There are no allowed %s"
-[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Connection disallowed: IP address '127.0.0.1' has been dropped."
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-Done
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/016.phpt b/sapi/fpm/tests/016.phpt
deleted file mode 100644
index 04ba4b5f69..0000000000
--- a/sapi/fpm/tests/016.phpt
+++ /dev/null
@@ -1,101 +0,0 @@
---TEST--
-FPM: Test splited configuration and load order #68391
---SKIPIF--
-<?php
-include "skipif.inc";
-
-$cfg = <<<EOT
-[global]
-error_log = /dev/null
-[poold_ondemand]
-listen=127.0.0.1:9000
-pm = ondemand
-pm.max_children = 2
-pm.process_idle_timeout = 10
-EOT;
-
-if (test_fpm_conf($cfg, $msg) == false) {
- die("skip " . $msg);
-}
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = __DIR__.'/php-fpm.log.tmp';
-$logdir = __DIR__.'/conf.d';
-$port = 9000+PHP_INT_SIZE;
-
-// Main configuration
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-log_level = notice
-include = $logdir/*.conf
-EOT;
-
-// Splited configuration
-@mkdir($logdir);
-$i=$port;
-$names = ['cccc', 'aaaa', 'eeee', 'dddd', 'bbbb'];
-foreach($names as $name) {
- $poolcfg = <<<EOT
-[$name]
-listen = 127.0.0.1:$i
-listen.allowed_clients=127.0.0.1
-user = foo
-pm = ondemand
-pm.max_children = 5
-EOT;
- file_put_contents("$logdir/$name.conf", $poolcfg);
- $i++;
-}
-
-// Test
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, count($names)+2);
- $i=$port;
- foreach($names as $name) {
- try {
- run_request('127.0.0.1', $i++);
- echo "OK $name\n";
- } catch (Exception $e) {
- echo "Error 1\n";
- }
- }
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-[%s] NOTICE: [pool aaaa] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: [pool bbbb] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: [pool cccc] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: [pool dddd] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: [pool eeee] 'user' directive is ignored when FPM is not running as root
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-OK cccc
-OK aaaa
-OK eeee
-OK dddd
-OK bbbb
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-Done
---CLEAN--
-<?php
- $logfile = __DIR__.'/php-fpm.log.tmp';
- $logdir = __DIR__.'/conf.d';
- @unlink($logfile);
- foreach(glob("$logdir/*.conf") as $name) {
- unlink($name);
- }
- @rmdir($logdir);
-?>
diff --git a/sapi/fpm/tests/017.phpt b/sapi/fpm/tests/017.phpt
deleted file mode 100644
index 46e5efd3f7..0000000000
--- a/sapi/fpm/tests/017.phpt
+++ /dev/null
@@ -1,67 +0,0 @@
---TEST--
-FPM: Test fastcgi_finish_request function
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = __DIR__.'/php-fpm.log.tmp';
-$srcfile = __DIR__.'/php-fpm.tmp.php';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = 127.0.0.1:$port
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 1
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$code = <<<EOT
-<?php
-echo "Test Start\n";
-fastcgi_finish_request();
-echo "Test End\n";
-EOT;
-file_put_contents($srcfile, $code);
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- $req = run_request('127.0.0.1', $port, $srcfile);
- echo strstr($req, "Test Start");
- echo "Request ok\n";
- } catch (Exception $e) {
- echo "Request error\n";
- }
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-Test Start
-
-Request ok
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-Done
---CLEAN--
-<?php
- $logfile = __DIR__.'/php-fpm.log.tmp';
- $srcfile = __DIR__.'/php-fpm.tmp.php';
- @unlink($logfile);
- @unlink($srcfile);
-?>
diff --git a/sapi/fpm/tests/019.phpt b/sapi/fpm/tests/019.phpt
deleted file mode 100644
index 7bed9e2436..0000000000
--- a/sapi/fpm/tests/019.phpt
+++ /dev/null
@@ -1,79 +0,0 @@
---TEST--
-FPM: Test global prefix
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = 'php-fpm.log.tmp';
-$accfile = 'php-fpm.acc.tmp';
-$slwfile = 'php-fpm.slw.tmp';
-$pidfile = 'php-fpm.pid.tmp';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-pid = $pidfile
-[test]
-listen = 127.0.0.1:$port
-access.log = $accfile
-slowlog = $slwfile;
-request_slowlog_timeout = 1
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail, '--prefix '.__DIR__);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- run_request('127.0.0.1', $port);
- echo "Ping ok\n";
- } catch (Exception $e) {
- echo "Ping error\n";
- }
- printf("File %s %s\n", $logfile, (file_exists(__DIR__.'/'.$logfile) ? "exists" : "missing"));
- printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
- printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
- printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "exists" : "missing"));
-
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
- printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "still exists" : "removed"));
- readfile(__DIR__.'/'.$accfile);
-}
-
-?>
---EXPECTF--
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-Ping ok
-File php-fpm.log.tmp exists
-File php-fpm.acc.tmp exists
-File php-fpm.slw.tmp exists
-File php-fpm.pid.tmp exists
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-File php-fpm.pid.tmp removed
-127.0.0.1 - %s "GET /ping" 200
---CLEAN--
-<?php
- $logfile = __DIR__.'/php-fpm.log.tmp';
- $accfile = __DIR__.'/php-fpm.acc.tmp';
- $slwfile = __DIR__.'/php-fpm.slw.tmp';
- $pidfile = __DIR__.'/php-fpm.pid.tmp';
- @unlink($logfile);
- @unlink($accfile);
- @unlink($slwfile);
- @unlink($pidfile);
-?>
diff --git a/sapi/fpm/tests/020.phpt b/sapi/fpm/tests/020.phpt
deleted file mode 100644
index 8131750015..0000000000
--- a/sapi/fpm/tests/020.phpt
+++ /dev/null
@@ -1,75 +0,0 @@
---TEST--
-FPM: Test pool prefix
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$prefix = __DIR__;
-$logfile = __DIR__.'/php-fpm.log.tmp';
-$accfile = 'php-fpm.acc.tmp';
-$slwfile = 'php-fpm.slw.tmp';
-$pidfile = __DIR__.'/php-fpm.pid.tmp';
-$port = 9000+PHP_INT_SIZE;
-$cfg = <<<EOT
-
-[global]
-error_log = $logfile
-pid = $pidfile
-[test]
-prefix = $prefix;
-listen = 127.0.0.1:$port
-access.log = $accfile
-slowlog = $slwfile;
-request_slowlog_timeout = 1
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- run_request('127.0.0.1', $port);
- echo "Ping ok\n";
- } catch (Exception $e) {
- echo "Ping error\n";
- }
- printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
- printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
-
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
- readfile(__DIR__.'/'.$accfile);
-}
-
-?>
---EXPECTF--
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-Ping ok
-File php-fpm.acc.tmp exists
-File php-fpm.slw.tmp exists
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-127.0.0.1 - %s "GET /ping" 200
---CLEAN--
-<?php
- $logfile = __DIR__.'/php-fpm.log.tmp';
- $accfile = __DIR__.'/php-fpm.acc.tmp';
- $slwfile = __DIR__.'/php-fpm.slw.tmp';
- $pidfile = __DIR__.'/php-fpm.pid.tmp';
- @unlink($logfile);
- @unlink($accfile);
- @unlink($slwfile);
- @unlink($pidfile);
-?>
diff --git a/sapi/fpm/tests/021-uds-acl.phpt b/sapi/fpm/tests/021-uds-acl.phpt
deleted file mode 100644
index 6e9ec08d8b..0000000000
--- a/sapi/fpm/tests/021-uds-acl.phpt
+++ /dev/null
@@ -1,102 +0,0 @@
---TEST--
-FPM: Test Unix Domain Socket with Posix ACL
---SKIPIF--
-<?php
-include "skipif.inc";
-if (!(file_exists('/usr/bin/getfacl') && file_exists('/etc/passwd') && file_exists('/etc/group'))) die ("skip missing getfacl command");
-$cfg = <<<EOT
-[global]
-error_log = /dev/null
-[unconfined]
-listen = 127.0.0.1:9999
-listen.acl_users = nobody
-listen.acl_groups = nobody
-listen.mode = 0600
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-if (test_fpm_conf($cfg, $msg) == false) { die("skip " . $msg); }
-?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$socket = dirname(__FILE__).'/php-fpm.sock';
-
-// Select 3 users and 2 groups known by system (avoid root)
-$users = $groups = [];
-$tmp = file('/etc/passwd');
-for ($i=1 ; $i<=3 ; $i++) {
- $tab = explode(':', $tmp[$i]);
- $users[] = $tab[0];
-}
-$users = implode(',', $users);
-$tmp = file('/etc/group');
-for ($i=1 ; $i<=2 ; $i++) {
- $tab = explode(':', $tmp[$i]);
- $groups[] = $tab[0];
-}
-$groups = implode(',', $groups);
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = $socket
-listen.acl_users = $users
-listen.acl_groups = $groups
-listen.mode = 0600
-ping.path = /ping
-ping.response = pong
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
- echo "UDS ok\n";
- } catch (Exception $e) {
- echo "UDS error\n";
- }
- passthru("/usr/bin/getfacl -cp $socket");
-
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
---EXPECTF--
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-int(%d)
-UDS ok
-user::rw-
-user:%s:rw-
-user:%s:rw-
-user:%s:rw-
-group::---
-group:%s:rw-
-group:%s:rw-
-mask::rw-
-other::---
-
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
---CLEAN--
-<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
diff --git a/sapi/fpm/tests/022-cve-2016-5385.phpt b/sapi/fpm/tests/022-cve-2016-5385.phpt
deleted file mode 100644
index 0bdf238f30..0000000000
--- a/sapi/fpm/tests/022-cve-2016-5385.phpt
+++ /dev/null
@@ -1,81 +0,0 @@
---TEST--
-FPM: HTTP_PROXY - CVE-2016-5385
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-include "include.inc";
-
-$logfile = __DIR__.'/php-fpm.log.tmp';
-$srcfile = __DIR__.'/php-fpm.tmp.php';
-$port = 9000+PHP_INT_SIZE;
-
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[unconfined]
-listen = 127.0.0.1:$port
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 1
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-EOT;
-
-$code = <<<EOT
-<?php
-echo "Test Start\n";
-var_dump(
- @\$_SERVER["HTTP_PROXY"],
- \$_SERVER["HTTP_FOO"],
- getenv("HTTP_PROXY"),
- getenv("HTTP_FOO")
-);
-echo "Test End\n";
-EOT;
-file_put_contents($srcfile, $code);
-
-$fpm = run_fpm($cfg, $tail);
-if (is_resource($fpm)) {
- fpm_display_log($tail, 2);
- try {
- $headers = [
- 'HTTP_FOO' => 'BAR',
- 'HTTP_PROXY' => 'BADPROXY',
- ];
- $req = run_request('127.0.0.1', $port, $srcfile, '', $headers);
- echo strstr($req, "Test Start");
- echo "Request ok\n";
- } catch (Exception $e) {
- echo "Request error\n";
- }
- proc_terminate($fpm);
- fpm_display_log($tail, -1);
- fclose($tail);
- proc_close($fpm);
-}
-
-?>
-Done
---EXPECTF--
-[%s] NOTICE: fpm is running, pid %d
-[%s] NOTICE: ready to handle connections
-Test Start
-NULL
-string(3) "BAR"
-bool(false)
-string(3) "BAR"
-Test End
-
-Request ok
-[%s] NOTICE: Terminating ...
-[%s] NOTICE: exiting, bye-bye!
-Done
---CLEAN--
-<?php
- $logfile = __DIR__.'/php-fpm.log.tmp';
- $srcfile = __DIR__.'/php-fpm.tmp.php';
- @unlink($logfile);
- @unlink($srcfile);
-?>
diff --git a/sapi/fpm/tests/bug68381-log-level-warning.phpt b/sapi/fpm/tests/bug68381-log-level-warning.phpt
new file mode 100644
index 0000000000..8d4a9af4cc
--- /dev/null
+++ b/sapi/fpm/tests/bug68381-log-level-warning.phpt
@@ -0,0 +1,40 @@
+--TEST--
+FPM: bug68381 - Log messages with warning level only
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+log_level = warning
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->checkConnection();
+$tester->terminate();
+$tester->expectNoLogMessages();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68391-conf-include-order.phpt b/sapi/fpm/tests/bug68391-conf-include-order.phpt
new file mode 100644
index 0000000000..012a978f29
--- /dev/null
+++ b/sapi/fpm/tests/bug68391-conf-include-order.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: bug68391 - Configuration inclusion in alphabetical order
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg['main'] = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+log_level = notice
+include = {{INCLUDE:CONF}}
+EOT;
+
+$cfgPoolTemplate = <<<EOT
+[%name%]
+listen = {{ADDR[%name%]}}
+user = foo
+pm = ondemand
+pm.max_children = 5
+EOT;
+
+$names = ['cccc', 'aaaa', 'eeee', 'dddd', 'bbbb'];
+foreach($names as $name) {
+ $cfg[$name] = str_replace('%name%', $name, $cfgPoolTemplate);
+}
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$userMessage = "'user' directive is ignored when FPM is not running as root";
+$tester->expectLogNotice($userMessage, 'aaaa');
+$tester->expectLogNotice($userMessage, 'bbbb');
+$tester->expectLogNotice($userMessage, 'cccc');
+$tester->expectLogNotice($userMessage, 'dddd');
+$tester->expectLogNotice($userMessage, 'eeee');
+$tester->expectLogStartNotices();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68420-ipv4-all-addresses.phpt b/sapi/fpm/tests/bug68420-ipv4-all-addresses.phpt
new file mode 100644
index 0000000000..9a4692d17c
--- /dev/null
+++ b/sapi/fpm/tests/bug68420-ipv4-all-addresses.phpt
@@ -0,0 +1,42 @@
+--TEST--
+FPM: bug68420 - IPv4 all addresses
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{PORT}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('127.0.0.1');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68421-ipv6-access-log.phpt b/sapi/fpm/tests/bug68421-ipv6-access-log.phpt
new file mode 100644
index 0000000000..80c115c17f
--- /dev/null
+++ b/sapi/fpm/tests/bug68421-ipv6-access-log.phpt
@@ -0,0 +1,47 @@
+--TEST--
+FPM: bug68421 - IPv6 all addresses and access_log
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfIPv6IsNotSupported();
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG:ERR}}
+[unconfined]
+listen = {{ADDR:IPv6:ANY}}
+access.log = {{FILE:LOG:ACC}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('127.0.0.1');
+$tester->ping('[::1]');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+$tester->printAccessLog();
+?>
+Done
+--EXPECTF--
+127.0.0.1 %s "GET /ping" 200
+::1 %s "GET /ping" 200
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68423-multi-pool-all-pms.phpt b/sapi/fpm/tests/bug68423-multi-pool-all-pms.phpt
new file mode 100644
index 0000000000..ae6b48351a
--- /dev/null
+++ b/sapi/fpm/tests/bug68423-multi-pool-all-pms.phpt
@@ -0,0 +1,55 @@
+--TEST--
+FPM: bug68423 - Multiple pools with different PMs (dynamic + ondemand + static)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[pool_dynamic]
+listen = {{ADDR[dynamic]}}
+ping.path = /ping
+ping.response = pong-dynamic
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+[pool_ondemand]
+listen = {{ADDR[ondemand]}}
+ping.path = /ping
+ping.response = pong-on-demand
+pm = ondemand
+pm.max_children = 2
+pm.process_idle_timeout = 10
+[pool_static]
+listen = {{ADDR[static]}}
+ping.path = /ping
+ping.response = pong-static
+pm = static
+pm.max_children = 2
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('{{ADDR[dynamic]}}', 'pong-dynamic');
+$tester->ping('{{ADDR[ondemand]}}', 'pong-on-demand');
+$tester->ping('{{ADDR[static]}}', 'pong-static');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68428-ipv6-allowed-clients.phpt b/sapi/fpm/tests/bug68428-ipv6-allowed-clients.phpt
new file mode 100644
index 0000000000..0998cf0aca
--- /dev/null
+++ b/sapi/fpm/tests/bug68428-ipv6-allowed-clients.phpt
@@ -0,0 +1,45 @@
+--TEST--
+FPM: bug68428 - IPv6 allowed client only
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfIPv6IsNotSupported();
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:IPv6:ANY}}
+listen.allowed_clients = ::1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->checkRequest('127.0.0.1', 'IPv4: ok', 'IPv4: error');
+$tester->checkRequest('[::1]', 'IPv6: ok', 'IPv6: error');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+IPv4: error
+IPv6: ok
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68442-signal-reload.phpt b/sapi/fpm/tests/bug68442-signal-reload.phpt
new file mode 100644
index 0000000000..d15c8e14e7
--- /dev/null
+++ b/sapi/fpm/tests/bug68442-signal-reload.phpt
@@ -0,0 +1,47 @@
+--TEST--
+FPM: bug68442 - Signal reload
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+pid = {{FILE:PID}}
+[unconfined]
+listen = {{ADDR}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('{{ADDR}}');
+$tester->signal('USR2');
+$tester->expectLogNotice('Reloading in progress ...');
+$tester->expectLogNotice('reloading: .*');
+$tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
+$tester->expectLogStartNotices();
+$tester->ping('{{ADDR}}');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug68458-pm-no-start-server.phpt b/sapi/fpm/tests/bug68458-pm-no-start-server.phpt
new file mode 100644
index 0000000000..c0c69b64b9
--- /dev/null
+++ b/sapi/fpm/tests/bug68458-pm-no-start-server.phpt
@@ -0,0 +1,42 @@
+--TEST--
+FPM: bug68458 - Missing pm.start_servers should emit notice instead of warning
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+log_level = warning
+[unconfined]
+listen = {{ADDR}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+;pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->checkConnection();
+$tester->terminate();
+$tester->expectNoLogMessages();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/bug72573-http-proxy.phpt b/sapi/fpm/tests/bug72573-http-proxy.phpt
new file mode 100644
index 0000000000..ffa60d9713
--- /dev/null
+++ b/sapi/fpm/tests/bug72573-http-proxy.phpt
@@ -0,0 +1,66 @@
+--TEST--
+FPM: bug72573 - HTTP_PROXY - CVE-2016-5385
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$code = <<<EOT
+<?php
+echo "Test Start\n";
+var_dump(
+ @\$_SERVER["HTTP_PROXY"],
+ \$_SERVER["HTTP_FOO"],
+ getenv("HTTP_PROXY"),
+ getenv("HTTP_FOO")
+);
+echo "Test End\n";
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester
+ ->request(
+ '',
+ [
+ 'HTTP_FOO' => 'BAR',
+ 'HTTP_PROXY' => 'BADPROXY',
+ ]
+ )
+ ->expectBody(
+ [
+ 'Test Start',
+ 'NULL',
+ 'string(3) "BAR"',
+ 'bool(false)',
+ 'string(3) "BAR"',
+ 'Test End'
+ ]
+ );
+$tester->terminate();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/fastcgi_finish_request_basic.phpt b/sapi/fpm/tests/fastcgi_finish_request_basic.phpt
new file mode 100644
index 0000000000..939782fa31
--- /dev/null
+++ b/sapi/fpm/tests/fastcgi_finish_request_basic.phpt
@@ -0,0 +1,45 @@
+--TEST--
+FPM: Function fastcgi_finish_request basic test
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$code = <<<EOT
+<?php
+echo "Test Start\n";
+fastcgi_finish_request();
+echo "Test End\n";
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectBody("Test Start");
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/fcgi.inc b/sapi/fpm/tests/fcgi.inc
index b31676260d..71bdad17b9 100644
--- a/sapi/fpm/tests/fcgi.inc
+++ b/sapi/fpm/tests/fcgi.inc
@@ -72,25 +72,25 @@ class Client
/**
* Socket
- * @var Resource
+ * @var resource
*/
private $_sock = null;
/**
* Host
- * @var String
+ * @var string
*/
private $_host = null;
/**
* Port
- * @var Integer
+ * @var int
*/
private $_port = null;
/**
* Keep Alive
- * @var Boolean
+ * @var bool
*/
private $_keepAlive = false;
@@ -110,27 +110,27 @@ class Client
/**
* Use persistent sockets to connect to backend
- * @var Boolean
+ * @var bool
*/
private $_persistentSocket = false;
/**
* Connect timeout in milliseconds
- * @var Integer
+ * @var int
*/
private $_connectTimeout = 5000;
/**
* Read/Write timeout in milliseconds
- * @var Integer
+ * @var int
*/
private $_readWriteTimeout = 5000;
/**
* Constructor
*
- * @param String $host Host of the FastCGI application
- * @param Integer $port Port of the FastCGI application
+ * @param string $host Host of the FastCGI application
+ * @param int $port Port of the FastCGI application
*/
public function __construct($host, $port)
{
@@ -139,14 +139,24 @@ class Client
}
/**
+ * Get host.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->_host;
+ }
+
+ /**
* Define whether or not the FastCGI application should keep the connection
* alive at the end of a request
*
- * @param Boolean $b true if the connection should stay alive, false otherwise
+ * @param bool $b true if the connection should stay alive, false otherwise
*/
public function setKeepAlive($b)
{
- $this->_keepAlive = (boolean)$b;
+ $this->_keepAlive = (bool)$b;
if (!$this->_keepAlive && $this->_sock) {
fclose($this->_sock);
}
@@ -155,7 +165,7 @@ class Client
/**
* Get the keep alive status
*
- * @return Boolean true if the connection should stay alive, false otherwise
+ * @return bool true if the connection should stay alive, false otherwise
*/
public function getKeepAlive()
{
@@ -166,12 +176,12 @@ class Client
* Define whether or not PHP should attempt to re-use sockets opened by previous
* request for efficiency
*
- * @param Boolean $b true if persistent socket should be used, false otherwise
+ * @param bool $b true if persistent socket should be used, false otherwise
*/
public function setPersistentSocket($b)
{
$was_persistent = ($this->_sock && $this->_persistentSocket);
- $this->_persistentSocket = (boolean)$b;
+ $this->_persistentSocket = (bool)$b;
if (!$this->_persistentSocket && $was_persistent) {
fclose($this->_sock);
}
@@ -180,7 +190,7 @@ class Client
/**
* Get the pesistent socket status
*
- * @return Boolean true if the socket should be persistent, false otherwise
+ * @return bool true if the socket should be persistent, false otherwise
*/
public function getPersistentSocket()
{
@@ -191,7 +201,7 @@ class Client
/**
* Set the connect timeout
*
- * @param Integer number of milliseconds before connect will timeout
+ * @param int number of milliseconds before connect will timeout
*/
public function setConnectTimeout($timeoutMs)
{
@@ -201,7 +211,7 @@ class Client
/**
* Get the connect timeout
*
- * @return Integer number of milliseconds before connect will timeout
+ * @return int number of milliseconds before connect will timeout
*/
public function getConnectTimeout()
{
@@ -211,7 +221,7 @@ class Client
/**
* Set the read/write timeout
*
- * @param Integer number of milliseconds before read or write call will timeout
+ * @param int number of milliseconds before read or write call will timeout
*/
public function setReadWriteTimeout($timeoutMs)
{
@@ -222,7 +232,7 @@ class Client
/**
* Get the read timeout
*
- * @return Integer number of milliseconds before read will timeout
+ * @return int number of milliseconds before read will timeout
*/
public function getReadWriteTimeout()
{
@@ -232,14 +242,18 @@ class Client
/**
* Helper to avoid duplicating milliseconds to secs/usecs in a few places
*
- * @param Integer millisecond timeout
- * @return Boolean
+ * @param int millisecond timeout
+ * @return bool
*/
private function set_ms_timeout($timeoutMs) {
if (!$this->_sock) {
return false;
}
- return stream_set_timeout($this->_sock, floor($timeoutMs / 1000), ($timeoutMs % 1000) * 1000);
+ return stream_set_timeout(
+ $this->_sock,
+ floor($timeoutMs / 1000),
+ ($timeoutMs % 1000) * 1000
+ );
}
@@ -250,9 +264,21 @@ class Client
{
if (!$this->_sock) {
if ($this->_persistentSocket) {
- $this->_sock = pfsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ $this->_sock = pfsockopen(
+ $this->_host,
+ $this->_port,
+ $errno,
+ $errstr,
+ $this->_connectTimeout/1000
+ );
} else {
- $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ $this->_sock = fsockopen(
+ $this->_host,
+ $this->_port,
+ $errno,
+ $errstr,
+ $this->_connectTimeout/1000
+ );
}
if (!$this->_sock) {
@@ -268,9 +294,10 @@ class Client
/**
* Build a FastCGI packet
*
- * @param Integer $type Type of the packet
- * @param String $content Content of the packet
- * @param Integer $requestId RequestId
+ * @param int $type Type of the packet
+ * @param string $content Content of the packet
+ * @param int $requestId RequestId
+ * @return string
*/
private function buildPacket($type, $content, $requestId = 1)
{
@@ -289,9 +316,9 @@ class Client
/**
* Build an FastCGI Name value pair
*
- * @param String $name Name
- * @param String $value Value
- * @return String FastCGI Name value pair
+ * @param string $name Name
+ * @param string $value Value
+ * @return string FastCGI Name value pair
*/
private function buildNvpair($name, $value)
{
@@ -302,14 +329,16 @@ class Client
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
- $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
+ $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF)
+ . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
- $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
+ $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF)
+ . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
@@ -318,7 +347,7 @@ class Client
/**
* Read a set of FastCGI Name value pairs
*
- * @param String $data Data containing the set of FastCGI NVPair
+ * @param string $data Data containing the set of FastCGI NVPair
* @return array of NVPair
*/
private function readNvpair($data, $length = null)
@@ -357,7 +386,7 @@ class Client
/**
* Decode a FastCGI Packet
*
- * @param String $data String containing all the packet
+ * @param string $data string containing all the packet
* @return array
*/
private function decodePacketHeader($data)
@@ -403,6 +432,7 @@ class Client
*
* @param array $requestedInfo information to retrieve
* @return array
+ * @throws \Exception
*/
public function getValues(array $requestedInfo)
{
@@ -423,11 +453,14 @@ class Client
}
/**
- * Execute a request to the FastCGI application
+ * Execute a request to the FastCGI application and return response body
*
* @param array $params Array of parameters
- * @param String $stdin Content
- * @return String
+ * @param string $stdin Content
+ * @return string
+ * @throws ForbiddenException
+ * @throws TimedOutException
+ * @throws \Exception
*/
public function request(array $params, $stdin)
{
@@ -436,19 +469,37 @@ class Client
}
/**
+ * Execute a request to the FastCGI application and return request data
+ *
+ * @param array $params Array of parameters
+ * @param string $stdin Content
+ * @return array
+ * @throws ForbiddenException
+ * @throws TimedOutException
+ * @throws \Exception
+ */
+ public function request_data(array $params, $stdin)
+ {
+ $id = $this->async_request($params, $stdin);
+ return $this->wait_for_response_data($id);
+ }
+
+ /**
* Execute a request to the FastCGI application asyncronously
- *
+ *
* This sends request to application and returns the assigned ID for that request.
*
* You should keep this id for later use with wait_for_response(). Ids are chosen randomly
- * rather than seqentially to guard against false-positives when using persistent sockets.
- * In that case it is possible that a delayed response to a request made by a previous script
- * invocation comes back on this socket and is mistaken for response to request made with same ID
- * during this request.
+ * rather than sequentially to guard against false-positives when using persistent sockets.
+ * In that case it is possible that a delayed response to a request made by a previous script
+ * invocation comes back on this socket and is mistaken for response to request made with same
+ * ID during this request.
*
* @param array $params Array of parameters
- * @param String $stdin Content
- * @return Integer
+ * @param string $stdin Content
+ * @return int
+ * @throws TimedOutException
+ * @throws \Exception
*/
public function async_request(array $params, $stdin)
{
@@ -460,10 +511,12 @@ class Client
// Using persistent sockets implies you want them keept alive by server!
$keepAlive = intval($this->_keepAlive || $this->_persistentSocket);
- $request = $this->buildPacket(self::BEGIN_REQUEST
- ,chr(0) . chr(self::RESPONDER) . chr($keepAlive) . str_repeat(chr(0), 5)
- ,$id
- );
+ $request = $this->buildPacket(
+ self::BEGIN_REQUEST,
+ chr(0) . chr(self::RESPONDER) . chr($keepAlive)
+ . str_repeat(chr(0), 5),
+ $id
+ );
$paramsRequest = '';
foreach ($params as $key => $value) {
@@ -494,21 +547,26 @@ class Client
$this->_requests[$id] = array(
'state' => self::REQ_STATE_WRITTEN,
- 'response' => null
+ 'response' => null,
+ 'err_response' => null,
+ 'out_response' => null,
);
return $id;
}
/**
- * Blocking call that waits for response to specific request
- *
- * @param Integer $requestId
- * @param Integer $timeoutMs [optional] the number of milliseconds to wait. Defaults to the ReadWriteTimeout value set.
- * @return string response body
+ * Blocking call that waits for response data of the specific request
+ *
+ * @param int $requestId
+ * @param int $timeoutMs [optional] the number of milliseconds to wait.
+ * @return array response data
+ * @throws ForbiddenException
+ * @throws TimedOutException
+ * @throws \Exception
*/
- public function wait_for_response($requestId, $timeoutMs = 0) {
-
+ public function wait_for_response_data($requestId, $timeoutMs = 0)
+ {
if (!isset($this->_requests[$requestId])) {
throw new \Exception('Invalid request id given');
}
@@ -537,6 +595,9 @@ class Client
if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
if ($resp['type'] == self::STDERR) {
$this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_ERR;
+ $this->_requests[$resp['requestId']]['err_response'] .= $resp['content'];
+ } else {
+ $this->_requests[$resp['requestId']]['out_response'] .= $resp['content'];
}
$this->_requests[$resp['requestId']]['response'] .= $resp['content'];
}
@@ -586,7 +647,22 @@ class Client
throw new \Exception('Role value not known [UNKNOWN_ROLE]');
break;
case self::REQUEST_COMPLETE:
- return $this->_requests[$requestId]['response'];
+ return $this->_requests[$requestId];
}
}
+
+ /**
+ * Blocking call that waits for response to specific request
+ *
+ * @param int $requestId
+ * @param int $timeoutMs [optional] the number of milliseconds to wait.
+ * @return string The response content.
+ * @throws ForbiddenException
+ * @throws TimedOutException
+ * @throws \Exception
+ */
+ public function wait_for_response($requestId, $timeoutMs = 0)
+ {
+ return $this->wait_for_response_data($requestId, $timeoutMs)['response'];
+ }
}
diff --git a/sapi/fpm/tests/include.inc b/sapi/fpm/tests/include.inc
deleted file mode 100644
index 8faf53dcc2..0000000000
--- a/sapi/fpm/tests/include.inc
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-
-function get_fpm_path() /* {{{ */
-{
- $php_path = getenv("TEST_PHP_EXECUTABLE");
- for ($i = 0; $i < 2; $i++) {
- $slash_pos = strrpos($php_path, "/");
- if ($slash_pos) {
- $php_path = substr($php_path, 0, $slash_pos);
- } else {
- return false;
- }
- }
-
-
- if ($php_path && is_dir($php_path)) {
- if (file_exists($php_path."/fpm/php-fpm") && is_executable($php_path."/fpm/php-fpm")) {
- /* gotcha */
- return $php_path."/fpm/php-fpm";
- }
- $php_sbin_fpm = $php_path."/sbin/php-fpm";
- if (file_exists($php_sbin_fpm) && is_executable($php_sbin_fpm)) {
- return $php_sbin_fpm;
- }
- }
- return false;
-}
-/* }}} */
-
-function run_fpm($config, &$out = false, $extra_args = '') /* {{{ */
-{
- $cfg = dirname(__FILE__).'/test-fpm-config.tmp';
- file_put_contents($cfg, $config);
- $desc = [];
- if ($out !== false) {
- $desc = [1 => array('pipe', 'w')];
- }
- /* Since it's not possible to spawn a process under linux without using a
- * shell in php (why?!?) we need a little shell trickery, so that we can
- * actually kill php-fpm */
- $fpm = proc_open('killit () { kill $child; }; trap killit TERM; '.get_fpm_path().' -F -O -y '.$cfg.' '.$extra_args.' 2>&1 & child=$!; wait', $desc, $pipes);
- register_shutdown_function(
- function($fpm) use($cfg) {
- @unlink($cfg);
- if (is_resource($fpm)) {
- @proc_terminate($fpm);
- while (proc_get_status($fpm)['running']) {
- usleep(10000);
- }
- }
- },
- $fpm
- );
- if ($out !== false) {
- $out = $pipes[1];
- }
- return $fpm;
-}
-/* }}} */
-
-function test_fpm_conf($config, &$msg = NULL) { /* {{{ */
- $cfg = dirname(__FILE__).'/test-fpm-config.tmp';
- file_put_contents($cfg, $config);
- exec(get_fpm_path() . ' -t -y ' . $cfg . ' 2>&1', $output, $code);
- if ($code) {
- $msg = preg_replace("/\[.+?\]/", "", $output[0]);
- return false;
- }
- return true;
-}
-/* }}} */
-
-function run_fpm_till($needle, $config, $max = 10) /* {{{ */
-{
- $i = 0;
- $fpm = run_fpm($config, $tail);
- if (is_resource($fpm)) {
- while($i < $max) {
- $i++;
- $line = fgets($tail);
- if(preg_match($needle, $line) === 1) {
- break;
- }
- }
- if ($i >= $max) {
- $line = false;
- }
- proc_terminate($fpm);
- stream_get_contents($tail);
- fclose($tail);
- proc_close($fpm);
- }
- return $line;
-}
-/* }}} */
-
-function fpm_display_log($tail, $n=1, $ignore='systemd') { /* {{{ */
- /* Read $n lines or until EOF */
- while ($n>0 || ($n<0 && !feof($tail))) {
- $a = fgets($tail);
- if (empty($ignore) || !strpos($a, $ignore)) {
- echo $a;
- $n--;
- }
- }
-} /* }}} */
-
-function run_request($host, $port, $uri='/ping', $query='', $headers=array()) { /* {{{ */
- require_once 'fcgi.inc';
- $client = new Adoy\FastCGI\Client($host, $port);
- $params = array_merge(array(
- 'GATEWAY_INTERFACE' => 'FastCGI/1.0',
- 'REQUEST_METHOD' => 'GET',
- 'SCRIPT_FILENAME' => $uri,
- 'SCRIPT_NAME' => $uri,
- 'QUERY_STRING' => $query,
- 'REQUEST_URI' => $uri . ($query ? '?'.$query : ""),
- 'DOCUMENT_URI' => $uri,
- 'SERVER_SOFTWARE' => 'php/fcgiclient',
- 'REMOTE_ADDR' => '127.0.0.1',
- 'REMOTE_PORT' => '9985',
- 'SERVER_ADDR' => '127.0.0.1',
- 'SERVER_PORT' => '80',
- 'SERVER_NAME' => php_uname('n'),
- 'SERVER_PROTOCOL' => 'HTTP/1.1',
- 'CONTENT_TYPE' => '',
- 'CONTENT_LENGTH' => 0
- ), $headers);
- return $client->request($params, false)."\n";
-}
-/* }}} */
diff --git a/sapi/fpm/tests/logtool.inc b/sapi/fpm/tests/logtool.inc
new file mode 100644
index 0000000000..219c6fedbb
--- /dev/null
+++ b/sapi/fpm/tests/logtool.inc
@@ -0,0 +1,476 @@
+<?php
+
+namespace FPM;
+
+class LogTool
+{
+ const P_TIME = '\[\d\d-\w\w\w-\d{4} \d\d:\d\d:\d\d\]';
+ const P_PREFIX = '\[pool unconfined\] child \d+ said into stderr: ';
+ const FINAL_SUFFIX = ', pipe is closed';
+
+ /**
+ * @var string
+ */
+ private $message;
+
+ /**
+ * @var string
+ */
+ private $level;
+
+ /**
+ * @var int
+ */
+ private $position;
+
+ /**
+ * @var int
+ */
+ private $suffixPosition;
+
+ /**
+ * @var int
+ */
+ private $limit;
+
+ /**
+ * @var string
+ */
+ private $pattern;
+
+ /**
+ * @var string
+ */
+ private $error;
+
+ /**
+ * @param string $message
+ * @param int $limit
+ * @param int $repeat
+ */
+ public function setExpectedMessage(string $message, int $limit, int $repeat = 0)
+ {
+ $this->message = ($repeat > 0) ? str_repeat($message, $repeat) : $message;
+ $this->limit = $limit;
+ $this->position = 0;
+ }
+
+ /**
+ * @param string $level
+ * @return int
+ */
+ public function setExpectedLevel(string $level)
+ {
+ return $this->level = $level;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpectedLevel(): string
+ {
+ return $this->level ?: 'WARNING';
+ }
+
+ /**
+ * @param string $line
+ * @return bool
+ */
+ public function checkTruncatedMessage(string $line)
+ {
+ if ($this->message === null) {
+ throw new \LogicException('The message has not been set');
+ }
+ $lineLen = strlen($line);
+ if (!$this->checkLineLength($line)) {
+ return false;
+ }
+ $this->pattern = '/^PHP message: (.*?)(\.\.\.)?$/';
+ if (preg_match($this->pattern, $line, $matches) === 0) {
+ return $this->error("Unexpected truncated message: {$line}");
+ }
+
+ if ($lineLen === $this->limit) {
+ if (!isset($matches[2])) {
+ return $this->error("The truncated line is not ended with '...'");
+ }
+ if (!$this->checkMessage($matches[1])) {
+ return false;
+ }
+ } else {
+ if (isset($matches[2])) {
+ // this is expecting that the expected message does not end with '...'
+ // which should not be an issue for the test purpose.
+ return $this->error("The line is complete and should not end with '...'");
+ }
+ if (!$this->checkMessage($matches[1], -1)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $lines
+ * @param bool $terminated
+ * @param bool $decorated
+ * @return bool
+ */
+ public function checkWrappedMessage(array $lines, bool $terminated = true, bool $decorated = true)
+ {
+ if ($this->message === null) {
+ throw new \LogicException('The message has not been set');
+ }
+ if ($decorated) {
+ $this->pattern = sprintf(
+ '/^(%s %s: %s)"([^"]*)"(.*)?$/',
+ self::P_TIME,
+ $this->getExpectedLevel(),
+ self::P_PREFIX
+ );
+ } else {
+ $this->pattern = null;
+ }
+
+ $idx = 0;
+ foreach ($lines as $idx => $line) {
+ if (!$this->checkLine($line)) {
+ break;
+ }
+ }
+
+ if ($this->suffixPosition > 0) {
+ $suffixPattern = sprintf(
+ '/^%s %s: %s(.*)$/',
+ self::P_TIME, $this->getExpectedLevel(),
+ self::P_PREFIX
+ );
+ $line = $lines[++$idx];
+ if (preg_match($suffixPattern, $line, $matches) === 0) {
+ return $this->error("Unexpected line: $line");
+ }
+ if ($matches[1] !== substr(self::FINAL_SUFFIX, $this->suffixPosition)) {
+ return $this->error(
+ "The suffix has not been finished from position $this->suffixPosition in line: $line"
+ );
+ }
+ }
+
+ if ($terminated) {
+ return $this->expectTerminatorLines($lines, $idx);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $line
+ * @return bool
+ */
+ private function checkLine(string $line)
+ {
+ if ($this->pattern === null) {
+ // plain (not decorated) output
+ $out = rtrim($line);
+ $finalSuffix = null;
+ } elseif (($res = preg_match($this->pattern, $line, $matches)) > 0) {
+ $out = $matches[2];
+ $finalSuffix = $matches[3] ?? false;
+ } else {
+ return $this->error("Unexpected line: $line");
+ }
+
+ $rem = strlen($this->message) - $this->position;
+ $lineLen = strlen($line);
+ if (!$this->checkLineLength($line, $lineLen)) {
+ return false;
+ }
+ if (!$this->checkMessage($out, $this->position)) {
+ return false;
+ }
+ $outLen = strlen($out);
+ if ($rem > $outLen) { // continuous line
+ if ($lineLen !== $this->limit) {
+ if ($lineLen + ($rem - $outLen) < $this->limit) {
+ return $this->error("Printed less than the message len");
+ }
+ return $this->error(
+ "The continuous line length is $lineLen but it should equal to limit $this->limit"
+ );
+ }
+ $this->position += $outLen;
+ return true;
+ }
+ if ($rem !== $outLen) {
+ return $this->error("Printed more than the message len");
+ }
+ if ($finalSuffix === null || $finalSuffix === "") {
+ return false;
+ }
+ if ($finalSuffix === false) {
+ return $this->error("No final suffix");
+ }
+ if (strpos(self::FINAL_SUFFIX, $finalSuffix) === false) {
+ return $this->error("The final suffix has to be equal to ', pipe is closed'");
+ }
+ if (self::FINAL_SUFFIX !== $finalSuffix) {
+ $this->suffixPosition = strlen($finalSuffix);
+ }
+ // complete final suffix printed
+ return false;
+ }
+
+ /**
+ * @param string $line
+ * @param int $lineLen
+ * @return bool
+ */
+ private function checkLineLength(string $line, $lineLen = null) {
+ $lineLen = $lineLen ?: strlen($line);
+ if ($lineLen > $this->limit) {
+ return $this->error(
+ "The line length is $lineLen which is higher than limit $this->limit"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $matchedMessage
+ * @param int $expectedMessageStart
+ * @return bool
+ */
+ private function checkMessage(string $matchedMessage, int $expectedMessageStart = 0)
+ {
+ if ($expectedMessageStart < 0) {
+ $expectedMessage = $this->message;
+ } else {
+ $expectedMessage = substr($this->message, $expectedMessageStart, strlen($matchedMessage));
+ }
+ if ($expectedMessage !== $matchedMessage) {
+ return $this->error(
+ sprintf(
+ "The actual string(%d) does not match expected string(%d):\n",
+ strlen($matchedMessage),
+ strlen($expectedMessage)
+ ) .
+ "- EXPECT: '$expectedMessage'\n" .
+ "- ACTUAL: '$matchedMessage'"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $lines
+ * @return bool
+ */
+ public function expectStartingLines(array $lines)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+
+ if (count($lines) < 2) {
+ return $this->error("No starting lines");
+ }
+
+ return (
+ $this->expectNotice($lines[0], 'fpm is running, pid \d+') &&
+ $this->expectNotice($lines[1], 'ready to handle connections')
+ );
+ }
+
+ /**
+ * @param array $lines
+ * @param int $idx
+ * @return bool
+ */
+ public function expectTerminatorLines(array $lines, int $idx = -1)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+
+ if (count($lines) - $idx < 3) {
+ return $this->error("No terminating lines");
+ }
+
+ return (
+ $this->expectNotice($lines[++$idx], 'Terminating ...') &&
+ $this->expectNotice($lines[++$idx], 'exiting, bye-bye!')
+ );
+ }
+
+ /**
+ * @param string $type
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectEntry(string $type, string $line, string $expectedMessage, $pool = null)
+ {
+ if ($this->getError()) {
+ return false;
+ }
+ if ($pool !== null) {
+ $expectedMessage = '\[pool ' . $pool . '\] ' . $expectedMessage;
+ }
+
+ $line = rtrim($line);
+ $pattern = sprintf('/^%s %s: %s$/', self::P_TIME, $type, $expectedMessage);
+
+ if (preg_match($pattern, $line, $matches) === 0) {
+ return $this->error(
+ "The $type does not match expected message:\n" .
+ "- PATTERN: $pattern\n" .
+ "- MESSAGE: $line\n" .
+ "- EXPECT: '$expectedMessage'\n" .
+ "- ACTUAL: '" . substr($line, strpos($line, $type) + strlen($type) + 2) . "'"
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectDebug(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('DEBUG', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectNotice(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('NOTICE', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectWarning(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('WARNING', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectError(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('ERROR', $line, $expectedMessage, $pool);
+ }
+
+ /**
+ * @param string $line
+ * @param string $expectedMessage
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectAlert(string $line, string $expectedMessage, $pool = null)
+ {
+ return $this->expectEntry('ALERT', $line, $expectedMessage, $pool);
+ }
+
+
+ /**
+ * @param string $msg
+ * @return bool
+ */
+ private function error(string $msg)
+ {
+ $this->error = $msg;
+ echo "ERROR: $msg\n";
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+}
+
+if (isset($argv[1]) && $argv[1] === 'logtool-selftest') {
+ $cases = [
+ [
+ 'limit' => 1050,
+ 'lines' => [
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 112) . '", pipe is closed',
+ '[08-Oct-2017 19:53:55] NOTICE: Terminating ...',
+ '[08-Oct-2017 19:53:55] NOTICE: exiting, bye-bye!',
+ ],
+ 'message' => str_repeat('a', 2048),
+ 'type' => 'stdio',
+ ],
+ [
+ 'limit' => 1050,
+ 'lines' => [
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 968) . '"',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: "' .
+ str_repeat('a', 964) . '", pi',
+ '[08-Oct-2017 19:53:50] WARNING: [pool unconfined] child 23183 said into stderr: pe is closed',
+ '[08-Oct-2017 19:53:55] NOTICE: Terminating ...',
+ '[08-Oct-2017 19:53:55] NOTICE: exiting, bye-bye!',
+ ],
+ 'message' => str_repeat('a', 2900),
+ 'type' => 'stdio',
+ ],
+ [
+ 'limit' => 1024,
+ 'line' => '[08-Oct-2017 19:53:50] WARNING: ' . str_repeat('a',989) . '...',
+ 'message' => str_repeat('a', 2900),
+ 'type' => 'message',
+ ],
+ [
+ 'limit' => 1024,
+ 'line' => '[08-Oct-2017 19:53:50] WARNING: ' . str_repeat('a',20),
+ 'message' => str_repeat('a', 20),
+ 'type' => 'message',
+ ],
+ ];
+ foreach ($cases as $case) {
+ printf("Test message with len %d and limit %d: ", strlen($case['message']), $case['limit']);
+ $logTool = new LogTool();
+ $logTool->setExpectedMessage($case['message'], $case['limit']);
+ if ($case['type'] === 'stdio') {
+ $logTool->checkWrappedMessage($case['lines']);
+ } else {
+ $logTool->checkTruncatedMessage($case['line']);
+ }
+ if (!$logTool->getError()) {
+ echo "OK\n";
+ }
+ }
+ echo "Done\n";
+}
diff --git a/sapi/fpm/tests/main-global-prefix.phpt b/sapi/fpm/tests/main-global-prefix.phpt
new file mode 100644
index 0000000000..710e688c40
--- /dev/null
+++ b/sapi/fpm/tests/main-global-prefix.phpt
@@ -0,0 +1,50 @@
+--TEST--
+FPM: Main invocation with prefix
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{RFILE:LOG:ERR}}
+pid = {{RFILE:PID}}
+[unconfined]
+listen = {{ADDR}}
+access.log = {{RFILE:LOG:ACC}}
+slowlog = {{RFILE:LOG:SLOW}}
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$prefix = __DIR__;
+$tester = new FPM\Tester($cfg);
+$tester->start('--prefix ' . $prefix);
+$tester->expectLogStartNotices();
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_ACC, $prefix);
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_ERR, $prefix);
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_SLOW, $prefix);
+$tester->expectFile(FPM\Tester::FILE_EXT_PID, $prefix);
+$tester->ping();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+$tester->expectNoFile(FPM\Tester::FILE_EXT_PID, $prefix);
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/001.phpt b/sapi/fpm/tests/main-version.phpt
index b721bfa925..6e42aae48f 100644
--- a/sapi/fpm/tests/001.phpt
+++ b/sapi/fpm/tests/main-version.phpt
@@ -5,9 +5,9 @@ FPM: version string
--FILE--
<?php
-include "include.inc";
+require_once "tester.inc";
-$php = get_fpm_path();
+$php = \FPM\Tester::findExecutable();
var_dump(`$php -n -v`);
diff --git a/sapi/fpm/tests/apparmor.phpt b/sapi/fpm/tests/pool-apparmor-basic.phpt
index e0f051998f..733f42f3f5 100644
--- a/sapi/fpm/tests/apparmor.phpt
+++ b/sapi/fpm/tests/pool-apparmor-basic.phpt
@@ -1,27 +1,32 @@
--TEST--
-FPM: Apparmor Test
---DESCRIPTION--
-This test tries to launches a pool which tries to change to non existing
-apparmor hat a. Test succeeds if apparmor is not running or hat is non
-existent.
+FPM: AppArmor basic test
--SKIPIF--
<?php
include "skipif.inc";
-include "skipapparmor.inc";
-
+$config = <<<EOT
+[global]
+error_log = /dev/null
+[unconfined]
+listen = {{ADDR}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+apparmor_hat = a
+EOT;
+FPM\Tester::skipIfConfigFails($config);
?>
--FILE--
<?php
-include "include.inc";
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+require_once "tester.inc";
$cfg = <<<EOT
[global]
-error_log = $logfile
-[a]
-listen = 127.0.0.1:9001
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:UDS}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
@@ -30,6 +35,7 @@ pm.max_spare_servers = 3
apparmor_hat = a
EOT;
+$tester = new FPM\Tester($cfg);
/* libapparmor has a bug which can cause SIGSEGV till Version 2.8.0-0ubuntu28
See https://bugs.launchpad.net/apparmor/+bug/1196880
Possible outcomes:
@@ -41,14 +47,17 @@ EOT;
- exited with code 70
Change to successful; Hat not existent (Process gets killed by apparmor)
*/
-var_dump(run_fpm_till('/(SIGSEGV|failed to query apparmor confinement|failed to change to new confinement|exited with code 70)/', $cfg));
+$tester->runTill(
+ '/(SIGSEGV|failed to query apparmor confinement|' .
+ 'failed to change to new confinement|exited with code 70)/'
+);
?>
---EXPECTF--
-string(%d) "%s
-"
+Done
+--EXPECT--
+Done
--CLEAN--
<?php
- $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
- @unlink($logfile);
-?>
+require_once "tester.inc";
+FPM\Tester::clean();
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/pool-prefix.phpt b/sapi/fpm/tests/pool-prefix.phpt
new file mode 100644
index 0000000000..b32b37e13a
--- /dev/null
+++ b/sapi/fpm/tests/pool-prefix.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Pool prefix
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$prefix = __DIR__;
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+pid = {{FILE:PID}}
+[unconfined]
+prefix = $prefix
+listen = {{ADDR}}
+access.log = {{RFILE:LOG:ACC}}
+slowlog = {{RFILE:LOG:SLOW}}
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping();
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_ACC, $prefix);
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_ERR);
+$tester->expectFile(FPM\Tester::FILE_EXT_LOG_SLOW, $prefix);
+$tester->expectFile(FPM\Tester::FILE_EXT_PID);
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+$tester->expectNoFile(FPM\Tester::FILE_EXT_PID);
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/proc-no-start-server.phpt b/sapi/fpm/tests/proc-no-start-server.phpt
new file mode 100644
index 0000000000..82f10727b6
--- /dev/null
+++ b/sapi/fpm/tests/proc-no-start-server.phpt
@@ -0,0 +1,45 @@
+--TEST--
+FPM: Process manager config option pm.start_servers missing
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+;pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogNotice(
+ "pm.start_servers is not set. It's been set to 2.",
+ 'unconfined'
+);
+$tester->expectLogStartNotices();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/proc-user-ignored.phpt b/sapi/fpm/tests/proc-user-ignored.phpt
new file mode 100644
index 0000000000..42a7dc2392
--- /dev/null
+++ b/sapi/fpm/tests/proc-user-ignored.phpt
@@ -0,0 +1,46 @@
+--TEST--
+FPM: Process user setting ignored when FPM is not running as root
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+user = foo
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogNotice(
+ "'user' directive is ignored when FPM is not running as root",
+ 'unconfined'
+);
+$tester->expectLogStartNotices();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc
new file mode 100644
index 0000000000..9888ec1b83
--- /dev/null
+++ b/sapi/fpm/tests/response.inc
@@ -0,0 +1,281 @@
+<?php
+
+namespace FPM;
+
+class Response
+{
+ const HEADER_SEPARATOR = "\r\n\r\n";
+
+ /**
+ * @var array
+ */
+ private $data;
+
+ /**
+ * @var string
+ */
+ private $rawData;
+
+ /**
+ * @var string
+ */
+ private $rawHeaders;
+
+ /**
+ * @var string
+ */
+ private $rawBody;
+
+ /**
+ * @var array
+ */
+ private $headers;
+
+ /**
+ * @var bool
+ */
+ private $valid;
+
+ /**
+ * @var bool
+ */
+ private $expectInvalid;
+
+ /**
+ * @param string|array|null $data
+ * @param bool $expectInvalid
+ */
+ public function __construct($data = null, $expectInvalid = false)
+ {
+ if (!is_array($data)) {
+ $data = [
+ 'response' => $data,
+ 'err_response' => null,
+ 'out_response' => $data,
+ ];
+ }
+
+ $this->data = $data;
+ $this->expectInvalid = $expectInvalid;
+ }
+
+ /**
+ * @param mixed $body
+ * @param string $contentType
+ * @return Response
+ */
+ public function expectBody($body, $contentType = 'text/html')
+ {
+ if ($multiLine = is_array($body)) {
+ $body = implode("\n", $body);
+ }
+
+ if (
+ $this->checkIfValid() &&
+ $this->checkDefaultHeaders($contentType) &&
+ $body !== $this->rawBody
+ ) {
+ if ($multiLine) {
+ $this->error(
+ "==> The expected body:\n$body\n" .
+ "==> does not match the actual body:\n$this->rawBody"
+ );
+ } else {
+ $this->error(
+ "The expected body '$body' does not match actual body '$this->rawBody'"
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return Response
+ */
+ public function expectEmptyBody()
+ {
+ return $this->expectBody('');
+ }
+
+ /**
+ * @param string $contentType
+ * @return string|null
+ */
+ public function getBody($contentType = 'text/html')
+ {
+ if ($this->checkIfValid() && $this->checkDefaultHeaders($contentType)) {
+ return $this->rawBody;
+ }
+
+ return null;
+ }
+
+ /**
+ * Print raw body
+ */
+ public function dumpBody()
+ {
+ var_dump($this->getBody());
+ }
+
+ /**
+ * Print raw body
+ */
+ public function printBody()
+ {
+ echo $this->getBody();
+ }
+
+ /**
+ * Debug response output
+ */
+ public function debugOutput()
+ {
+ echo "-------------- RESPONSE: --------------\n";
+ echo "OUT:\n";
+ echo $this->data['out_response'];
+ echo "ERR:\n";
+ echo $this->data['err_response'];
+ echo "---------------------------------------\n\n";
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getErrorData()
+ {
+ return $this->data['err_response'];
+ }
+
+ /**
+ * Check if the response is valid and if not emit error message
+ *
+ * @return bool
+ */
+ private function checkIfValid()
+ {
+ if ($this->isValid()) {
+ return true;
+ }
+
+ if (!$this->expectInvalid) {
+ $this->error("The response is invalid: $this->rawData");
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $contentType
+ * @return bool
+ */
+ private function checkDefaultHeaders($contentType)
+ {
+ // check default headers
+ return (
+ $this->checkHeader('X-Powered-By', '|^PHP/7|', true) &&
+ $this->checkHeader('Content-type', '|^' . $contentType . '(;\s?charset=\w+)?|', true)
+ );
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @param bool $useRegex
+ * @return bool
+ */
+ private function checkHeader(string $name, string $value, $useRegex = false)
+ {
+ $lcName = strtolower($name);
+ $headers = $this->getHeaders();
+ if (!isset($headers[$lcName])) {
+ return $this->error("The header $name is not present");
+ }
+ $header = $headers[$lcName];
+
+ if (!$useRegex) {
+ if ($header === $value) {
+ return true;
+ }
+ return $this->error("The header $name value '$header' is not the same as '$value'");
+ }
+
+ if (!preg_match($value, $header)) {
+ return $this->error("The header $name value '$header' does not match RegExp '$value'");
+ }
+
+ return true;
+ }
+
+ /**
+ * @return array|null
+ */
+ private function getHeaders()
+ {
+ if (!$this->isValid()) {
+ return null;
+ }
+
+ if (is_array($this->headers)) {
+ return $this->headers;
+ }
+
+ $headerRows = explode("\r\n", $this->rawHeaders);
+ $headers = [];
+ foreach ($headerRows as $headerRow) {
+ $colonPosition = strpos($headerRow, ':');
+ if ($colonPosition === false) {
+ $this->error("Invalid header row (no colon): $headerRow");
+ }
+ $headers[strtolower(substr($headerRow, 0, $colonPosition))] = trim(
+ substr($headerRow, $colonPosition + 1)
+ );
+ }
+
+ return ($this->headers = $headers);
+ }
+
+ /**
+ * @return bool
+ */
+ private function isValid()
+ {
+ if ($this->valid === null) {
+ $this->processData();
+ }
+
+ return $this->valid;
+ }
+
+ /**
+ * Process data and set validity and raw data
+ */
+ private function processData()
+ {
+ $this->rawData = $this->data['out_response'];
+ $this->valid = (
+ !is_null($this->rawData) &&
+ strpos($this->rawData, self::HEADER_SEPARATOR)
+ );
+ if ($this->valid) {
+ list ($this->rawHeaders, $this->rawBody) = array_map(
+ 'trim',
+ explode(self::HEADER_SEPARATOR, $this->rawData)
+ );
+ }
+ }
+
+ /**
+ * Emit error message
+ *
+ * @param string $message
+ * @return bool
+ */
+ private function error($message)
+ {
+ echo "ERROR: $message\n";
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/sapi/fpm/tests/skipapparmor.inc b/sapi/fpm/tests/skipapparmor.inc
deleted file mode 100644
index b286d0361d..0000000000
--- a/sapi/fpm/tests/skipapparmor.inc
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
-$cfg = <<<EOT
-[global]
-error_log = $logfile
-[a]
-listen = 127.0.0.1:9001
-pm = dynamic
-pm.max_children = 5
-pm.start_servers = 2
-pm.min_spare_servers = 1
-pm.max_spare_servers = 3
-apparmor_hat = a
-EOT;
-
-$fpm = run_fpm($cfg, $out, '-t');
-$ok = false;
-if (is_resource($fpm)) {
- if (strpos(stream_get_contents($out), "test is successful") !== FALSE) {
- $ok = true;
- }
- fclose($out);
- proc_close($fpm);
-}
-if (!$ok) {
- die("skip No apparmor support built in");
-}
-
-?>
diff --git a/sapi/fpm/tests/skipif.inc b/sapi/fpm/tests/skipif.inc
index 8c569daafd..25910a8e05 100644
--- a/sapi/fpm/tests/skipif.inc
+++ b/sapi/fpm/tests/skipif.inc
@@ -1,13 +1,15 @@
<?php
-
+// Do not run on Windows
if (substr(PHP_OS, 0, 3) == 'WIN') {
- die ("skip not for Windows");
+ die("skip not for Windows");
}
-
-include dirname(__FILE__)."/include.inc";
-
-if (!get_fpm_path()) {
- die("skip FPM not found");
+// Running as root is not allowed without TEST_FPM_RUN_AS_ROOT env
+if (!getmyuid() && !getenv('TEST_FPM_RUN_AS_ROOT')) {
+ die('skip Refusing to run as root');
}
-?>
+require_once "tester.inc";
+
+if (!FPM\Tester::findExecutable()) {
+ die("skip php-fpm binary not found");
+} \ No newline at end of file
diff --git a/sapi/fpm/tests/socket-invalid-allowed-clients.phpt b/sapi/fpm/tests/socket-invalid-allowed-clients.phpt
new file mode 100644
index 0000000000..b2240687fb
--- /dev/null
+++ b/sapi/fpm/tests/socket-invalid-allowed-clients.phpt
@@ -0,0 +1,48 @@
+--TEST--
+FPM: Socket for invalid allowed client only
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+listen.allowed_clients = xxx
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->checkRequest('127.0.0.1', 'Req: ok', 'Req: error');
+$tester->terminate();
+// this is from child when starting
+$tester->expectLogLine("ERROR: Wrong IP address 'xxx' in listen.allowed_clients");
+$tester->expectLogLine("ERROR: There are no allowed addresses");
+// this is from the request
+$tester->expectLogLine("ERROR: Connection disallowed: IP address '127.0.0.1' has been dropped.");
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Req: error
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/socket-ipv4-allowed-clients.phpt b/sapi/fpm/tests/socket-ipv4-allowed-clients.phpt
new file mode 100644
index 0000000000..3de7c0049b
--- /dev/null
+++ b/sapi/fpm/tests/socket-ipv4-allowed-clients.phpt
@@ -0,0 +1,45 @@
+--TEST--
+FPM: Socket for IPv4 allowed client only
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfIPv6IsNotSupported();
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:IPv6:ANY}}
+listen.allowed_clients = 127.0.0.1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->checkRequest('127.0.0.1', 'IPv4: ok', 'IPv4: error');
+$tester->checkRequest('[::1]', 'IPv6: ok', 'IPv6: error');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+IPv4: ok
+IPv6: error
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/socket-ipv4-basic.phpt b/sapi/fpm/tests/socket-ipv4-basic.phpt
new file mode 100644
index 0000000000..5cce244d10
--- /dev/null
+++ b/sapi/fpm/tests/socket-ipv4-basic.phpt
@@ -0,0 +1,37 @@
+--TEST--
+FPM: Socket for IPv4 connection
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:IPv4}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/socket-ipv6-any.phpt b/sapi/fpm/tests/socket-ipv6-any.phpt
new file mode 100644
index 0000000000..11441acddb
--- /dev/null
+++ b/sapi/fpm/tests/socket-ipv6-any.phpt
@@ -0,0 +1,44 @@
+--TEST--
+FPM: Socket for IPv6 any address connection
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfIPv6IsNotSupported();
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:IPv6:ANY}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->checkConnection('127.0.0.1', 'IPv4: ok');
+$tester->checkConnection('[::1]', 'IPv6: ok');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+IPv4: ok
+IPv6: ok
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/socket-ipv6-basic.phpt b/sapi/fpm/tests/socket-ipv6-basic.phpt
new file mode 100644
index 0000000000..b91dc19718
--- /dev/null
+++ b/sapi/fpm/tests/socket-ipv6-basic.phpt
@@ -0,0 +1,40 @@
+--TEST--
+FPM: Socket for IPv6 connection
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfIPv6IsNotSupported();
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:IPv6}}
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/socket-uds-acl.phpt b/sapi/fpm/tests/socket-uds-acl.phpt
new file mode 100644
index 0000000000..6423ae446c
--- /dev/null
+++ b/sapi/fpm/tests/socket-uds-acl.phpt
@@ -0,0 +1,87 @@
+--TEST--
+FPM: Unix Domain Socket with Posix ACL
+--SKIPIF--
+<?php
+include "skipif.inc";
+FPM\Tester::skipIfAnyFileDoesNotExist(['/usr/bin/getfacl', '/etc/passwd', '/etc/group']);
+$config = <<<EOT
+[global]
+error_log = /dev/null
+[unconfined]
+listen = {{ADDR}}
+listen.acl_users = nobody
+listen.acl_groups = nobody
+listen.mode = 0600
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+FPM\Tester::skipIfConfigFails($config);
+?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+// Select 3 users and 2 groups known by system (avoid root)
+$users = $groups = [];
+$tmp = file('/etc/passwd');
+for ($i=1 ; $i <= 3 ; $i++) {
+ $tab = explode(':', $tmp[$i]);
+ $users[] = $tab[0];
+}
+$users = implode(',', $users);
+$tmp = file('/etc/group');
+for ($i=1 ; $i <= 2 ; $i++) {
+ $tab = explode(':', $tmp[$i]);
+ $groups[] = $tab[0];
+}
+$groups = implode(',', $groups);
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:UDS}}
+listen.acl_users = $users
+listen.acl_groups = $groups
+listen.mode = 0600
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('{{ADDR:UDS}}');
+passthru("/usr/bin/getfacl -cp " . $tester->getListen('{{ADDR:UDS}}'));
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECTF--
+user::rw-
+user:%s:rw-
+user:%s:rw-
+user:%s:rw-
+group::---
+group:%s:rw-
+group:%s:rw-
+mask::rw-
+other::---
+
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/socket-uds-basic.phpt b/sapi/fpm/tests/socket-uds-basic.phpt
new file mode 100644
index 0000000000..b22f3384f9
--- /dev/null
+++ b/sapi/fpm/tests/socket-uds-basic.phpt
@@ -0,0 +1,40 @@
+--TEST--
+FPM: Unix Domain Socket connection
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR:UDS}}
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->ping('{{ADDR:UDS}}');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/status-basic.phpt b/sapi/fpm/tests/status-basic.phpt
new file mode 100644
index 0000000000..323592262f
--- /dev/null
+++ b/sapi/fpm/tests/status-basic.phpt
@@ -0,0 +1,50 @@
+--TEST--
+FPM: Status basic test
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 1
+pm.status_path = /status
+EOT;
+
+$expectedStatusData = [
+ 'pool' => 'unconfined',
+ 'process manager' => 'static',
+ 'listen queue' => 0,
+ 'max listen queue' => 0,
+ 'idle processes' => 0,
+ 'active processes' => 1,
+ 'total processes' => 1,
+ 'max active processes' => 1,
+ 'max children reached' => 0,
+ 'slow requests' => 0,
+];
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectEmptyBody();
+$tester->status($expectedStatusData);
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/status.inc b/sapi/fpm/tests/status.inc
new file mode 100644
index 0000000000..5965f130eb
--- /dev/null
+++ b/sapi/fpm/tests/status.inc
@@ -0,0 +1,199 @@
+<?php
+
+namespace FPM;
+
+class Status
+{
+ const HTML_TITLE = 'PHP-FPM Status Page';
+
+ /**
+ * @var array
+ */
+ private $contentTypes = [
+ 'plain' => 'text/plain',
+ 'html' => 'text/html',
+ 'xml' => 'text/xml',
+ 'json' => 'application/json',
+ ];
+
+ /**
+ * @var array
+ */
+ private $defaultFields = [
+ 'pool' => '\w+',
+ 'process manager' => '(static|dynamic|ondemand)',
+ 'start time' => '\d+\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+-]\d{4}',
+ 'start since' => '\d+',
+ 'accepted conn' => '\d+',
+ 'listen queue' => '\d+',
+ 'max listen queue' => '\d+',
+ 'listen queue len' => '\d+',
+ 'idle processes' => '\d+',
+ 'active processes' => '\d+',
+ 'total processes' => '\d+',
+ 'max active processes' => '\d+',
+ 'max children reached' => '\d+',
+ 'slow requests' => '\d+',
+ ];
+
+ /**
+ * Check status page.
+ *
+ * @param Response $response
+ * @param array $fields
+ * @param string $type
+ * @throws \Exception
+ */
+ public function checkStatus(Response $response, array $fields, string $type)
+ {
+ if (!isset($this->contentTypes[$type])) {
+ throw new \Exception('Invalid content type ' . $type);
+ }
+
+ $body = $response->getBody($this->contentTypes[$type]);
+ if ($body === null) {
+ return;
+ }
+ $method = "checkStatus" . ucfirst($type);
+
+ $this->$method($body, array_merge($this->defaultFields, $fields));
+ }
+
+ /**
+ * Make status check for status page.
+ *
+ * @param string $body
+ * @param array $fields
+ * @param string $rowPattern
+ * @param string $header
+ * @param string $footer
+ * @param null|callable $nameTransformer
+ * @param null|callable $valueTransformer
+ * @param bool $startTimeTimestamp
+ * @param bool $closingName
+ */
+ private function makeStatusCheck(
+ string $body,
+ array $fields,
+ string $rowPattern,
+ string $header = '',
+ string $footer = '',
+ $nameTransformer = null,
+ $valueTransformer = null,
+ bool $startTimeTimestamp = false,
+ bool $closingName = false
+ ) {
+
+ if ($startTimeTimestamp && $fields['start time'][0] === '\\') {
+ $fields['start time'] = '\d+';
+ }
+ $pattern = '|' . $header;
+ foreach ($fields as $name => $value) {
+ if ($nameTransformer) {
+ $name = call_user_func($nameTransformer, $name);
+ }
+ if ($valueTransformer) {
+ $value = call_user_func($valueTransformer, $value);
+ }
+ if ($closingName) {
+ $pattern .= sprintf($rowPattern, $name, $value, $name);
+ } else {
+ $pattern .= sprintf($rowPattern, $name, $value);
+ }
+ }
+ $pattern = rtrim($pattern, $rowPattern[strlen($rowPattern) - 1]);
+ $pattern .= $footer . '|';
+
+ if (!preg_match($pattern, $body)) {
+ echo "ERROR: Expected body does not match pattern\n";
+ echo "BODY:\n";
+ var_dump($body);
+ echo "PATTERN:\n";
+ var_dump($pattern);
+ }
+ }
+
+ /**
+ * Check plain status page.
+ *
+ * @param string $body
+ * @param array $fields
+ */
+ protected function checkStatusPlain(string $body, array $fields)
+ {
+ $this->makeStatusCheck($body, $fields, "%s:\s+%s\n");
+ }
+
+ /**
+ * Check html status page.
+ *
+ * @param string $body
+ * @param array $fields
+ */
+ protected function checkStatusHtml(string $body, array $fields)
+ {
+ $header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " .
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" .
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" .
+ "<head><title>" . self::HTML_TITLE . "</title></head>\n" .
+ "<body>\n<table>\n";
+ $footer = "\n</table>\n</body></html>";
+
+ $this->makeStatusCheck(
+ $body,
+ $fields,
+ "<tr><th>%s</th><td>%s</td></tr>\n",
+ $header,
+ $footer
+ );
+ }
+
+ /**
+ * Check xml status page.
+ *
+ * @param string $body
+ * @param array $fields
+ */
+ protected function checkStatusXml(string $body, array $fields)
+ {
+ $this->makeStatusCheck(
+ $body,
+ $fields,
+ "<%s>%s</%s>\n",
+ "<\?xml version=\"1.0\" \?>\n<status>\n",
+ "\n</status>",
+ function ($name) {
+ return str_replace(' ', '-', $name);
+ },
+ null,
+ true,
+ true
+ );
+ }
+
+ /**
+ * Check json status page.
+ *
+ * @param string $body
+ * @param array $fields
+ */
+ protected function checkStatusJson(string $body, array $fields)
+ {
+ $this->makeStatusCheck(
+ $body,
+ $fields,
+ '"%s":%s,',
+ '{',
+ '}',
+ null,
+ function ($value) {
+ if (is_numeric($value) || $value === '\d+') {
+ return $value;
+ }
+
+ return '"' . $value . '"';
+ },
+ true
+ );
+ }
+} \ No newline at end of file
diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc
new file mode 100644
index 0000000000..c3b6c83e59
--- /dev/null
+++ b/sapi/fpm/tests/tester.inc
@@ -0,0 +1,1183 @@
+<?php
+
+namespace FPM;
+
+use Adoy\FastCGI\Client;
+
+require_once 'fcgi.inc';
+require_once 'logtool.inc';
+require_once 'response.inc';
+
+class Tester
+{
+ /**
+ * Config directory for included files.
+ */
+ const CONF_DIR = __DIR__ . '/conf.d';
+
+ /**
+ * File extension for access log.
+ */
+ const FILE_EXT_LOG_ACC = 'acc.log';
+
+ /**
+ * File extension for error log.
+ */
+ const FILE_EXT_LOG_ERR = 'err.log';
+
+ /**
+ * File extension for slow log.
+ */
+ const FILE_EXT_LOG_SLOW = 'slow.log';
+
+ /**
+ * File extension for PID file.
+ */
+ const FILE_EXT_PID = 'pid';
+
+ /**
+ * @var array
+ */
+ static private $supportedFiles = [
+ self::FILE_EXT_LOG_ACC,
+ self::FILE_EXT_LOG_ERR,
+ self::FILE_EXT_LOG_SLOW,
+ self::FILE_EXT_PID,
+ 'src.php',
+ 'ini',
+ 'skip.ini',
+ '*.sock',
+ ];
+
+ /**
+ * @var array
+ */
+ static private $filesToClean = ['.user.ini'];
+
+ /**
+ * @var bool
+ */
+ private $debug;
+
+ /**
+ * @var array
+ */
+ private $clients;
+
+ /**
+ * @var LogTool
+ */
+ private $logTool;
+
+ /**
+ * Configuration template
+ *
+ * @var string
+ */
+ private $configTemplate;
+
+ /**
+ * The PHP code to execute
+ *
+ * @var string
+ */
+ private $code;
+
+ /**
+ * @var array
+ */
+ private $options;
+
+ /**
+ * @var string
+ */
+ private $fileName;
+
+ /**
+ * @var resource
+ */
+ private $masterProcess;
+
+ /**
+ * @var resource
+ */
+ private $outDesc;
+
+ /**
+ * @var array
+ */
+ private $ports = [];
+
+ /**
+ * @var string
+ */
+ private $error;
+
+ /**
+ * The last response for the request call
+ *
+ * @var Response
+ */
+ private $response;
+
+ /**
+ * Clean all the created files up
+ *
+ * @param int $backTraceIndex
+ */
+ static public function clean($backTraceIndex = 1)
+ {
+ $filePrefix = self::getCallerFileName($backTraceIndex);
+ if (substr($filePrefix, -6) === 'clean.') {
+ $filePrefix = substr($filePrefix, 0, -6);
+ }
+
+ $filesToClean = array_merge(
+ array_map(
+ function($fileExtension) use ($filePrefix) {
+ return $filePrefix . $fileExtension;
+ },
+ self::$supportedFiles
+ ),
+ array_map(
+ function($fileExtension) {
+ return __DIR__ . '/' . $fileExtension;
+ },
+ self::$filesToClean
+ )
+ );
+ // clean all the root files
+ foreach ($filesToClean as $filePattern) {
+ foreach (glob($filePattern) as $filePath) {
+ unlink($filePath);
+ }
+ }
+ // clean config files
+ if (is_dir(self::CONF_DIR)) {
+ foreach(glob(self::CONF_DIR . '/*.conf') as $name) {
+ unlink($name);
+ }
+ rmdir(self::CONF_DIR);
+ }
+ }
+
+ /**
+ * @param int $backTraceIndex
+ * @return string
+ */
+ static private function getCallerFileName($backTraceIndex = 1)
+ {
+ $backtrace = debug_backtrace();
+ if (isset($backtrace[$backTraceIndex]['file'])) {
+ $filePath = $backtrace[$backTraceIndex]['file'];
+ } else {
+ $filePath = __FILE__;
+ }
+
+ return substr($filePath, 0, -strlen(pathinfo($filePath, PATHINFO_EXTENSION)));
+ }
+
+ /**
+ * @return bool|string
+ */
+ static public function findExecutable()
+ {
+ $phpPath = getenv("TEST_PHP_EXECUTABLE");
+ for ($i = 0; $i < 2; $i++) {
+ $slashPosition = strrpos($phpPath, "/");
+ if ($slashPosition) {
+ $phpPath = substr($phpPath, 0, $slashPosition);
+ } else {
+ break;
+ }
+ }
+
+ if ($phpPath && is_dir($phpPath)) {
+ if (file_exists($phpPath."/fpm/php-fpm") && is_executable($phpPath."/fpm/php-fpm")) {
+ /* gotcha */
+ return $phpPath."/fpm/php-fpm";
+ }
+ $phpSbinFpmi = $phpPath."/sbin/php-fpm";
+ if (file_exists($phpSbinFpmi) && is_executable($phpSbinFpmi)) {
+ return $phpSbinFpmi;
+ }
+ }
+
+ // try local php-fpm
+ $fpmPath = dirname(__DIR__) . '/php-fpm';
+ if (file_exists($fpmPath) && is_executable($fpmPath)) {
+ return $fpmPath;
+ }
+
+ return false;
+ }
+
+ /**
+ * Skip test if any of the supplied files does not exist.
+ *
+ * @param mixed $files
+ */
+ static public function skipIfAnyFileDoesNotExist($files)
+ {
+ if (!is_array($files)) {
+ $files = array($files);
+ }
+ foreach ($files as $file) {
+ if (!file_exists($file)) {
+ die("skip File $file does not exist");
+ }
+ }
+ }
+
+ /**
+ * Skip test if config file is invalid.
+ *
+ * @param string $configTemplate
+ * @throws \Exception
+ */
+ static public function skipIfConfigFails(string $configTemplate)
+ {
+ $tester = new self($configTemplate, '', [], self::getCallerFileName());
+ $testResult = $tester->testConfig();
+ if ($testResult !== null) {
+ self::clean(2);
+ die("skip $testResult");
+ }
+ }
+
+ /**
+ * Skip test if IPv6 is not supported.
+ */
+ static public function skipIfIPv6IsNotSupported()
+ {
+ @stream_socket_client('tcp://[::1]:0', $errno);
+ if ($errno != 111) {
+ die('skip IPv6 is not supported.');
+ }
+ }
+
+ /**
+ * Skip if running on Travis.
+ *
+ * @param $message
+ */
+ static public function skipIfTravis($message)
+ {
+ if (getenv("TRAVIS")) {
+ die('skip Travis: ' . $message);
+ }
+ }
+
+ /**
+ * Tester constructor.
+ *
+ * @param string|array $configTemplate
+ * @param string $code
+ * @param array $options
+ * @param string $fileName
+ */
+ public function __construct(
+ $configTemplate,
+ string $code = '',
+ array $options = [],
+ $fileName = null
+ ) {
+ $this->configTemplate = $configTemplate;
+ $this->code = $code;
+ $this->options = $options;
+ $this->fileName = $fileName ?: self::getCallerFileName();
+ $this->logTool = new LogTool();
+ $this->debug = (bool) getenv('TEST_FPM_DEBUG');
+ }
+
+ /**
+ * @param string $ini
+ */
+ public function setUserIni(string $ini)
+ {
+ $iniFile = __DIR__ . '/.user.ini';
+ file_put_contents($iniFile, $ini);
+ }
+
+ /**
+ * Test configuration file.
+ *
+ * @return null|string
+ * @throws \Exception
+ */
+ public function testConfig()
+ {
+ $configFile = $this->createConfig();
+ $cmd = self::findExecutable() . ' -t -y ' . $configFile . ' 2>&1';
+ exec($cmd, $output, $code);
+ if ($code) {
+ return preg_replace("/\[.+?\]/", "", $output[0]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Start PHP-FPM master process
+ *
+ * @param string $extraArgs
+ * @return bool
+ * @throws \Exception
+ */
+ public function start(string $extraArgs = '')
+ {
+ $configFile = $this->createConfig();
+ $desc = $this->outDesc ? [] : [1 => array('pipe', 'w')];
+ $asRoot = getenv('TEST_FPM_RUN_AS_ROOT') ? '--allow-to-run-as-root' : '';
+ $cmd = self::findExecutable() . " $asRoot -F -O -y $configFile $extraArgs";
+ /* Since it's not possible to spawn a process under linux without using a
+ * shell in php (why?!?) we need a little shell trickery, so that we can
+ * actually kill php-fpm */
+ $this->masterProcess = proc_open(
+ "killit () { kill \$child 2> /dev/null; }; " .
+ "trap killit TERM; $cmd 2>&1 & child=\$!; wait",
+ $desc,
+ $pipes
+ );
+ register_shutdown_function(
+ function($masterProcess) use($configFile) {
+ @unlink($configFile);
+ if (is_resource($masterProcess)) {
+ @proc_terminate($masterProcess);
+ while (proc_get_status($masterProcess)['running']) {
+ usleep(10000);
+ }
+ }
+ },
+ $this->masterProcess
+ );
+ if (!$this->outDesc !== false) {
+ $this->outDesc = $pipes[1];
+ }
+
+ return true;
+ }
+
+ /**
+ * Run until needle is found in the log.
+ *
+ * @param string $needle
+ * @param int $max
+ * @return bool
+ * @throws \Exception
+ */
+ public function runTill(string $needle, $max = 10)
+ {
+ $this->start();
+ $found = false;
+ for ($i = 0; $i < $max; $i++) {
+ $line = $this->getLogLine();
+ if (is_null($line)) {
+ break;
+ }
+ if (preg_match($needle, $line) === 1) {
+ $found = true;
+ break;
+ }
+ }
+ $this->close(true);
+
+ if (!$found) {
+ return $this->error("The search pattern not found");
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if connection works.
+ *
+ * @param string $host
+ * @param null|string $successMessage
+ * @param null|string $errorMessage
+ * @param int $attempts
+ * @param int $delay
+ */
+ public function checkConnection(
+ $host = '127.0.0.1',
+ $successMessage = null,
+ $errorMessage = 'Connection failed',
+ $attempts = 20,
+ $delay = 50000
+ ) {
+ $i = 0;
+ do {
+ if ($i > 0 && $delay > 0) {
+ usleep($delay);
+ }
+ $fp = @fsockopen($host, $this->getPort());
+ } while ((++$i < $attempts) && !$fp);
+
+ if ($fp) {
+ $this->message($successMessage);
+ fclose($fp);
+ } else {
+ $this->message($errorMessage);
+ }
+ }
+
+
+ /**
+ * Execute request with parameters ordered for better checking.
+ *
+ * @param string $address
+ * @param string|null $successMessage
+ * @param string|null $errorMessage
+ * @param string $uri
+ * @param string $query
+ * @param array $headers
+ * @return Response
+ */
+ public function checkRequest(
+ string $address,
+ string $successMessage = null,
+ string $errorMessage = null,
+ $uri = '/ping',
+ $query = '',
+ $headers = []
+ ) {
+ return $this->request($query, $headers, $uri, $address, $successMessage, $errorMessage);
+ }
+
+ /**
+ * Execute and check ping request.
+ *
+ * @param string $address
+ * @param string $pingPath
+ * @param string $pingResponse
+ */
+ public function ping(
+ string $address = '{{ADDR}}',
+ string $pingResponse = 'pong',
+ string $pingPath = '/ping'
+ ) {
+ $response = $this->request('', [], $pingPath, $address);
+ $response->expectBody($pingResponse, 'text/plain');
+ }
+
+ /**
+ * Execute and check status request(s).
+ *
+ * @param array $expectedFields
+ * @param string|null $address
+ * @param string $statusPath
+ * @param mixed $formats
+ * @throws \Exception
+ */
+ public function status(
+ array $expectedFields,
+ string $address = null,
+ string $statusPath = '/status',
+ $formats = ['plain', 'html', 'xml', 'json']
+ ) {
+ if (!is_array($formats)) {
+ $formats = [$formats];
+ }
+
+ require_once "status.inc";
+ $status = new Status();
+ foreach ($formats as $format) {
+ $query = $format === 'plain' ? '' : $format;
+ $response = $this->request($query, [], $statusPath, $address);
+ $status->checkStatus($response, $expectedFields, $format);
+ }
+ }
+
+ /**
+ * Execute request.
+ *
+ * @param string $query
+ * @param array $headers
+ * @param string|null $uri
+ * @param string|null $address
+ * @param string|null $successMessage
+ * @param string|null $errorMessage
+ * @param bool $connKeepAlive
+ * @return Response
+ */
+ public function request(
+ string $query = '',
+ array $headers = [],
+ string $uri = null,
+ string $address = null,
+ string $successMessage = null,
+ string $errorMessage = null,
+ bool $connKeepAlive = false
+ ) {
+ if ($this->hasError()) {
+ return new Response(null, true);
+ }
+ if (is_null($uri)) {
+ $uri = $this->makeFile('src.php', $this->code);
+ }
+
+ $params = array_merge(
+ [
+ 'GATEWAY_INTERFACE' => 'FastCGI/1.0',
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_FILENAME' => $uri,
+ 'SCRIPT_NAME' => $uri,
+ 'QUERY_STRING' => $query,
+ 'REQUEST_URI' => $uri . ($query ? '?'.$query : ""),
+ 'DOCUMENT_URI' => $uri,
+ 'SERVER_SOFTWARE' => 'php/fcgiclient',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'REMOTE_PORT' => '7777',
+ 'SERVER_ADDR' => '127.0.0.1',
+ 'SERVER_PORT' => '80',
+ 'SERVER_NAME' => php_uname('n'),
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'DOCUMENT_ROOT' => __DIR__,
+ 'CONTENT_TYPE' => '',
+ 'CONTENT_LENGTH' => 0
+ ],
+ $headers
+ );
+
+ try {
+ $this->response = new Response(
+ $this->getClient($address, $connKeepAlive)->request_data($params, false)
+ );
+ $this->message($successMessage);
+ } catch (\Exception $exception) {
+ if ($errorMessage === null) {
+ $this->error("Request failed", $exception);
+ } else {
+ $this->message($errorMessage);
+ }
+ $this->response = new Response();
+ }
+ if ($this->debug) {
+ $this->response->debugOutput();
+ }
+ return $this->response;
+ }
+
+ /**
+ * Get client.
+ *
+ * @param string $address
+ * @param bool $keepAlive
+ * @return Client
+ */
+ private function getClient(string $address = null, $keepAlive = false)
+ {
+ $address = $address ? $this->processTemplate($address) : $this->getAddr();
+ if ($address[0] === '/') { // uds
+ $host = 'unix://' . $address;
+ $port = -1;
+ } elseif ($address[0] === '[') { // ipv6
+ $addressParts = explode(']:', $address);
+ $host = $addressParts[0];
+ if (isset($addressParts[1])) {
+ $host .= ']';
+ $port = $addressParts[1];
+ } else {
+ $port = $this->getPort();
+ }
+ } else { // ipv4
+ $addressParts = explode(':', $address);
+ $host = $addressParts[0];
+ $port = $addressParts[1] ?? $this->getPort();
+ }
+
+ if (!$keepAlive) {
+ return new Client($host, $port);
+ }
+
+ if (!isset($this->clients[$host][$port])) {
+ $client = new Client($host, $port);
+ $client->setKeepAlive(true);
+ $this->clients[$host][$port] = $client;
+ }
+
+ return $this->clients[$host][$port];
+ }
+
+ /**
+ * Display logs
+ *
+ * @param int $number
+ * @param string $ignore
+ */
+ public function displayLog(int $number = 1, string $ignore = 'systemd')
+ {
+ /* Read $number lines or until EOF */
+ while ($number > 0 || ($number < 0 && !feof($this->outDesc))) {
+ $a = fgets($this->outDesc);
+ if (empty($ignore) || !strpos($a, $ignore)) {
+ echo $a;
+ $number--;
+ }
+ }
+ }
+
+ /**
+ * Get a single log line
+ *
+ * @return null|string
+ */
+ private function getLogLine()
+ {
+ $read = [$this->outDesc];
+ $write = null;
+ $except = null;
+ if (stream_select($read, $write, $except, 2 )) {
+ return fgets($this->outDesc);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get log lines
+ *
+ * @param int $number
+ * @param bool $skipBlank
+ * @param string $ignore
+ * @return array
+ */
+ public function getLogLines(int $number = 1, bool $skipBlank = false, string $ignore = 'systemd')
+ {
+ $lines = [];
+ /* Read $n lines or until EOF */
+ while ($number > 0 || ($number < 0 && !feof($this->outDesc))) {
+ $line = $this->getLogLine();
+ if (is_null($line)) {
+ break;
+ }
+ if ((empty($ignore) || !strpos($line, $ignore)) && (!$skipBlank || strlen(trim($line)) > 0)) {
+ $lines[] = $line;
+ $number--;
+ }
+ }
+
+ return $lines;
+ }
+
+ /**
+ * @return mixed|string
+ */
+ public function getLastLogLine()
+ {
+ $lines = $this->getLogLines();
+
+ return $lines[0] ?? '';
+ }
+
+ /**
+ * Send signal to the supplied PID or the server PID.
+ *
+ * @param string $signal
+ * @param int|null $pid
+ * @return string
+ */
+ public function signal($signal, int $pid = null)
+ {
+ if (is_null($pid)) {
+ $pid = $this->getPid();
+ }
+
+ return exec("kill -$signal $pid");
+ }
+
+ /**
+ * Terminate master process
+ */
+ public function terminate()
+ {
+ proc_terminate($this->masterProcess);
+ }
+
+ /**
+ * Close all open descriptors and process resources
+ *
+ * @param bool $terminate
+ */
+ public function close($terminate = false)
+ {
+ if ($terminate) {
+ $this->terminate();
+ }
+ fclose($this->outDesc);
+ proc_close($this->masterProcess);
+ }
+
+ /**
+ * Create a config file.
+ *
+ * @param string $extension
+ * @return string
+ * @throws \Exception
+ */
+ private function createConfig($extension = 'ini')
+ {
+ if (is_array($this->configTemplate)) {
+ $configTemplates = $this->configTemplate;
+ if (!isset($configTemplates['main'])) {
+ throw new \Exception('The config template array has to have main config');
+ }
+ $mainTemplate = $configTemplates['main'];
+ unset($configTemplates['main']);
+ if (!is_dir(self::CONF_DIR)) {
+ mkdir(self::CONF_DIR);
+ }
+ foreach ($configTemplates as $name => $configTemplate) {
+ $this->makeFile(
+ 'conf',
+ $this->processTemplate($configTemplate),
+ self::CONF_DIR,
+ $name
+ );
+ }
+ } else {
+ $mainTemplate = $this->configTemplate;
+ }
+
+ return $this->makeFile($extension, $this->processTemplate($mainTemplate));
+ }
+
+ /**
+ * Process template string.
+ *
+ * @param string $template
+ * @return string
+ */
+ private function processTemplate(string $template)
+ {
+ $vars = [
+ 'FILE:LOG:ACC' => ['getAbsoluteFile', self::FILE_EXT_LOG_ACC],
+ 'FILE:LOG:ERR' => ['getAbsoluteFile', self::FILE_EXT_LOG_ERR],
+ 'FILE:LOG:SLOW' => ['getAbsoluteFile', self::FILE_EXT_LOG_SLOW],
+ 'FILE:PID' => ['getAbsoluteFile', self::FILE_EXT_PID],
+ 'RFILE:LOG:ACC' => ['getRelativeFile', self::FILE_EXT_LOG_ACC],
+ 'RFILE:LOG:ERR' => ['getRelativeFile', self::FILE_EXT_LOG_ERR],
+ 'RFILE:LOG:SLOW' => ['getRelativeFile', self::FILE_EXT_LOG_SLOW],
+ 'RFILE:PID' => ['getRelativeFile', self::FILE_EXT_PID],
+ 'ADDR:IPv4' => ['getAddr', 'ipv4'],
+ 'ADDR:IPv4:ANY' => ['getAddr', 'ipv4-any'],
+ 'ADDR:IPv6' => ['getAddr', 'ipv6'],
+ 'ADDR:IPv6:ANY' => ['getAddr', 'ipv6-any'],
+ 'ADDR:UDS' => ['getAddr', 'uds'],
+ 'PORT' => ['getPort', 'ip'],
+ 'INCLUDE:CONF' => self::CONF_DIR . '/*.conf',
+ ];
+ $aliases = [
+ 'ADDR' => 'ADDR:IPv4',
+ 'FILE:LOG' => 'FILE:LOG:ERR',
+ ];
+ foreach ($aliases as $aliasName => $aliasValue) {
+ $vars[$aliasName] = $vars[$aliasValue];
+ }
+
+ return preg_replace_callback(
+ '/{{([a-zA-Z0-9:]+)(\[\w+\])?}}/',
+ function ($matches) use ($vars) {
+ $varName = $matches[1];
+ if (!isset($vars[$varName])) {
+ $this->error("Invalid config variable $varName");
+ return 'INVALID';
+ }
+ $pool = $matches[2] ?? 'default';
+ $varValue = $vars[$varName];
+ if (is_string($varValue)) {
+ return $varValue;
+ }
+ $functionName = array_shift($varValue);
+ $varValue[] = $pool;
+ return call_user_func_array([$this, $functionName], $varValue);
+ },
+ $template
+ );
+ }
+
+ /**
+ * @param string $type
+ * @param string $pool
+ * @return string
+ */
+ public function getAddr(string $type = 'ipv4', $pool = 'default')
+ {
+ $port = $this->getPort($type, $pool, true);
+ if ($type === 'uds') {
+ return $this->getFile($port . '.sock');
+ }
+
+ return $this->getHost($type) . ':' . $port;
+ }
+
+ /**
+ * @param string $type
+ * @param string $pool
+ * @param bool $useAsId
+ * @return int
+ */
+ public function getPort(string $type = 'ip', $pool = 'default', $useAsId = false)
+ {
+ if ($type === 'uds' && !$useAsId) {
+ return -1;
+ }
+
+ if (isset($this->ports['values'][$pool])) {
+ return $this->ports['values'][$pool];
+ }
+ $port = ($this->ports['last'] ?? 9000 + PHP_INT_SIZE - 1) + 1;
+ $this->ports['values'][$pool] = $this->ports['last'] = $port;
+
+ return $port;
+ }
+
+ /**
+ * @param string $type
+ * @return string
+ */
+ public function getHost(string $type = 'ipv4')
+ {
+ switch ($type) {
+ case 'ipv6-any':
+ return '[::]';
+ case 'ipv6':
+ return '[::1]';
+ case 'ipv4-any':
+ return '0.0.0.0';
+ default:
+ return '127.0.0.1';
+ }
+ }
+
+ /**
+ * Get listen address.
+ *
+ * @param string|null $template
+ * @return string
+ */
+ public function getListen($template = null)
+ {
+ return $template ? $this->processTemplate($template) : $this->getAddr();
+ }
+
+ /**
+ * Get PID.
+ *
+ * @return int
+ */
+ public function getPid()
+ {
+ $pidFile = $this->getFile('pid');
+ if (!is_file($pidFile)) {
+ return (int) $this->error("PID file has not been created");
+ }
+ $pidContent = file_get_contents($pidFile);
+ if (!is_numeric($pidContent)) {
+ return (int) $this->error("PID content '$pidContent' is not integer");
+ }
+
+ return (int) $pidContent;
+ }
+
+
+ /**
+ * @param string $extension
+ * @param string|null $dir
+ * @param string|null $name
+ * @return string
+ */
+ private function getFile(string $extension, $dir = null, $name = null)
+ {
+ $fileName = (is_null($name) ? $this->fileName : $name . '.') . $extension;
+
+ return is_null($dir) ? $fileName : $dir . '/' . $fileName;
+ }
+
+ /**
+ * @param string $extension
+ * @return string
+ */
+ private function getAbsoluteFile(string $extension)
+ {
+ return $this->getFile($extension);
+ }
+
+ /**
+ * @param string $extension
+ * @return string
+ */
+ private function getRelativeFile(string $extension)
+ {
+ $fileName = rtrim(basename($this->fileName), '.');
+
+ return $this->getFile($extension, null, $fileName);
+ }
+
+ /**
+ * @param string $extension
+ * @param string $prefix
+ * @return string
+ */
+ private function getPrefixedFile(string $extension, string $prefix = null)
+ {
+ $fileName = rtrim($this->fileName, '.');
+ if (!is_null($prefix)) {
+ $fileName = $prefix . '/' . basename($fileName);
+ }
+
+ return $this->getFile($extension, null, $fileName);
+ }
+
+ /**
+ * @param string $extension
+ * @param string $content
+ * @param string|null $dir
+ * @param string|null $name
+ * @return string
+ */
+ private function makeFile(string $extension, string $content = '', $dir = null, $name = null)
+ {
+ $filePath = $this->getFile($extension, $dir, $name);
+ file_put_contents($filePath, $content);
+
+ return $filePath;
+ }
+
+ /**
+ * @param string|null $msg
+ */
+ private function message($msg)
+ {
+ if ($msg !== null) {
+ echo "$msg\n";
+ }
+ }
+
+ /**
+ * @param string $msg
+ * @param \Exception|null $exception
+ */
+ private function error($msg, \Exception $exception = null)
+ {
+ $this->error = 'ERROR: ' . $msg;
+ if ($exception) {
+ $this->error .= '; EXCEPTION: ' . $exception->getMessage();
+ }
+ $this->error .= "\n";
+
+ echo $this->error;
+ }
+
+ /**
+ * @return bool
+ */
+ private function hasError()
+ {
+ return !is_null($this->error) || !is_null($this->logTool->getError());
+ }
+
+ /**
+ * Expect file with a supplied extension to exist.
+ *
+ * @param string $extension
+ * @param string $prefix
+ * @return bool
+ */
+ public function expectFile(string $extension, $prefix = null)
+ {
+ $filePath = $this->getPrefixedFile($extension, $prefix);
+ if (!file_exists($filePath)) {
+ return $this->error("The file $filePath does not exist");
+ }
+
+ return true;
+ }
+
+ /**
+ * Expect file with a supplied extension to not exist.
+ *
+ * @param string $extension
+ * @param string $prefix
+ * @return bool
+ */
+ public function expectNoFile(string $extension, $prefix = null)
+ {
+ $filePath = $this->getPrefixedFile($extension, $prefix);
+ if (file_exists($filePath)) {
+ return $this->error("The file $filePath exists");
+ }
+
+ return true;
+ }
+
+ /**
+ * Expect message to be written to FastCGI error stream.
+ *
+ * @param string $message
+ * @param int $limit
+ * @param int $repeat
+ */
+ public function expectFastCGIErrorMessage(
+ string $message,
+ int $limit = 1024,
+ int $repeat = 0
+ ) {
+ $this->logTool->setExpectedMessage($message, $limit, $repeat);
+ $this->logTool->checkTruncatedMessage($this->response->getErrorData());
+ }
+
+ /**
+ * Expect starting lines to be logged.
+ */
+ public function expectLogStartNotices()
+ {
+ $this->logTool->expectStartingLines($this->getLogLines(2));
+ }
+
+ /**
+ * Expect terminating lines to be logged.
+ */
+ public function expectLogTerminatingNotices()
+ {
+ $this->logTool->expectTerminatorLines($this->getLogLines(-1));
+ }
+
+ /**
+ * Expect log message that can span multiple lines.
+ *
+ * @param string $message
+ * @param int $limit
+ * @param int $repeat
+ * @param bool $decorated
+ * @param bool $wrapped
+ */
+ public function expectLogMessage(
+ string $message,
+ int $limit = 1024,
+ int $repeat = 0,
+ bool $decorated = true,
+ bool $wrapped = true
+ ) {
+ $this->logTool->setExpectedMessage($message, $limit, $repeat);
+ if ($wrapped) {
+ $logLines = $this->getLogLines(-1, true);
+ $this->logTool->checkWrappedMessage($logLines, true, $decorated);
+ } else {
+ $logLines = $this->getLogLines(1, true);
+ $this->logTool->checkTruncatedMessage($logLines[0] ?? '');
+ }
+ if ($this->debug) {
+ $this->message("-------------- LOG LINES: -------------");
+ var_dump($logLines);
+ $this->message("---------------------------------------\n");
+ }
+ }
+
+ /**
+ * Expect a single log line.
+ *
+ * @param string $message
+ * @return bool
+ */
+ public function expectLogLine(string $message)
+ {
+ $messageLen = strlen($message);
+ $limit = $messageLen > 1024 ? $messageLen + 16 : 1024;
+ $this->logTool->setExpectedMessage($message, $limit);
+ $logLines = $this->getLogLines(1, true);
+ if ($this->debug) {
+ $this->message("LOG LINE: " . ($logLines[0] ?? ''));
+ }
+
+ return $this->logTool->checkWrappedMessage($logLines, false);
+ }
+
+ /**
+ * Expect a log debug message.
+ *
+ * @param string $message
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectLogDebug(string $message, $pool = null)
+ {
+ return $this->logTool->expectDebug($this->getLastLogLine(), $message, $pool);
+ }
+
+ /**
+ * Expect a log notice.
+ *
+ * @param string $message
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectLogNotice(string $message, $pool = null)
+ {
+ return $this->logTool->expectNotice($this->getLastLogLine(), $message, $pool);
+ }
+
+ /**
+ * Expect a log warning.
+ *
+ * @param string $message
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectLogWarning(string $message, $pool = null)
+ {
+ return $this->logTool->expectWarning($this->getLastLogLine(), $message, $pool);
+ }
+
+ /**
+ * Expect a log error.
+ *
+ * @param string $message
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectLogError(string $message, $pool = null)
+ {
+ return $this->logTool->expectError($this->getLastLogLine(), $message, $pool);
+ }
+
+ /**
+ * Expect a log alert.
+ *
+ * @param string $message
+ * @param string|null $pool
+ * @return bool
+ */
+ public function expectLogAlert(string $message, $pool = null)
+ {
+ return $this->logTool->expectAlert($this->getLastLogLine(), $message, $pool);
+ }
+
+ /**
+ * Expect no log lines to be logged.
+ *
+ * @return bool
+ */
+ public function expectNoLogMessages()
+ {
+ $logLines = $this->getLogLines(-1, true);
+ if (!empty($logLines)) {
+ return $this->error(
+ "Expected no log lines but following lines logged:\n" . implode("\n", $logLines)
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Print content of access log.
+ */
+ public function printAccessLog()
+ {
+ $accessLog = $this->getFile('acc.log');
+ if (is_file($accessLog)) {
+ print file_get_contents($accessLog);
+ }
+ }
+} \ No newline at end of file