ShellInputTest.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2018 Justin Hileman
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Psy\Test\Input;
  11. use Psy\Input\CodeArgument;
  12. use Psy\Input\ShellInput;
  13. use Symfony\Component\Console\Input\InputArgument;
  14. use Symfony\Component\Console\Input\InputDefinition;
  15. use Symfony\Component\Console\Input\InputOption;
  16. class ShellInputTest extends \PHPUnit\Framework\TestCase
  17. {
  18. /**
  19. * @expectedException \InvalidArgumentException
  20. * @expectedExceptionMessage Unexpected CodeArgument before the final position: a
  21. */
  22. public function testThrowsWhenCodeArgumentNotInFinalPosition()
  23. {
  24. $definition = new InputDefinition([
  25. new CodeArgument('a', null, CodeArgument::REQUIRED),
  26. new InputArgument('b', null, InputArgument::REQUIRED),
  27. ]);
  28. $input = new ShellInput('foo bar');
  29. $input->bind($definition);
  30. }
  31. public function testInputOptionWithGivenString()
  32. {
  33. $definition = new InputDefinition([
  34. new InputOption('foo', null, InputOption::VALUE_REQUIRED),
  35. new CodeArgument('code', null, CodeArgument::REQUIRED),
  36. ]);
  37. $input = new ShellInput('--foo=bar echo "baz\\\\n";');
  38. $input->bind($definition);
  39. $this->assertSame('bar', $input->getOption('foo'));
  40. $this->assertSame('echo "baz\n";', $input->getArgument('code'));
  41. }
  42. public function testInputOptionWithoutCodeArguments()
  43. {
  44. $definition = new InputDefinition([
  45. new InputOption('foo', null, InputOption::VALUE_REQUIRED),
  46. new InputOption('qux', 'q', InputOption::VALUE_REQUIRED),
  47. new InputArgument('bar', null, InputArgument::REQUIRED),
  48. new InputArgument('baz', null, InputArgument::REQUIRED),
  49. ]);
  50. $input = new ShellInput('--foo=foo -q qux bar "baz\\\\n"');
  51. $input->bind($definition);
  52. $this->assertSame('foo', $input->getOption('foo'));
  53. $this->assertSame('qux', $input->getOption('qux'));
  54. $this->assertSame('bar', $input->getArgument('bar'));
  55. $this->assertSame('baz\\n', $input->getArgument('baz'));
  56. }
  57. public function testInputWithDashDash()
  58. {
  59. $definition = new InputDefinition([
  60. new InputOption('foo', null, InputOption::VALUE_REQUIRED),
  61. new CodeArgument('code', null, CodeArgument::REQUIRED),
  62. ]);
  63. $input = new ShellInput('-- echo --foo::$bar');
  64. $input->bind($definition);
  65. $this->assertNull($input->getOption('foo'));
  66. $this->assertSame('echo --foo::$bar', $input->getArgument('code'));
  67. }
  68. public function testInputWithEmptyString()
  69. {
  70. $definition = new InputDefinition([
  71. new InputOption('foo', null, InputOption::VALUE_REQUIRED),
  72. new CodeArgument('code', null, CodeArgument::REQUIRED),
  73. ]);
  74. $input = new ShellInput('"" --foo bar');
  75. $input->bind($definition);
  76. $this->assertSame('"" --foo bar', $input->getArgument('code'));
  77. }
  78. /**
  79. * @dataProvider getTokenizeData
  80. */
  81. public function testTokenize($input, $tokens, $message)
  82. {
  83. $input = new ShellInput($input);
  84. $r = new \ReflectionClass('Psy\Input\ShellInput');
  85. $p = $r->getProperty('tokenPairs');
  86. $p->setAccessible(true);
  87. $this->assertSame($tokens, $p->getValue($input), $message);
  88. }
  89. public function getTokenizeData()
  90. {
  91. // Test all the cases from StringInput test, ensuring they have an appropriate $rest token.
  92. return [
  93. [
  94. '',
  95. [],
  96. '->tokenize() parses an empty string',
  97. ],
  98. [
  99. 'foo',
  100. [['foo', 'foo']],
  101. '->tokenize() parses arguments',
  102. ],
  103. [
  104. ' foo bar ',
  105. [['foo', 'foo bar '], ['bar', 'bar ']],
  106. '->tokenize() ignores whitespaces between arguments',
  107. ],
  108. [
  109. '"quoted"',
  110. [['quoted', '"quoted"']],
  111. '->tokenize() parses quoted arguments',
  112. ],
  113. [
  114. "'quoted'",
  115. [['quoted', "'quoted'"]],
  116. '->tokenize() parses quoted arguments',
  117. ],
  118. [
  119. "'a\rb\nc\td'",
  120. [["a\rb\nc\td", "'a\rb\nc\td'"]],
  121. '->tokenize() parses whitespace chars in strings',
  122. ],
  123. [
  124. "'a'\r'b'\n'c'\t'd'",
  125. [
  126. ['a', "'a'\r'b'\n'c'\t'd'"],
  127. ['b', "'b'\n'c'\t'd'"],
  128. ['c', "'c'\t'd'"],
  129. ['d', "'d'"],
  130. ],
  131. '->tokenize() parses whitespace chars between args as spaces',
  132. ],
  133. /*
  134. * These don't play nice with unescaping input, but the end result
  135. * is correct, so disable the tests for now.
  136. *
  137. * @todo Sort this out and re-enable these test cases.
  138. */
  139. // [
  140. // '\"quoted\"',
  141. // [['"quoted"', '\"quoted\"']],
  142. // '->tokenize() parses escaped-quoted arguments',
  143. // ],
  144. // [
  145. // "\'quoted\'",
  146. // [['\'quoted\'', "\'quoted\'"]],
  147. // '->tokenize() parses escaped-quoted arguments',
  148. // ],
  149. [
  150. '-a',
  151. [['-a', '-a']],
  152. '->tokenize() parses short options',
  153. ],
  154. [
  155. '-azc',
  156. [['-azc', '-azc']],
  157. '->tokenize() parses aggregated short options',
  158. ],
  159. [
  160. '-awithavalue',
  161. [['-awithavalue', '-awithavalue']],
  162. '->tokenize() parses short options with a value',
  163. ],
  164. [
  165. '-a"foo bar"',
  166. [['-afoo bar', '-a"foo bar"']],
  167. '->tokenize() parses short options with a value',
  168. ],
  169. [
  170. '-a"foo bar""foo bar"',
  171. [['-afoo barfoo bar', '-a"foo bar""foo bar"']],
  172. '->tokenize() parses short options with a value',
  173. ],
  174. [
  175. '-a\'foo bar\'',
  176. [['-afoo bar', '-a\'foo bar\'']],
  177. '->tokenize() parses short options with a value',
  178. ],
  179. [
  180. '-a\'foo bar\'\'foo bar\'',
  181. [['-afoo barfoo bar', '-a\'foo bar\'\'foo bar\'']],
  182. '->tokenize() parses short options with a value',
  183. ],
  184. [
  185. '-a\'foo bar\'"foo bar"',
  186. [['-afoo barfoo bar', '-a\'foo bar\'"foo bar"']],
  187. '->tokenize() parses short options with a value',
  188. ],
  189. [
  190. '--long-option',
  191. [['--long-option', '--long-option']],
  192. '->tokenize() parses long options',
  193. ],
  194. [
  195. '--long-option=foo',
  196. [['--long-option=foo', '--long-option=foo']],
  197. '->tokenize() parses long options with a value',
  198. ],
  199. [
  200. '--long-option="foo bar"',
  201. [['--long-option=foo bar', '--long-option="foo bar"']],
  202. '->tokenize() parses long options with a value',
  203. ],
  204. [
  205. '--long-option="foo bar""another"',
  206. [['--long-option=foo baranother', '--long-option="foo bar""another"']],
  207. '->tokenize() parses long options with a value',
  208. ],
  209. [
  210. '--long-option=\'foo bar\'',
  211. [['--long-option=foo bar', '--long-option=\'foo bar\'']],
  212. '->tokenize() parses long options with a value',
  213. ],
  214. [
  215. "--long-option='foo bar''another'",
  216. [['--long-option=foo baranother', "--long-option='foo bar''another'"]],
  217. '->tokenize() parses long options with a value',
  218. ],
  219. [
  220. "--long-option='foo bar'\"another\"",
  221. [['--long-option=foo baranother', "--long-option='foo bar'\"another\""]],
  222. '->tokenize() parses long options with a value',
  223. ],
  224. [
  225. 'foo -a -ffoo --long bar',
  226. [
  227. ['foo', 'foo -a -ffoo --long bar'],
  228. ['-a', '-a -ffoo --long bar'],
  229. ['-ffoo', '-ffoo --long bar'],
  230. ['--long', '--long bar'],
  231. ['bar', 'bar'],
  232. ],
  233. '->tokenize() parses when several arguments and options',
  234. ],
  235. ];
  236. }
  237. }