[php-src] master: ext/readline: write test for readline CLI (#22994)

Weilin Du via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-08-05T00:38:56+08:00

Commit: https://github.com/php/php-src/commit/762b0c401a53935c1a9333f97e310e3eef3e0063
Raw diff: https://github.com/php/php-src/commit/762b0c401a53935c1a9333f97e310e3eef3e0063.diff

ext/readline: write test for readline CLI (#22994)

This commit offers basic behavior tests for ext/readline/readline_cli.c

Changed paths:
  A  ext/readline/tests/readline_cli_auto_prepend.phpt
  A  ext/readline/tests/readline_cli_completion_readline.phpt
  A  ext/readline/tests/readline_cli_long_input.phpt
  A  ext/readline/tests/readline_cli_multiline_states.phpt
  A  ext/readline/tests/readline_cli_pager.phpt
  A  ext/readline/tests/readline_cli_prompt.phpt


Diff:

diff --git a/ext/readline/tests/readline_cli_auto_prepend.phpt b/ext/readline/tests/readline_cli_auto_prepend.phpt
new file mode 100644
index 000000000000..71d4477a7cde
--- /dev/null
+++ b/ext/readline/tests/readline_cli_auto_prepend.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Interactive shell: auto_prepend_file is executed before input
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (!function_exists('proc_open')) die('skip proc_open() not available');
+?>
+--FILE--
+<?php
+$prepend = tempnam(sys_get_temp_dir(), 'readline_cli_prepend');
+file_put_contents($prepend, <<<'PHP'
+<?php
+echo "prepended\n";
+define('READLINE_CLI_PREPENDED', 'ok');
+readline_completion_function(static fn() => []);
+PHP);
+
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+$descriptorspec = [['pipe', 'r'], STDOUT, STDERR];
+$proc = proc_open("$php $ini -d auto_prepend_file=" . escapeshellarg($prepend) . " -a", $descriptorspec, $pipes);
+fwrite($pipes[0], "echo READLINE_CLI_PREPENDED . \"\n\";\n");
+fwrite($pipes[0], "exit\n");
+fclose($pipes[0]);
+proc_close($proc);
+unlink($prepend);
+?>
+--EXPECTF--
+%AInteractive shell%Aprepended%Aok%A
diff --git a/ext/readline/tests/readline_cli_completion_readline.phpt b/ext/readline/tests/readline_cli_completion_readline.phpt
new file mode 100644
index 000000000000..cd4cbe148346
--- /dev/null
+++ b/ext/readline/tests/readline_cli_completion_readline.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Interactive shell: default completion function
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (READLINE_LIB !== "readline") die('skip readline only');
+if (!function_exists('shell_exec')) die('skip shell_exec() not available');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+
+putenv('TERM=VT100');
+
+$code = <<<'PHP'
+$readline_cli_completion_variable = strtolower("VARIABLE_OK\n");
+echo $readline_cli_completion_var	;
+#prec	3
+echo "precision_result=" . ini_get("precision") . "\n";
+function readline_cli_completion_function() { echo strtolower("FUNCTION_OK\n"); }
+readline_cli_completion_fun	);
+define('READLINE_CLI_COMPLETION_CONSTANT', strtolower("CONSTANT_OK\n"));
+echo READLINE_CLI_COMPLETION_CON	;
+class ReadlineCliCompletionClass {
+    public static function completionMethod() { echo strtolower("METHOD_OK\n"); }
+}
+echo "class_ok:" . ReadlineCliCompletionCla	::class . "\n";
+ReadlineCliCompletionClass::completionM	);
+exit
+PHP;
+
+echo shell_exec("echo " . escapeshellarg($code) . " | $php $ini -a");
+?>
+--EXPECTF--
+%AInteractive shell%Avariable_ok%Aprecision_result=3%Afunction_ok%Aconstant_ok%Aclass_ok:ReadlineCliCompletionClass%Amethod_ok%A
diff --git a/ext/readline/tests/readline_cli_long_input.phpt b/ext/readline/tests/readline_cli_long_input.phpt
new file mode 100644
index 000000000000..a74850aef2d4
--- /dev/null
+++ b/ext/readline/tests/readline_cli_long_input.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Interactive shell: input buffer grows for long lines
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (!function_exists('proc_open')) die('skip proc_open() not available');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+$descriptorspec = [['pipe', 'r'], STDOUT, STDERR];
+$proc = proc_open("$php $ini -a", $descriptorspec, $pipes);
+$long = str_repeat('x', 5000);
+fwrite($pipes[0], 'echo strlen("' . $long . '") . "\n";' . "\n");
+fwrite($pipes[0], "exit\n");
+fclose($pipes[0]);
+proc_close($proc);
+?>
+--EXPECTF--
+%AInteractive shell%A5000%A
diff --git a/ext/readline/tests/readline_cli_multiline_states.phpt b/ext/readline/tests/readline_cli_multiline_states.phpt
new file mode 100644
index 000000000000..1aaea49ac963
--- /dev/null
+++ b/ext/readline/tests/readline_cli_multiline_states.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Interactive shell: multiline input states
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (!function_exists('proc_open')) die('skip proc_open() not available');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+$descriptorspec = [['pipe', 'r'], STDOUT, STDERR];
+$proc = proc_open("$php $ini -a", $descriptorspec, $pipes);
+
+fwrite($pipes[0], "\n");
+fwrite($pipes[0], "echo strtoupper('single \\' semi ; brace }\nend');\n");
+fwrite($pipes[0], "echo strtoupper(\"double \\\" semi ; brace }\nend\");\n");
+fwrite($pipes[0], "echo strtoupper(\n\"paren\"\n);\n");
+fwrite($pipes[0], "echo \"arithmetic=\", 6 /\n2 + 100;\n");
+fwrite($pipes[0], "if (true) {\necho strtoupper(\"block_body\n\");\n}\n");
+fwrite($pipes[0], "echo strtoupper(\n\"hash_comment\\n\"\n# ) ;\n);\n");
+fwrite($pipes[0], "echo strtoupper(\n\"slash_comment\\n\"\n// ) ;\n);\n");
+fwrite($pipes[0], "echo strtoupper(\n\"block_comment\\n\" /*\n) ;\n*/\n);\n");
+fwrite($pipes[0], "#[AllowDynamicProperties]\nclass ReadlineCliCoverageClass {}\n");
+fwrite($pipes[0], "echo strtoupper((new ReflectionClass(ReadlineCliCoverageClass::class))->getAttributes()[0]->getName()), \"\\n\";\n");
+fwrite($pipes[0], "if (true) ?>outside } );\n<?php\necho strtoupper(\"outside_ok\\n\");\n");
+fwrite($pipes[0], "quit\n");
+fclose($pipes[0]);
+proc_close($proc);
+?>
+--EXPECTF--
+%AInteractive shell%ASINGLE%ADOUBLE%APAREN%Aarithmetic=103%ABLOCK_BODY%AHASH_COMMENT%ASLASH_COMMENT%ABLOCK_COMMENT%AALLOWDYNAMICPROPERTIES%AOUTSIDE_OK%A
diff --git a/ext/readline/tests/readline_cli_pager.phpt b/ext/readline/tests/readline_cli_pager.phpt
new file mode 100644
index 000000000000..2d5309472324
--- /dev/null
+++ b/ext/readline/tests/readline_cli_pager.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Interactive shell: output through cli.pager
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (!function_exists('proc_open')) die('skip proc_open() not available');
+if (READLINE_LIB !== "readline") die('skip readline only');
+if (PHP_OS_FAMILY === 'Windows') die('skip tr pager is not portable on Windows');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+$descriptorspec = [['pipe', 'r'], STDOUT, STDERR];
+$proc = proc_open("$php $ini -d cli.pager='tr a-z A-Z' -a", $descriptorspec, $pipes);
+fwrite($pipes[0], "echo \"pager output\n\";\n");
+fwrite($pipes[0], "quit\n");
+fclose($pipes[0]);
+proc_close($proc);
+?>
+--EXPECT--
+Interactive shell
+
+php > echo "pager output
+php " ";
+pager output
+PAGER OUTPUT
+php > quit
diff --git a/ext/readline/tests/readline_cli_prompt.phpt b/ext/readline/tests/readline_cli_prompt.phpt
new file mode 100644
index 000000000000..ddb5296cf943
--- /dev/null
+++ b/ext/readline/tests/readline_cli_prompt.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Interactive shell: custom prompt escape sequences
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (READLINE_LIB !== "readline") die('skip readline only');
+if (!function_exists('proc_open')) die('skip proc_open() not available');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+$prompt = 'pre\\\\-\n-\t-\e-\v-\b-\>-\`-\q-' . "\xC3\xA9\xC3\xA9" . '-`echo \'dyn\';`-`-x ';
+$descriptorspec = [['pipe', 'r'], STDOUT, STDERR];
+$proc = proc_open("$php $ini -d " . escapeshellarg("cli.prompt=\"$prompt\"") . " -a", $descriptorspec, $pipes);
+fwrite($pipes[0], "if (true) {\n");
+fwrite($pipes[0], "echo strtoupper(\"prompt_body\\n\");\n");
+fwrite($pipes[0], "}\n");
+fwrite($pipes[0], "quit\n");
+fclose($pipes[0]);
+proc_close($proc);
+?>
+--EXPECTF--
+Interactive shell
+
+
+Warning: prompt contains unsupported unicode characters in Unknown on line 0
+dynpre\-
+-	-%c-%d.%d.%s-php->-`-\q-????-dyn--x if (true) {
+
+Warning: prompt contains unsupported unicode characters in Unknown on line 0
+dynpre\-
+-	-%c-%d.%d.%s-php-{-`-\q-????-dyn--x echo strtoupper("prompt_body\n");
+
+Warning: prompt contains unsupported unicode characters in Unknown on line 0
+dynpre\-
+-	-%c-%d.%d.%s-php-{-`-\q-????-dyn--x }
+
+Warning: prompt contains unsupported unicode characters in Unknown on line 0
+dynPROMPT_BODY
+pre\-
+-	-%c-%d.%d.%s-php->-`-\q-????-dyn--x quit
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.