From 54d3d63cf15bacbfa331a26883781f15009fe238 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 8 Jun 2016 20:13:57 +0200 Subject: [PATCH] Centralize input stream in base Input class Add StreamableInputInterface Removed commented code Prevent BC by looking for QuestionHelper:: Logic fixes Check for that implements StreamableInputInterface Rollback E_USER_DEPRECATED notice in getInputStream Remove legacy tests to avoid KO because of deprecations Add missing use Keep old tests marked as legacy Undeprecate getInputStream, CS Fixes Move legacy tests in separated class Revert separated legacy test class Keep legacy createInputInterfaceMock() Depreciate QuestionHelper::getInputStream() --- src/Symfony/Component/Console/Application.php | 16 +- .../Console/Helper/QuestionHelper.php | 19 +- src/Symfony/Component/Console/Input/Input.php | 19 +- .../Input/StreamableInputInterface.php | 37 ++ .../Console/Tests/ApplicationTest.php | 2 +- .../Tests/Helper/QuestionHelperTest.php | 453 ++++++++++++++++-- .../Console/Tests/Input/InputTest.php | 8 + 7 files changed, 502 insertions(+), 52 deletions(-) create mode 100644 src/Symfony/Component/Console/Input/StreamableInputInterface.php diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index ae737ad45dcca..0b7fe9b19c000 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Helper\ProcessHelper; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; @@ -769,8 +770,19 @@ protected function configureIO(InputInterface $input, OutputInterface $output) if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) { $input->setInteractive(false); - } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { - $inputStream = $this->getHelperSet()->get('question')->getInputStream(); + } elseif (function_exists('posix_isatty')) { + $inputStream = null; + + if ($input instanceof StreamableInputInterface) { + $inputStream = $input->getStream(); + } + + // This check ensures that calling QuestionHelper::setInputStream() works + // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream) + if (!$inputStream && $this->getHelperSet()->has('question')) { + $inputStream = $this->getHelperSet()->get('question')->getInputStream(false); + } + if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { $input->setInteractive(false); } diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index c6f5a409c3dc8..c6d7af60aa8a3 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StreamableInputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -52,6 +53,10 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu return $question->getDefault(); } + if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) { + $this->inputStream = $stream; + } + if (!$question->getValidator()) { return $this->doAsk($output, $question); } @@ -68,12 +73,17 @@ public function ask(InputInterface $input, OutputInterface $output, Question $qu * * This is mainly useful for testing purpose. * + * @deprecated since version 3.2, to be removed in 4.0. Use + * StreamableInputInterface::setStream() instead. + * * @param resource $stream The input stream * * @throws InvalidArgumentException In case the stream is not a resource */ public function setInputStream($stream) { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s:setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); + if (!is_resource($stream)) { throw new InvalidArgumentException('Input stream must be a valid resource.'); } @@ -84,10 +94,17 @@ public function setInputStream($stream) /** * Returns the helper's input stream. * + * @deprecated since version 3.2, to be removed in 4.0. Use + * StreamableInputInterface::getStream() instead. + * * @return resource */ - public function getInputStream() + public function getInputStream($triggerDeprecationError = true) { + if ($triggerDeprecationError) { + @trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use %s:getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED); + } + return $this->inputStream; } diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index 85499fc489d36..2a8d0628aa291 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -25,12 +25,13 @@ * * @author Fabien Potencier */ -abstract class Input implements InputInterface +abstract class Input implements InputInterface, StreamableInputInterface { /** * @var InputDefinition */ protected $definition; + protected $stream; protected $options = array(); protected $arguments = array(); protected $interactive = true; @@ -233,4 +234,20 @@ public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } + + /** + * {@inheritdoc} + */ + public function setStream($stream) + { + $this->stream = $stream; + } + + /** + * {@inheritdoc} + */ + public function getStream() + { + return $this->stream; + } } diff --git a/src/Symfony/Component/Console/Input/StreamableInputInterface.php b/src/Symfony/Component/Console/Input/StreamableInputInterface.php new file mode 100644 index 0000000000000..d7e462f244431 --- /dev/null +++ b/src/Symfony/Component/Console/Input/StreamableInputInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StreamableInputInterface is the interface implemented by all input classes + * that have an input stream. + * + * @author Robin Chalas + */ +interface StreamableInputInterface extends InputInterface +{ + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setStream($stream); + + /** + * Returns the input stream. + * + * @return resource|null + */ + public function getStream(); +} diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 954d02eaad963..493299ba11091 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -1099,7 +1099,7 @@ public function testCanCheckIfTerminalIsInteractive() $this->assertFalse($tester->getInput()->hasParameterOption(array('--no-interaction', '-n'))); - $inputStream = $application->getHelperSet()->get('question')->getInputStream(); + $inputStream = $tester->getInput()->getStream(); $this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream)); } } diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 6a4f8aceae9fe..84abeaa300735 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Input\StreamableInputInterface; /** * @group tty @@ -34,22 +35,22 @@ public function testAskChoice() $heroes = array('Superman', 'Batman', 'Spiderman'); - $questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + $inputStream = $this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n"); $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2'); $question->setMaxAttempts(1); // first answer is an empty answer, we're supposed to receive the default value - $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Spiderman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); $question->setMaxAttempts(1); - $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); $question->setErrorMessage('Input "%s" is not a superhero!'); $question->setMaxAttempts(2); - $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question)); rewind($output->getStream()); $stream = stream_get_contents($output->getStream()); @@ -58,7 +59,7 @@ public function testAskChoice() try { $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1'); $question->setMaxAttempts(1); - $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question); + $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question); $this->fail(); } catch (\InvalidArgumentException $e) { $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); @@ -68,34 +69,34 @@ public function testAskChoice() $question->setMaxAttempts(1); $question->setMultiselect(true); - $this->assertEquals(array('Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1'); $question->setMaxAttempts(1); $question->setMultiselect(true); - $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 '); $question->setMaxAttempts(1); $question->setMultiselect(true); - $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } public function testAsk() { $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream("\n8AM\n")); + $inputStream = $this->getInputStream("\n8AM\n"); $question = new Question('What time is it?', '2PM'); - $this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('2PM', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new Question('What time is it?', '2PM'); - $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question)); rewind($output->getStream()); $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); @@ -118,21 +119,20 @@ public function testAskWithAutocomplete() $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); $dialog = new QuestionHelper(); - $dialog->setInputStream($inputStream); $helperSet = new HelperSet(array(new FormatterHelper())); $dialog->setHelperSet($helperSet); $question = new Question('Please select a bundle', 'FrameworkBundle'); $question->setAutocompleterValues(array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle')); - $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } public function testAskWithAutocompleteWithNonSequentialKeys() @@ -145,14 +145,13 @@ public function testAskWithAutocompleteWithNonSequentialKeys() $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n"); $dialog = new QuestionHelper(); - $dialog->setInputStream($inputStream); $dialog->setHelperSet(new HelperSet(array(new FormatterHelper()))); $question = new ChoiceQuestion('Please select a bundle', array(1 => 'AcmeDemoBundle', 4 => 'AsseticBundle')); $question->setMaxAttempts(1); - $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } public function testAskHiddenResponse() @@ -162,12 +161,11 @@ public function testAskHiddenResponse() } $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream("8AM\n")); $question = new Question('What time is it?'); $question->setHidden(true); - $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("8AM\n")), $this->createOutputInterface(), $question)); } /** @@ -177,9 +175,9 @@ public function testAskConfirmation($question, $expected, $default = true) { $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream($question."\n")); + $inputStream = $this->getInputStream($question."\n"); $question = new ConfirmationQuestion('Do you like French fries?', $default); - $this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); + $this->assertEquals($expected, $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); } public function getAskConfirmationData() @@ -198,11 +196,11 @@ public function testAskConfirmationWithCustomTrueAnswer() { $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream("j\ny\n")); + $inputStream = $this->getInputStream("j\ny\n"); $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); - $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertTrue($dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); - $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertTrue($dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } public function testAskAndValidate() @@ -224,13 +222,12 @@ public function testAskAndValidate() $question->setValidator($validator); $question->setMaxAttempts(2); - $dialog->setInputStream($this->getInputStream("\nblack\n")); - $this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); - $this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $inputStream = $this->getInputStream("\nblack\n"); + $this->assertEquals('white', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); + $this->assertEquals('black', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); - $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); try { - $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("green\nyellow\norange\n")), $this->createOutputInterface(), $question); $this->fail(); } catch (\InvalidArgumentException $e) { $this->assertEquals($error, $e->getMessage()); @@ -249,13 +246,12 @@ public function testSelectChoiceFromSimpleChoices($providedAnswer, $expectedValu ); $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); $helperSet = new HelperSet(array(new FormatterHelper())); $dialog->setHelperSet($helperSet); $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); $question->setMaxAttempts(1); - $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question); $this->assertSame($expectedValue, $answer); } @@ -285,13 +281,12 @@ public function testChoiceFromChoicelistWithMixedKeys($providedAnswer, $expected ); $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); $helperSet = new HelperSet(array(new FormatterHelper())); $dialog->setHelperSet($helperSet); $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); $question->setMaxAttempts(1); - $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question); $this->assertSame($expectedValue, $answer); } @@ -320,13 +315,12 @@ public function testSelectChoiceFromChoiceList($providedAnswer, $expectedValue) ); $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); $helperSet = new HelperSet(array(new FormatterHelper())); $dialog->setHelperSet($helperSet); $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); $question->setMaxAttempts(1); - $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question); $this->assertSame($expectedValue, $answer); } @@ -344,14 +338,13 @@ public function testAmbiguousChoiceFromChoicelist() ); $dialog = new QuestionHelper(); - $dialog->setInputStream($this->getInputStream("My environment\n")); $helperSet = new HelperSet(array(new FormatterHelper())); $dialog->setHelperSet($helperSet); $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); $question->setMaxAttempts(1); - $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("My environment\n")), $this->createOutputInterface(), $question); } public function answerProvider() @@ -368,7 +361,7 @@ public function testNoInteraction() { $dialog = new QuestionHelper(); $question = new Question('Do you have a job?', 'not yet'); - $this->assertEquals('not yet', $dialog->ask($this->createInputInterfaceMock(false), $this->createOutputInterface(), $question)); + $this->assertEquals('not yet', $dialog->ask($this->createStreamableInputInterfaceMock(null, false), $this->createOutputInterface(), $question)); } /** @@ -391,6 +384,356 @@ public function testChoiceOutputFormattingQuestionForUtf8Keys() $output = $this->getMock('\Symfony\Component\Console\Output\OutputInterface'); $output->method('getFormatter')->willReturn(new OutputFormatter()); + $dialog = new QuestionHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $output->expects($this->once())->method('writeln')->with($this->equalTo($outputShown)); + + $question = new ChoiceQuestion($question, $possibleChoices, 'foo'); + $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("\n")), $output, $question); + } + + /** + * @group legacy + */ + public function testLegacyAskChoice() + { + $questionHelper = new QuestionHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $questionHelper->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2'); + $question->setMaxAttempts(1); + // first answer is an empty answer, we're supposed to receive the default value + $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setMaxAttempts(1); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes); + $question->setErrorMessage('Input "%s" is not a superhero!'); + $question->setMaxAttempts(2); + $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $stream = stream_get_contents($output->getStream()); + $this->assertContains('Input "Fabien" is not a superhero!', $stream); + + try { + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1'); + $question->setMaxAttempts(1); + $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals(array('Superman', 'Spiderman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1'); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 '); + $question->setMaxAttempts(1); + $question->setMultiselect(true); + + $this->assertEquals(array('Superman', 'Batman'), $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @group legacy + */ + public function testLegacyAsk() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("\n8AM\n")); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $question = new Question('What time is it?', '2PM'); + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question)); + + rewind($output->getStream()); + $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); + } + + /** + * @group legacy + */ + public function testLegacyAskWithAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle')); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @group legacy + */ + public function testLegacyAskWithAutocompleteWithNonSequentialKeys() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // + $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $dialog->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $question = new ChoiceQuestion('Please select a bundle', array(1 => 'AcmeDemoBundle', 4 => 'AsseticBundle')); + $question->setMaxAttempts(1); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @group legacy + */ + public function testLegacyAskHiddenResponse() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $question = new Question('What time is it?'); + $question->setHidden(true); + + $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @group legacy + * @dataProvider getAskConfirmationData + */ + public function testLegacyAskConfirmation($question, $expected, $default = true) + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream($question."\n")); + $question = new ConfirmationQuestion('Do you like French fries?', $default); + $this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel')); + } + + /** + * @group legacy + */ + public function testLegacyAskConfirmationWithCustomTrueAnswer() + { + $dialog = new QuestionHelper(); + + $dialog->setInputStream($this->getInputStream("j\ny\n")); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i'); + $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + + /** + * @group legacy + */ + public function testLegacyAskAndValidate() + { + $dialog = new QuestionHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $error = 'This is not a color!'; + $validator = function ($color) use ($error) { + if (!in_array($color, array('white', 'black'))) { + throw new \InvalidArgumentException($error); + } + + return $color; + }; + + $question = new Question('What color was the white horse of Henry IV?', 'white'); + $question->setValidator($validator); + $question->setMaxAttempts(2); + + $dialog->setInputStream($this->getInputStream("\nblack\n")); + $this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + + $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); + try { + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($error, $e->getMessage()); + } + } + + /** + * @group legacy + * @dataProvider simpleAnswerProvider + */ + public function testLegacySelectChoiceFromSimpleChoices($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'My environment 1', + 'My environment 2', + 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + /** + * @group legacy + * @dataProvider mixedKeysChoiceListAnswerProvider + */ + public function testLegacyChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue) + { + $possibleChoices = array( + '0' => 'No environment', + '1' => 'My environment 1', + 'env_2' => 'My environment 2', + 3 => 'My environment 3', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + /** + * @group legacy + * @dataProvider answerProvider + */ + public function testLegacySelectChoiceFromChoiceList($providedAnswer, $expectedValue) + { + $possibleChoices = array( + 'env_1' => 'My environment 1', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream($providedAnswer."\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + + $this->assertSame($expectedValue, $answer); + } + + /** + * @group legacy + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The provided answer is ambiguous. Value should be one of env_2 or env_3. + */ + public function testLegacyAmbiguousChoiceFromChoicelist() + { + $possibleChoices = array( + 'env_1' => 'My first environment', + 'env_2' => 'My environment', + 'env_3' => 'My environment', + ); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($this->getInputStream("My environment\n")); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices); + $question->setMaxAttempts(1); + + $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question); + } + + /** + * @requires function mb_strwidth + * @group legacy + */ + public function testLegacyChoiceOutputFormattingQuestionForUtf8Keys() + { + $question = 'Lorem ipsum?'; + $possibleChoices = array( + 'foo' => 'foo', + 'żółw' => 'bar', + 'łabądź' => 'baz', + ); + $outputShown = array( + $question, + ' [foo ] foo', + ' [żółw ] bar', + ' [łabądź] baz', + ); + $output = $this->getMock('\Symfony\Component\Console\Output\OutputInterface'); + $output->method('getFormatter')->willReturn(new OutputFormatter()); + $dialog = new QuestionHelper(); $dialog->setInputStream($this->getInputStream("\n")); $helperSet = new HelperSet(array(new FormatterHelper())); @@ -426,6 +769,22 @@ protected function createInputInterfaceMock($interactive = true) return $mock; } + protected function createStreamableInputInterfaceMock($stream = null, $interactive = true) + { + $mock = $this->getMock(StreamableInputInterface::class); + $mock->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue($interactive)); + + if ($stream) { + $mock->expects($this->any()) + ->method('getStream') + ->willReturn($stream); + } + + return $mock; + } + private function hasSttyAvailable() { exec('stty 2>&1', $output, $exitcode); diff --git a/src/Symfony/Component/Console/Tests/Input/InputTest.php b/src/Symfony/Component/Console/Tests/Input/InputTest.php index eb1c6617f5644..ef7e5c4309a30 100644 --- a/src/Symfony/Component/Console/Tests/Input/InputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/InputTest.php @@ -129,4 +129,12 @@ public function testSetGetInteractive() $input->setInteractive(false); $this->assertFalse($input->isInteractive(), '->setInteractive() changes the interactive flag'); } + + public function testSetGetStream() + { + $input = new ArrayInput(array()); + $stream = fopen('php://memory', 'r+', false); + $input->setStream($stream); + $this->assertSame($stream, $input->getStream()); + } }