3

This is quite an advanced question, perhaps knowledge of Symfony and Behat may not be necessary to understand the problem.

So in order to test the input and output of an interactive CLI app bin/albumgrab I've written in PHP using the Symfony Console component, I've set up my Behat feature context to build an expect script, run via exec.

This exec command is run in PHP via Symfony Process.

$expect = <<<BASH
exec expect -c '
    set timeout 180
    spawn bin/albumgrab
    expect "Please enter the name of the directory"
    send "/tmp/php-london\n"
    expect "Please enter the URL to the first image"
    send "https://www.facebook.com/PeeHPLondon/photos/pb.7119218495.-2207520000.1430669248./10153559172718496/?type=3&src=https%3A%2F%2Ffbcdn-sphotos-g-a.akamaihd.net%2Fhphotos-ak-xfp1%2Fv%2Ft1.0-9%2F10986697_10153559172718496_5727444485530442900_n.jpg%3Foh%3Dc47770f4cd15fecc6888bcd504899087%26oe%3D55DA9CB0%26__gda__%3D1439174101_7c78a93bf247dbad6c56681b6db5309c&size=960%2C959&fbid=10153559172718496\\n"
    interact
'

BASH;

$process = new Symfony\Component\Process\Process($expect);

$process->mustRun();

However, when it gets past the second input, it seems to exit but successfully.

Calling:

$process->setTty(true);

Makes it run all the way through, but will print straight to stdout and I can no longer capture the output to make an assertion, not even with PHP's output buffering.

I figured PTY would be more suitable anyway:

$process->setPty(true);

As it was the solution to this StackOverflow question. However, this is not supported everywhere, at least not on Mac OS X.

You can see what I've attempted so far in Github: https://github.com/adamelso/albumgrab/pull/13/files and the Travis output for the last attempt https://travis-ci.org/adamelso/albumgrab/jobs/61137499

So my main question is why it keeps exiting with 0 early and how to prevent it?

Community
  • 1
  • 1
Adam Elsodaney
  • 7,722
  • 6
  • 39
  • 65

2 Answers2

1

In order to be able receive an answer to a question - you definitely need a Terminal (TTY) or a Pseudo-Terminal (PTY) to obtain any user-input.

That's why - without $process->setTty(true) or setPty(true) - the QuestionHelper silently falls back to the default value and the command succeeds with exit code 0.

Now - to test your command with example user input - you should make use of the symfony's console helper components instead of using expect.

Symfony\Component\Console\Helper\HelperSet 
Symfony\Component\Console\Tester\CommandTester

How to use these helpers is described in the cookbook chapter Testing a Command that expects input.

Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • What's interesting is that even without calling `setTty()` and `setPty()`, the command still receives an answer to both questions. It wouldn't have proceeded to the second question had it not received an answer for the first (right?). Which makes it more weird as to why after the answer to the second question has been received, the command exits suddenly...? – Adam Elsodaney May 04 '15 at 16:00
1

As per the answer to Expect Script wait Command Hangs you need to wait for EOF instead of using the interact command:

set timeout 180
spawn ./bin/albumgrab
expect "Please enter the name of the directory"
send "/tmp/php-london\n"
expect "Please enter the URL to the first image"
send "https://www.facebook.com/PeeHPLondon/photos/pb.7119218495.-2207520000.1430669248./10153559172718496/\n"
expect EOF

Here's a full script I've used to test it on an OS X:

<?php

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\Process\Process;

$expect = <<<BASH
exec expect -c '
    set timeout 180
    spawn ./bin/albumgrab
    expect "Please enter the name of the directory"
    send "/tmp/php-london\n"
    expect "Please enter the URL to the first image"
    send "https://www.facebook.com/PeeHPLondon/photos/pb.7119218495.-2207520000.1430669248./10153559172718496/?type=3&src=https%3A%2F%2Ffbcdn-sphotos-g-a.akamaihd.net%2Fhphotos-ak-xfp1%2Fv%2Ft1.0-9%2F10986697_10153559172718496_5727444485530442900_n.jpg%3Foh%3Dc47770f4cd15fecc6888bcd504899087%26oe%3D55DA9CB0%26__gda__%3D1439174101_7c78a93bf247dbad6c56681b6db5309c&size=960%2C959&fbid=10153559172718496\\n"
    expect EOF
'

BASH;

$process = new Process($expect);
$process->setPty(true);
$process->start();

$process->wait(function ($type, $buffer) {
    if (Process::ERR === $type) {
        echo 'ERR > '.$buffer;
    } else {
        echo 'OUT > '.$buffer;
    }
});

if (!$process->isSuccessful()) {
    throw new \RuntimeException($process->getErrorOutput());
}
Community
  • 1
  • 1
Jakub Zalas
  • 35,761
  • 9
  • 93
  • 125
  • 1
    Btw, if what you're testing is a Symfony Console command, there are better ways of testing it: http://symfony.com/doc/current/components/console/helpers/questionhelper.html#testing-a-command-that-expects-input – Jakub Zalas May 07 '15 at 15:23
  • Cheers Kuba, it's working! I had to run my command without ANSI output and [replace newline control characters with PHP_EOL](https://github.com/adamelso/albumgrab/pull/13/files#diff-8a50a8f3567ab9a03107fd86eeaf827eR124) – Adam Elsodaney May 08 '15 at 19:07
  • that said, it would be better to use Symfony's command tester, but I think there is value in doing one smoke test (should I want to move away from Symfony... not that I would ;). – Adam Elsodaney May 08 '15 at 19:12