CommandTest.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <?php
  2. use MediaWiki\Shell\Command;
  3. use Wikimedia\TestingAccessWrapper;
  4. /**
  5. * @covers \MediaWiki\Shell\Command
  6. * @group Shell
  7. */
  8. class CommandTest extends PHPUnit\Framework\TestCase {
  9. use MediaWikiCoversValidator;
  10. private function requirePosix() {
  11. if ( wfIsWindows() ) {
  12. $this->markTestSkipped( 'This test requires a POSIX environment.' );
  13. }
  14. }
  15. /**
  16. * @dataProvider provideExecute
  17. */
  18. public function testExecute( $commandInput, $expectedExitCode, $expectedOutput ) {
  19. $this->requirePosix();
  20. $command = new Command();
  21. $result = $command
  22. ->params( $commandInput )
  23. ->execute();
  24. $this->assertSame( $expectedExitCode, $result->getExitCode() );
  25. $this->assertSame( $expectedOutput, $result->getStdout() );
  26. }
  27. public function provideExecute() {
  28. return [
  29. 'success status' => [ 'true', 0, '' ],
  30. 'failure status' => [ 'false', 1, '' ],
  31. 'output' => [ [ 'echo', '-n', 'x', '>', 'y' ], 0, 'x > y' ],
  32. ];
  33. }
  34. public function testEnvironment() {
  35. $this->requirePosix();
  36. $command = new Command();
  37. $result = $command
  38. ->params( [ 'printenv', 'FOO' ] )
  39. ->environment( [ 'FOO' => 'bar' ] )
  40. ->execute();
  41. $this->assertSame( "bar\n", $result->getStdout() );
  42. }
  43. public function testStdout() {
  44. $this->requirePosix();
  45. $command = new Command();
  46. $result = $command
  47. ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
  48. ->execute();
  49. $this->assertNotContains( 'ThisIsStderr', $result->getStdout() );
  50. $this->assertEquals( "ThisIsStderr\n", $result->getStderr() );
  51. }
  52. public function testStdoutRedirection() {
  53. $this->requirePosix();
  54. $command = new Command();
  55. $result = $command
  56. ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
  57. ->includeStderr( true )
  58. ->execute();
  59. $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
  60. $this->assertNull( $result->getStderr() );
  61. }
  62. public function testOutput() {
  63. global $IP;
  64. $this->requirePosix();
  65. chdir( $IP );
  66. $command = new Command();
  67. $result = $command
  68. ->params( [ 'ls', 'index.php' ] )
  69. ->execute();
  70. $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
  71. $this->assertSame( null, $result->getStderr() );
  72. $command = new Command();
  73. $result = $command
  74. ->params( [ 'ls', 'index.php', 'no-such-file' ] )
  75. ->includeStderr()
  76. ->execute();
  77. $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
  78. $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStdout() );
  79. $this->assertSame( null, $result->getStderr() );
  80. $command = new Command();
  81. $result = $command
  82. ->params( [ 'ls', 'index.php', 'no-such-file' ] )
  83. ->execute();
  84. $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
  85. $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStderr() );
  86. }
  87. /**
  88. * Test that null values are skipped by params() and unsafeParams()
  89. */
  90. public function testNullsAreSkipped() {
  91. $command = TestingAccessWrapper::newFromObject( new Command );
  92. $command->params( 'echo', 'a', null, 'b' );
  93. $command->unsafeParams( 'c', null, 'd' );
  94. $this->assertEquals( "'echo' 'a' 'b' c d", $command->command );
  95. }
  96. public function testT69870() {
  97. $commandLine = wfIsWindows()
  98. // 333 = 331 + CRLF
  99. ? ( 'for /l %i in (1, 1, 1001) do @echo ' . str_repeat( '*', 331 ) )
  100. : 'printf "%-333333s" "*"';
  101. // Test several times because it involves a race condition that may randomly succeed or fail
  102. for ( $i = 0; $i < 10; $i++ ) {
  103. $command = new Command();
  104. $output = $command->unsafeParams( $commandLine )
  105. ->execute()
  106. ->getStdout();
  107. $this->assertEquals( 333333, strlen( $output ) );
  108. }
  109. }
  110. public function testLogStderr() {
  111. $this->requirePosix();
  112. $logger = new TestLogger( true, function ( $message, $level, $context ) {
  113. return $level === Psr\Log\LogLevel::ERROR ? '1' : null;
  114. }, true );
  115. $command = new Command();
  116. $command->setLogger( $logger );
  117. $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
  118. $command->execute();
  119. $this->assertEmpty( $logger->getBuffer() );
  120. $command = new Command();
  121. $command->setLogger( $logger );
  122. $command->logStderr();
  123. $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
  124. $command->execute();
  125. $this->assertSame( 1, count( $logger->getBuffer() ) );
  126. $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 'ThisIsStderr' );
  127. }
  128. public function testInput() {
  129. $this->requirePosix();
  130. $command = new Command();
  131. $command->params( 'cat' );
  132. $command->input( 'abc' );
  133. $result = $command->execute();
  134. $this->assertSame( 'abc', $result->getStdout() );
  135. // now try it with something that does not fit into a single block
  136. $command = new Command();
  137. $command->params( 'cat' );
  138. $command->input( str_repeat( '!', 1000000 ) );
  139. $result = $command->execute();
  140. $this->assertSame( 1000000, strlen( $result->getStdout() ) );
  141. // And try it with empty input
  142. $command = new Command();
  143. $command->params( 'cat' );
  144. $command->input( '' );
  145. $result = $command->execute();
  146. $this->assertSame( '', $result->getStdout() );
  147. }
  148. }