Skip to content

Commit b96825a

Browse files
committed
* fix CURL tests
1 parent e6c6d0c commit b96825a

14 files changed

+803
-311
lines changed

scheduler.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,11 @@ void async_scheduler_main_coroutine_suspend(void)
624624

625625
coroutine->context.cleanup = NULL;
626626

627+
// Current coroutine is no longer valid.
628+
ZEND_ASYNC_CURRENT_COROUTINE = NULL;
629+
// Async context is no longer valid.
630+
ZEND_ASYNC_DEACTIVATE;
631+
627632
OBJ_RELEASE(&coroutine->std);
628633

629634
//

tests/common/http_server.php

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
<?php
2+
/**
3+
* HTTP test server for async tests using PHP's built-in development server
4+
* Adapted from sapi/cli/tests/php_cli_server.inc
5+
*/
6+
7+
class AsyncTestServerInfo {
8+
public function __construct(
9+
public string $docRoot,
10+
public $processHandle,
11+
public string $address,
12+
public int $port
13+
) {}
14+
}
15+
16+
function async_test_server_start(?string $router = null): AsyncTestServerInfo {
17+
$php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
18+
19+
// Use the common test router by default
20+
if ($router === null) {
21+
$router = __DIR__ . '/test_router.php';
22+
}
23+
24+
// Create dedicated doc root
25+
$doc_root = __DIR__ . DIRECTORY_SEPARATOR . 'server_' . uniqid();
26+
@mkdir($doc_root);
27+
28+
$cmd = [$php_executable, '-t', $doc_root, '-n', '-S', 'localhost:0', $router];
29+
30+
$output_file = tempnam(sys_get_temp_dir(), 'async_test_server_output');
31+
$output_file_fd = fopen($output_file, 'ab');
32+
if ($output_file_fd === false) {
33+
die(sprintf("Failed opening output file %s\n", $output_file));
34+
}
35+
36+
register_shutdown_function(function () use ($output_file) {
37+
@unlink($output_file);
38+
});
39+
40+
$descriptorspec = array(
41+
0 => STDIN,
42+
1 => $output_file_fd,
43+
2 => $output_file_fd,
44+
);
45+
$handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true));
46+
47+
// Wait for the server to start
48+
$bound = null;
49+
for ($i = 0; $i < 60; $i++) {
50+
usleep(50000); // 50ms per try
51+
$status = proc_get_status($handle);
52+
if (empty($status['running'])) {
53+
echo "Server failed to start\n";
54+
printf("Server output:\n%s\n", file_get_contents($output_file));
55+
proc_terminate($handle);
56+
exit(1);
57+
}
58+
59+
$output = file_get_contents($output_file);
60+
if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $output, $matches)) {
61+
$bound = $matches[1];
62+
break;
63+
}
64+
}
65+
66+
if ($bound === null) {
67+
echo "Server did not output startup message\n";
68+
printf("Server output:\n%s\n", file_get_contents($output_file));
69+
proc_terminate($handle);
70+
exit(1);
71+
}
72+
73+
// Wait for a successful connection
74+
$error = "Unable to connect to server\n";
75+
for ($i = 0; $i < 60; $i++) {
76+
usleep(50000); // 50ms per try
77+
$status = proc_get_status($handle);
78+
$fp = @fsockopen("tcp://$bound");
79+
80+
if (!($status && $status['running'])) {
81+
$error = sprintf("Server stopped\nServer output:\n%s\n", file_get_contents($output_file));
82+
break;
83+
}
84+
85+
if ($fp) {
86+
fclose($fp);
87+
$error = '';
88+
break;
89+
}
90+
}
91+
92+
if ($error) {
93+
echo $error;
94+
proc_terminate($handle);
95+
exit(1);
96+
}
97+
98+
register_shutdown_function(
99+
function($handle) use($doc_root, $output_file) {
100+
if (is_resource($handle) && get_resource_type($handle) === 'process') {
101+
$status = proc_get_status($handle);
102+
if ($status !== false && $status['running']) {
103+
proc_terminate($handle);
104+
}
105+
proc_close($handle);
106+
107+
if ($status !== false && $status['exitcode'] !== -1 && $status['exitcode'] !== 0
108+
&& !($status['exitcode'] === 255 && PHP_OS_FAMILY == 'Windows')) {
109+
printf("Server exited with non-zero status: %d\n", $status['exitcode']);
110+
printf("Server output:\n%s\n", file_get_contents($output_file));
111+
}
112+
}
113+
@unlink($output_file);
114+
remove_directory($doc_root);
115+
},
116+
$handle
117+
);
118+
119+
$port = (int) substr($bound, strrpos($bound, ':') + 1);
120+
121+
// Define global constants for backward compatibility
122+
if (!defined('ASYNC_TEST_SERVER_HOSTNAME')) {
123+
define('ASYNC_TEST_SERVER_HOSTNAME', 'localhost');
124+
define('ASYNC_TEST_SERVER_PORT', $port);
125+
define('ASYNC_TEST_SERVER_ADDRESS', "localhost:$port");
126+
}
127+
128+
return new AsyncTestServerInfo($doc_root, $handle, $bound, $port);
129+
}
130+
131+
function async_test_server_start_custom(string $router_file): AsyncTestServerInfo {
132+
return async_test_server_start($router_file);
133+
}
134+
135+
function async_test_server_connect(AsyncTestServerInfo $server) {
136+
$timeout = 1.0;
137+
$fp = fsockopen('localhost', $server->port, $errno, $errstr, $timeout);
138+
if (!$fp) {
139+
die("connect failed: $errstr ($errno)");
140+
}
141+
return $fp;
142+
}
143+
144+
function async_test_server_stop(AsyncTestServerInfo $server) {
145+
if ($server->processHandle && is_resource($server->processHandle) && get_resource_type($server->processHandle) === 'process') {
146+
$status = proc_get_status($server->processHandle);
147+
if ($status !== false && $status['running']) {
148+
proc_terminate($server->processHandle);
149+
}
150+
proc_close($server->processHandle);
151+
$server->processHandle = null;
152+
}
153+
154+
// Always remove directory, regardless of process state
155+
remove_directory($server->docRoot);
156+
}
157+
158+
function remove_directory($dir) {
159+
if (is_dir($dir) === false) {
160+
return;
161+
}
162+
163+
// On Windows, give the process time to release the directory
164+
if (PHP_OS_FAMILY === 'Windows') {
165+
usleep(100000); // 100ms delay
166+
}
167+
168+
$files = new RecursiveIteratorIterator(
169+
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
170+
RecursiveIteratorIterator::CHILD_FIRST
171+
);
172+
foreach ($files as $fileinfo) {
173+
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
174+
@$todo($fileinfo->getRealPath());
175+
}
176+
@rmdir($dir);
177+
}
178+
179+
function start_test_server_process($port = 8088) {
180+
$server_script = __FILE__;
181+
$php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY;
182+
$cmd = $php_executable . " $server_script $port > /dev/null 2>&1 & echo $!";
183+
$pid = exec($cmd);
184+
185+
// Wait a bit for server to start
186+
usleep(100000); // 100ms
187+
188+
return (int)$pid;
189+
}
190+
191+
function stop_test_server_process($pid) {
192+
if (PHP_OS_FAMILY === 'Windows') {
193+
exec("taskkill /PID $pid /F 2>NUL");
194+
} else {
195+
exec("kill $pid 2>/dev/null");
196+
}
197+
}
198+
199+
function run_test_server($port) {
200+
$server = stream_socket_server("tcp://127.0.0.1:$port", $errno, $errstr);
201+
if (!$server) {
202+
die("Failed to create server: $errstr ($errno)\n");
203+
}
204+
205+
echo "Test HTTP server running on 127.0.0.1:$port\n";
206+
207+
while (true) {
208+
$client = stream_socket_accept($server);
209+
if (!$client) continue;
210+
211+
// Read the request
212+
$request = '';
213+
while (!feof($client)) {
214+
$line = fgets($client);
215+
$request .= $line;
216+
if (trim($line) === '') break; // End of headers
217+
}
218+
219+
// Parse request line
220+
$lines = explode("\n", $request);
221+
$request_line = trim($lines[0]);
222+
preg_match('/^(\S+)\s+(\S+)\s+(\S+)$/', $request_line, $matches);
223+
224+
if (count($matches) >= 3) {
225+
$method = $matches[1];
226+
$path = $matches[2];
227+
228+
// Route requests
229+
$response = route_test_request($method, $path, $request);
230+
} else {
231+
$response = http_test_response(400, "Bad Request");
232+
}
233+
234+
fwrite($client, $response);
235+
fclose($client);
236+
}
237+
238+
fclose($server);
239+
}
240+
241+
function route_test_request($method, $path, $full_request) {
242+
switch ($path) {
243+
case '/':
244+
return http_test_response(200, "Hello World");
245+
246+
case '/json':
247+
return http_test_response(200, '{"message":"Hello JSON","status":"ok"}', 'application/json');
248+
249+
case '/slow':
250+
// Simulate slow response (useful for timeout tests)
251+
usleep(500000); // 500ms
252+
return http_test_response(200, "Slow Response");
253+
254+
case '/very-slow':
255+
// Very slow response (for timeout tests)
256+
sleep(2);
257+
return http_test_response(200, "Very Slow Response");
258+
259+
case '/error':
260+
return http_test_response(500, "Internal Server Error");
261+
262+
case '/not-found':
263+
return http_test_response(404, "Not Found");
264+
265+
case '/large':
266+
// Large response for testing data transfer
267+
return http_test_response(200, str_repeat("ABCDEFGHIJ", 1000)); // 10KB
268+
269+
case '/post':
270+
if ($method === 'POST') {
271+
// Extract body if present
272+
$body_start = strpos($full_request, "\r\n\r\n");
273+
$body = $body_start !== false ? substr($full_request, $body_start + 4) : '';
274+
return http_test_response(200, "POST received: " . strlen($body) . " bytes");
275+
} else {
276+
return http_test_response(405, "Method Not Allowed");
277+
}
278+
279+
case '/headers':
280+
// Return request headers as response
281+
$headers = [];
282+
$lines = explode("\n", $full_request);
283+
foreach ($lines as $line) {
284+
$line = trim($line);
285+
if ($line && strpos($line, ':') !== false) {
286+
$headers[] = $line;
287+
}
288+
}
289+
return http_test_response(200, implode("\n", $headers));
290+
291+
case '/echo':
292+
// Echo back the entire request
293+
return http_test_response(200, $full_request, 'text/plain');
294+
295+
case '/redirect':
296+
// Simple redirect
297+
return "HTTP/1.1 302 Found\r\n" .
298+
"Location: /\r\n" .
299+
"Content-Length: 0\r\n" .
300+
"Connection: close\r\n" .
301+
"\r\n";
302+
303+
default:
304+
return http_test_response(404, "Not Found");
305+
}
306+
}
307+
308+
function http_test_response($code, $body, $content_type = 'text/plain') {
309+
$status_text = [
310+
200 => 'OK',
311+
302 => 'Found',
312+
400 => 'Bad Request',
313+
404 => 'Not Found',
314+
405 => 'Method Not Allowed',
315+
500 => 'Internal Server Error'
316+
][$code] ?? 'Unknown';
317+
318+
$length = strlen($body);
319+
320+
return "HTTP/1.1 $code $status_text\r\n" .
321+
"Content-Type: $content_type\r\n" .
322+
"Content-Length: $length\r\n" .
323+
"Connection: close\r\n" .
324+
"Server: AsyncTestServer/1.0\r\n" .
325+
"\r\n" .
326+
$body;
327+
}
328+
329+
// Helper functions for tests
330+
function get_test_server_address($port = 8088) {
331+
return "127.0.0.1:$port";
332+
}
333+
334+
function get_test_server_url($path = '/', $port = 8088) {
335+
return "http://127.0.0.1:$port$path";
336+
}
337+
338+
// If called directly, run the server
339+
if (basename(__FILE__) === basename($_SERVER['SCRIPT_NAME'] ?? '')) {
340+
$port = $argv[1] ?? 8088;
341+
run_test_server($port);
342+
}

0 commit comments

Comments
 (0)