Создание интерактивной команды CLI с ожиданием завершает работу с 0 раньше, при запуске как Symfony Process в контексте Behat

Это довольно сложный вопрос, возможно, знание Symfony и Behat может не понадобиться для понимания проблемы.

Итак, чтобы протестировать ввод и вывод интерактивного приложения CLI, bin/albumgrab которое я написал на PHP с использованием компонента консоли Symfony, я настроил свой контекст функции Behat для создания expect скрипта, запускаемого через exec.

Эта команда exec запускается в PHP через 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();

Однако, когда он проходит второй вход, он, кажется, выходит, но успешно.

Вызов:

$process->setTty(true);

Заставляет его работать полностью, но будет печатать прямо на стандартный вывод, и я больше не могу захватывать вывод, чтобы сделать утверждение, даже с буферизацией вывода PHP.

Я решил, что PTY в любом случае будет более подходящим:

$process->setPty(true);

Поскольку это было решение этого вопроса StackOverflow. Однако это поддерживается не везде, по крайней мере, не в Mac OS X.

Вы можете увидеть, что я уже пытался сделать в Github: https://github.com/adamelso/albumgrab/pull/13/files и выходные данные Travis для последней попытки https://travis-ci.org/adamelso/albumgrab/jobs/61137499

Итак, мой главный вопрос: почему он продолжает выходить с 0 раньше и как это предотвратить?


person Adam Elsodaney    schedule 04.05.2015    source источник


Ответы (2)


Согласно ответу на Ожидание зависания команды ожидания сценария, вам нужно дождаться EOF вместо использования команды interact:

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

Вот полный скрипт, который я использовал для тестирования на 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());
}
person Jakub Zalas    schedule 07.05.2015
comment
Кстати, если вы тестируете консольную команду Symfony, есть лучшие способы ее тестирования: symfony.com/doc/current/components/console/helpers/ - person Jakub Zalas; 07.05.2015
comment
Ура, Куба, все работает! Мне пришлось запустить мою команду без вывода ANSI и заменить управляющие символы новой строки на PHP_EOL - person Adam Elsodaney; 08.05.2015
comment
тем не менее, было бы лучше использовать тестер команд Symfony, но я думаю, что есть смысл провести один дымовой тест (если я захочу отойти от Symfony... не то чтобы я бы стал ;). - person Adam Elsodaney; 08.05.2015

Чтобы иметь возможность получить ответ на вопрос, вам обязательно нужен Терминал (TTY) или Псевдотерминал (PTY) для получения любого пользовательского ввода.

Вот почему — без $process->setTty(true) или setPty(true)QuestionHelper молча возвращается к значению по умолчанию, и команда завершается успешно с кодом выхода 0.

Теперь, чтобы протестировать вашу команду на примере пользовательского ввода, вы должны использовать вспомогательные компоненты консоли symfony вместо ожидаемого.

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

Как использовать эти помощники, описано в главе поваренной книги Тестирование команды, ожидающей ввода.

person Nicolai Fröhlich    schedule 04.05.2015
comment
Что интересно, даже без вызова setTty() и setPty() команда все равно получает ответ на оба вопроса. Он бы не перешел ко второму вопросу, если бы не получил ответа на первый (верно?). Что делает еще более странным, почему после получения ответа на второй вопрос команда внезапно завершает работу...? - person Adam Elsodaney; 04.05.2015