]> granicus.if.org Git - php/commitdiff
Rewrite FPM tests
authorJakub Zelenka <bukka@php.net>
Thu, 7 Jun 2018 16:21:54 +0000 (17:21 +0100)
committerJakub Zelenka <bukka@php.net>
Tue, 12 Jun 2018 16:59:28 +0000 (17:59 +0100)
52 files changed:
sapi/fpm/tests/002.phpt [deleted file]
sapi/fpm/tests/003.phpt [deleted file]
sapi/fpm/tests/004.phpt [deleted file]
sapi/fpm/tests/005.phpt [deleted file]
sapi/fpm/tests/006.phpt [deleted file]
sapi/fpm/tests/007.phpt [deleted file]
sapi/fpm/tests/008.phpt [deleted file]
sapi/fpm/tests/009.phpt [deleted file]
sapi/fpm/tests/010.phpt [deleted file]
sapi/fpm/tests/011.phpt [deleted file]
sapi/fpm/tests/012.phpt [deleted file]
sapi/fpm/tests/013.phpt [deleted file]
sapi/fpm/tests/014.phpt [deleted file]
sapi/fpm/tests/015.phpt [deleted file]
sapi/fpm/tests/016.phpt [deleted file]
sapi/fpm/tests/017.phpt [deleted file]
sapi/fpm/tests/019.phpt [deleted file]
sapi/fpm/tests/020.phpt [deleted file]
sapi/fpm/tests/021-uds-acl.phpt [deleted file]
sapi/fpm/tests/022-cve-2016-5385.phpt [deleted file]
sapi/fpm/tests/bug68381-log-level-warning.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68391-conf-include-order.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68420-ipv4-all-addresses.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68421-ipv6-access-log.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68423-multi-pool-all-pms.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68428-ipv6-allowed-clients.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68442-signal-reload.phpt [new file with mode: 0644]
sapi/fpm/tests/bug68458-pm-no-start-server.phpt [new file with mode: 0644]
sapi/fpm/tests/bug72573-http-proxy.phpt [new file with mode: 0644]
sapi/fpm/tests/fastcgi_finish_request_basic.phpt [new file with mode: 0644]
sapi/fpm/tests/fcgi.inc
sapi/fpm/tests/include.inc [deleted file]
sapi/fpm/tests/logtool.inc [new file with mode: 0644]
sapi/fpm/tests/main-global-prefix.phpt [new file with mode: 0644]
sapi/fpm/tests/main-version.phpt [moved from sapi/fpm/tests/001.phpt with 81% similarity]
sapi/fpm/tests/pool-apparmor-basic.phpt [moved from sapi/fpm/tests/apparmor.phpt with 52% similarity]
sapi/fpm/tests/pool-prefix.phpt [new file with mode: 0644]
sapi/fpm/tests/proc-no-start-server.phpt [new file with mode: 0644]
sapi/fpm/tests/proc-user-ignored.phpt [new file with mode: 0644]
sapi/fpm/tests/response.inc [new file with mode: 0644]
sapi/fpm/tests/skipapparmor.inc [deleted file]
sapi/fpm/tests/skipif.inc
sapi/fpm/tests/socket-invalid-allowed-clients.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-ipv4-allowed-clients.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-ipv4-basic.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-ipv6-any.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-ipv6-basic.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-uds-acl.phpt [new file with mode: 0644]
sapi/fpm/tests/socket-uds-basic.phpt [new file with mode: 0644]
sapi/fpm/tests/status-basic.phpt [new file with mode: 0644]
sapi/fpm/tests/status.inc [new file with mode: 0644]
sapi/fpm/tests/tester.inc [new file with mode: 0644]

