xref: /PHP-8.2/sapi/cli/tests/php_cli_server.inc (revision 3bf4098e)
1<?php
2
3// TODO: Move address/port info in here?
4class CliServerInfo {
5    public function __construct(
6        public string $docRoot,
7    ) {}
8}
9
10function php_cli_server_start(
11    ?string $code = 'echo "Hello world";',
12    ?string $router = 'index.php',
13    array $cmd_args = []
14): CliServerInfo {
15    $php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
16    $error = null;
17
18    // Create dedicated doc root to avoid index.php clashes between tests.
19    $doc_root = __DIR__ . '/' . basename($_SERVER['PHP_SELF'], '.php');
20    @mkdir($doc_root);
21
22    if ($code) {
23        file_put_contents($doc_root . '/' . ($router ?: 'index.php'), '<?php ' . $code . ' ?>');
24    }
25
26    $cmd = [$php_executable, '-t', $doc_root, '-n', ...$cmd_args, '-S', 'localhost:0'];
27    if (!is_null($router)) {
28        $cmd[] = $router;
29    }
30
31    $output_file = tempnam(sys_get_temp_dir(), 'cli_server_output');
32    $output_file_fd = fopen($output_file, 'ab');
33    if ($output_file_fd === false) {
34        die(sprintf("Failed opening output file %s\n", $output_file));
35    }
36
37    $descriptorspec = array(
38        0 => STDIN,
39        1 => $output_file_fd,
40        2 => $output_file_fd,
41    );
42    $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true));
43
44    // First, wait for the dev server to declare itself ready.
45    $bound = null;
46    for ($i = 0; $i < 60; $i++) {
47        usleep(50000); // 50ms per try
48        $status = proc_get_status($handle);
49        if (empty($status['running'])) {
50            echo "Server failed to start\n";
51            printf("Server output:\n%s\n", file_get_contents($output_file));
52            proc_terminate($handle);
53            exit(1);
54        }
55
56        $output = file_get_contents($output_file);
57        if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $output, $matches)) {
58            $bound = $matches[1];
59            break;
60        }
61    }
62    if ($bound === null) {
63        echo "Server did not output startup message";
64        printf("Server output:\n%s\n", file_get_contents($output_file));
65        proc_terminate($handle);
66        exit(1);
67    }
68
69    // Now wait for a connection to succeed.
70    // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.'
71    //       it might not be listening yet...need to wait until fsockopen() call returns
72    $error = "Unable to connect to server\n";
73    for ($i=0; $i < 60; $i++) {
74        usleep(50000); // 50ms per try
75        $status = proc_get_status($handle);
76        $fp = @fsockopen("tcp://$bound");
77        // Failure, the server is no longer running
78        if (!($status && $status['running'])) {
79            $error = sprintf("Server stopped\nServer output:\n%s\n", file_get_contents($output_file));
80            break;
81        }
82        // Success, Connected to servers
83        if ($fp) {
84            $error = '';
85            break;
86        }
87    }
88
89    if ($error) {
90        echo $error;
91        proc_terminate($handle);
92        exit(1);
93    }
94
95    register_shutdown_function(
96        function($handle) use($router, $doc_root, $output_file) {
97            proc_terminate($handle);
98            $status = proc_get_status($handle);
99            if ($status['exitcode'] !== -1 && $status['exitcode'] !== 0
100                    && !($status['exitcode'] === 255 && PHP_OS_FAMILY == 'Windows')) {
101                printf("Server exited with non-zero status: %d\n", $status['exitcode']);
102                printf("Server output:\n%s\n", file_get_contents($output_file));
103            }
104            @unlink(__DIR__ . "/{$router}");
105            @rmdir($doc_root);
106        },
107        $handle
108    );
109
110    // Define the same "constants" we previously did.
111    $port = (int) substr($bound, strrpos($bound, ':') + 1);
112    define("PHP_CLI_SERVER_HOSTNAME", "localhost");
113    define("PHP_CLI_SERVER_PORT", $port);
114    define("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT);
115
116    return new CliServerInfo($doc_root);
117}
118
119function php_cli_server_connect() {
120    $timeout = 1.0;
121    $fp = fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT, $errno, $errstr, $timeout);
122    if (!$fp) {
123        die("connect failed");
124    }
125    return $fp;
126}
127
128?>
129