ShellInputTest.php 8.8 KB

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