Merge "Type hint against LinkTarget in WatchedItemStore"
[lhc/web/wiklou.git] / tests / phpunit / includes / shell / CommandTest.php
1 <?php
2
3 use MediaWiki\Shell\Command;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * @covers \MediaWiki\Shell\Command
8 * @group Shell
9 */
10 class CommandTest extends PHPUnit\Framework\TestCase {
11
12 use MediaWikiCoversValidator;
13
14 private function requirePosix() {
15 if ( wfIsWindows() ) {
16 $this->markTestSkipped( 'This test requires a POSIX environment.' );
17 }
18 }
19
20 /**
21 * @dataProvider provideExecute
22 */
23 public function testExecute( $commandInput, $expectedExitCode, $expectedOutput ) {
24 $this->requirePosix();
25
26 $command = new Command();
27 $result = $command
28 ->params( $commandInput )
29 ->execute();
30
31 $this->assertSame( $expectedExitCode, $result->getExitCode() );
32 $this->assertSame( $expectedOutput, $result->getStdout() );
33 }
34
35 public function provideExecute() {
36 return [
37 'success status' => [ 'true', 0, '' ],
38 'failure status' => [ 'false', 1, '' ],
39 'output' => [ [ 'echo', '-n', 'x', '>', 'y' ], 0, 'x > y' ],
40 ];
41 }
42
43 public function testEnvironment() {
44 $this->requirePosix();
45
46 $command = new Command();
47 $result = $command
48 ->params( [ 'printenv', 'FOO' ] )
49 ->environment( [ 'FOO' => 'bar' ] )
50 ->execute();
51 $this->assertSame( "bar\n", $result->getStdout() );
52 }
53
54 public function testStdout() {
55 $this->requirePosix();
56
57 $command = new Command();
58
59 $result = $command
60 ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
61 ->execute();
62
63 $this->assertNotContains( 'ThisIsStderr', $result->getStdout() );
64 $this->assertEquals( "ThisIsStderr\n", $result->getStderr() );
65 }
66
67 public function testStdoutRedirection() {
68 $this->requirePosix();
69
70 $command = new Command();
71
72 $result = $command
73 ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
74 ->includeStderr( true )
75 ->execute();
76
77 $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
78 $this->assertNull( $result->getStderr() );
79 }
80
81 public function testOutput() {
82 global $IP;
83
84 $this->requirePosix();
85 chdir( $IP );
86
87 $command = new Command();
88 $result = $command
89 ->params( [ 'ls', 'index.php' ] )
90 ->execute();
91 $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
92 $this->assertSame( null, $result->getStderr() );
93
94 $command = new Command();
95 $result = $command
96 ->params( [ 'ls', 'index.php', 'no-such-file' ] )
97 ->includeStderr()
98 ->execute();
99 $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
100 $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStdout() );
101 $this->assertSame( null, $result->getStderr() );
102
103 $command = new Command();
104 $result = $command
105 ->params( [ 'ls', 'index.php', 'no-such-file' ] )
106 ->execute();
107 $this->assertRegExp( '/^index.php$/m', $result->getStdout() );
108 $this->assertRegExp( '/^.+no-such-file.*$/m', $result->getStderr() );
109 }
110
111 /**
112 * Test that null values are skipped by params() and unsafeParams()
113 */
114 public function testNullsAreSkipped() {
115 $command = TestingAccessWrapper::newFromObject( new Command );
116 $command->params( 'echo', 'a', null, 'b' );
117 $command->unsafeParams( 'c', null, 'd' );
118 $this->assertEquals( "'echo' 'a' 'b' c d", $command->command );
119 }
120
121 public function testT69870() {
122 if ( wfIsWindows() ) {
123 // T209159: Anonymous pipe under Windows does not support asynchronous read and write,
124 // and the default buffer is too small (~4K), it is easy to be blocked.
125 $this->markTestSkipped(
126 'T209159: Anonymous pipe under Windows cannot withstand such a large amount of data'
127 );
128 }
129
130 // Test several times because it involves a race condition that may randomly succeed or fail
131 for ( $i = 0; $i < 10; $i++ ) {
132 $command = new Command();
133 $output = $command->unsafeParams( 'printf "%-333333s" "*"' )
134 ->execute()
135 ->getStdout();
136 $this->assertEquals( 333333, strlen( $output ) );
137 }
138 }
139
140 public function testLogStderr() {
141 $this->requirePosix();
142
143 $logger = new TestLogger( true, function ( $message, $level, $context ) {
144 return $level === Psr\Log\LogLevel::ERROR ? '1' : null;
145 }, true );
146 $command = new Command();
147 $command->setLogger( $logger );
148 $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
149 $command->execute();
150 $this->assertEmpty( $logger->getBuffer() );
151
152 $command = new Command();
153 $command->setLogger( $logger );
154 $command->logStderr();
155 $command->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' );
156 $command->execute();
157 $this->assertSame( 1, count( $logger->getBuffer() ) );
158 $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 'ThisIsStderr' );
159 }
160
161 public function testInput() {
162 $this->requirePosix();
163
164 $command = new Command();
165 $command->params( 'cat' );
166 $command->input( 'abc' );
167 $result = $command->execute();
168 $this->assertSame( 'abc', $result->getStdout() );
169
170 // now try it with something that does not fit into a single block
171 $command = new Command();
172 $command->params( 'cat' );
173 $command->input( str_repeat( '!', 1000000 ) );
174 $result = $command->execute();
175 $this->assertSame( 1000000, strlen( $result->getStdout() ) );
176
177 // And try it with empty input
178 $command = new Command();
179 $command->params( 'cat' );
180 $command->input( '' );
181 $result = $command->execute();
182 $this->assertSame( '', $result->getStdout() );
183 }
184 }