diff --git a/sapi/fpm/tests/002.phpt b/sapi/fpm/tests/002.phpt
deleted file mode 100644 (file)
index 5ad9e4b..0000000
+++ /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 (file)
index 8accbd0..0000000
+++ /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 (file)
index 4375f8d..0000000
+++ /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 (file)
index 6c8210e..0000000
+++ /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 (file)
index e552087..0000000
+++ /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 (file)
index 6329af2..0000000
+++ /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 (file)
index d5fe05b..0000000
+++ /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 (file)
index 34cdffc..0000000
+++ /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 (file)
index 49e1a07..0000000
+++ /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 (file)
index 0b849f8..0000000
+++ /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 (file)
index d96c530..0000000
+++ /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 (file)
index 641eb44..0000000
+++ /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 (file)
index e243ef6..0000000
+++ /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 (file)
index a3c7ad3..0000000
+++ /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 (file)
index 04ba4b5..0000000
+++ /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 (file)
index 46e5efd..0000000
+++ /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 (file)
index 7bed9e2..0000000
+++ /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 (file)
index 8131750..0000000
+++ /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 (file)
index 6e9ec08..0000000
+++ /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 (file)
index 0bdf238..0000000
+++ /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 (file)
index 0000000..8d4a9af
--- /dev/null
@@ -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 (file)
index 0000000..012a978
--- /dev/null
@@ -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 (file)
index 0000000..9a4692d
--- /dev/null
@@ -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 (file)
index 0000000..80c115c
--- /dev/null
@@ -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 (file)
index 0000000..ae6b483
--- /dev/null
@@ -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 (file)
index 0000000..0998cf0
--- /dev/null
@@ -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 (file)
index 0000000..d15c8e1
--- /dev/null
@@ -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 (file)
index 0000000..c0c69b6
--- /dev/null
@@ -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 (file)
index 0000000..ffa60d9
--- /dev/null
@@ -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 (file)
index 0000000..939782f
--- /dev/null
@@ -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();
+?>
index b31676260dac65bc6487832c4d4f89bc68865f5a..71bdad17b952394fa455e5a095dc4054fd72857b 100644 (file)
@@ -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)
     {
@@ -138,15 +138,25 @@ class Client
         $this->_port = $port;
     }
 
+    /**
+     * 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)
     {
@@ -435,20 +468,38 @@ class Client
         return $this->wait_for_response($id);
     }
 
+    /**
+     * 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 (file)
index 8faf53d..0000000
+++ /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 (file)
index 0000000..219c6fe
--- /dev/null
@@ -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 (file)
index 0000000..710e688
--- /dev/null
@@ -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();
+?>
similarity index 81%
rename from sapi/fpm/tests/001.phpt
rename to sapi/fpm/tests/main-version.phpt
index b721bfa9254a7d4c38e75e76ddf0c2200503bca5..6e42aae48f75f9cdadc4132b484dc6fc15b3e72d 100644 (file)
@@ -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`);
 
similarity index 52%
rename from sapi/fpm/tests/apparmor.phpt
rename to sapi/fpm/tests/pool-apparmor-basic.phpt
index e0f051998ff22a817fb4a3f69ff477eea77952c6..733f42f3f5fa70adb3309a427eac0ad97103c252 100644 (file)
@@ -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 (file)
index 0000000..b32b37e
--- /dev/null
@@ -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 (file)
index 0000000..82f1072
--- /dev/null
@@ -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 (file)
index 0000000..42a7dc2
--- /dev/null
@@ -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 (file)
index 0000000..9888ec1
--- /dev/null
@@ -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 (file)
index b286d03..0000000
+++ /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");
-}
-
-?>
index 8c569daafdc7e9fb2440fbb1646b9fdbd38f8026..25910a8e05e5634e0ebbbe1c88c680e0b68a33a2 100644 (file)
@@ -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 (file)
index 0000000..b224068
--- /dev/null
@@ -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 (file)
index 0000000..3de7c00
--- /dev/null
@@ -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 (file)
index 0000000..5cce244
--- /dev/null
@@ -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 (file)
index 0000000..11441ac
--- /dev/null
@@ -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 (file)
index 0000000..b91dc19
--- /dev/null
@@ -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 (file)
index 0000000..6423ae4
--- /dev/null
@@ -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 (file)
index 0000000..b22f338
--- /dev/null
@@ -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 (file)
index 0000000..3235922
--- /dev/null
@@ -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 (file)
index 0000000..5965f13
--- /dev/null
@@ -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 (file)
index 0000000..c3b6c83
--- /dev/null
@@ -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