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