An AperiTestCase decorator. Test shell scripts, including interactive scripts. Read from sdout/stderr and write to stdin.
runScript( $command, - shell $command $cwd = null, - optionally pass $cwd (absolute path) $config = array()) - Optionally override default settings. (see InteractiveScript::_defaultConfig)
enter($input) - enter some input at a script prompt
assertStdout($text) - Assert the last chunk of text read from script STDOUT. In a non-interactive script the "last chunk" is the full stdout output. In an interactive script this is one of: (a) the initial stdout output up to and including the first prompt after launching the script (b) all stdout text after a stdin input up to and including the next prompt
assertStderr($text) - stderr since last prompt or start of script
assertStdoutHas($pattern) - assert a perl regex pattern in the last stdout chunk
assertStderrHas($pattern) - assert a perl regex pattern in the last stderr chunk
assertConversationHas($pattern) - assert a perl regex pattern in the full conversation ie all stdin inputs and all stdout / stderr chunks
assertExitCode($i) - the script's exit code
dumpStdout() - print the last chunk of text read from script stdout
dumpStderr() - print the last chunk of text read from script stderr
dumpConversation() - print all inputs, all stderr chunks & all stdout chunks (in the sequence in which they were read by the script wrapper: this may differ from the sequence in which they were written by the script).
dumpStatus() - proc_get_status() call: command, pid, running etc. From the manual: "The exit code returned by the process (which is only meaningful if running is FALSE). Only the first call of this function returns the real value, the next call returns -1".
A test might look something like this. First, the script to test, greet.php:
<?php
fwrite("hello world! hello! hello!\n");
exit(0);
Decorate the standard test case with ShellTestTools:
<?php
class YourTestCase extends AperiUnitTest {
function beforeInvoking() {
$this->mixin('ShellTestTools');
}
Test the script:
<?php
class YourTestCase extends AperiUnitTest {
function beforeInvoking() {
...
}
function test() {
$this->runScript('php greet.php');
$this->assertStdoutHas('/hello world/i');
}
Often it's better to assert a few, significant parts of the output rather than an identical string comparison of the entire text.
The ~Has() methods accept a variable number of aguments. You can optionally specify the number of matches:
<?php
class YourTestCase extends AperiUnitTest {
...
...
function test() {
$this->runScript('php greet.php');
$this->assertStdoutHas(3, "/hello/");
}
Use $this->enter($input) for script inputs. This has the same effect as typing in the terminal and hitting enter.
For convenience, the $input arg doesn't have to end in a newline. This will be added automatically if it doesn't exist.
Suppose you want to test the script, petshop.php:
<?php
fwrite(STDOUT, "what's your pet?\n");
$reply = trim(fgets(STDIN));
if('exit' == $reply) {
exit(0);
}
if('Norwegian Blue' == $reply) {
fwrite(STDERR, "he doesn't look at all well\n");
exit(1);
} else {
fwrite(STDOUT, "that's a nice $reply\n");
exit(0);
}
<?php
class YourTestCase extends AperiUnitTest {
function beforeInvoking() {
$this->mixin('ShellTestTools');
}
function test() {
$this->runScript('php petshop.php');
$this->assertStdoutHas("/what's your pet\?/i");
$this->enter('gannet');
$this->assertStdoutHas("/that's a nice gannet/i");
}
To check the stderr output you'd do a similar test with "Norwegian Blue" as the input.
Note that, in an interactive script, assertStdout() does not match against the full stdout output, just the chunks read between prompts. As soon as you enter($input), buffers are filled with the next stdout chunk. The methods: assertStdout, assertStdoutHas, assertStderr, and assertStderrHas all work in this way. Only assertConversationHas looks at the full "conversation" ie the combined stdin, stderr and stdout text in the sequence in which they were read by the wrapper class (note that this may differ slightly from the order in which the writes were made by the wrapped script).
Thus, you can test all the logic pathways in a complex script which contains a series of prompts and inputs. Enter an input, assert the script's stdout/stderr reponse to the input, and repeat until done.
If an interactive test is failing, but you're not sure why, try calling $this->dumpConversation() to see the big picture.