Localisation updates from https://translatewiki.net.
[mediawiki.git] / tests / phpunit / includes / shell / CommandTest.php
blobfeeb985c74688baca8b53552b0709ae644f39cd6
1 <?php
3 use MediaWiki\Shell\Command;
4 use MediaWiki\Shell\Shell;
5 use Shellbox\Shellbox;
7 /**
8 * @covers \MediaWiki\Shell\Command
9 * @group Shell
11 class CommandTest extends PHPUnit\Framework\TestCase {
13 use MediaWikiCoversValidator;
14 use MediaWikiTestCaseTrait;
16 private function requirePosix() {
17 if ( wfIsWindows() ) {
18 $this->markTestSkipped( 'This test requires a POSIX environment.' );
22 private function createCommand() {
23 return new Command( Shellbox::createUnboxedExecutor() );
26 /**
27 * @dataProvider provideExecute
29 public function testExecute( $command, $args, $expectedExitCode, $expectedOutput ) {
30 $command = $this->getPhpCommand( $command );
31 $result = $command
32 ->params( $args )
33 ->execute();
35 $this->assertSame( $expectedExitCode, $result->getExitCode() );
36 $this->assertSame( $expectedOutput, $result->getStdout() );
39 public static function provideExecute() {
40 return [
41 'success status' => [ 'success_status.php', [], 0, '' ],
42 'failure status' => [ 'failure_status.php', [], 1, '' ],
43 'output' => [ 'echo_args.php', [ 'x', '>', 'y' ], 0, 'x > y' ],
47 public function testEnvironment() {
48 $command = $this->getPhpCommand( 'echo_env.php' );
49 $result = $command
50 ->params( [ 'FOO' ] )
51 ->environment( [ 'FOO' => 'bar' ] )
52 ->execute();
53 $this->assertSame( "bar", $result->getStdout() );
56 public function testStdout() {
57 $command = $this->getPhpCommand( 'echo_args.php' );
59 $result = $command
60 ->unsafeParams( 'ThisIsStderr', '1>&2' )
61 ->execute();
63 $this->assertStringNotContainsString( 'ThisIsStderr', $result->getStdout() );
64 $this->assertEquals( "ThisIsStderr", $result->getStderr() );
67 public function testStdoutRedirection() {
68 // The double redirection doesn't work on Windows
69 $this->requirePosix();
71 $command = $this->createCommand();
73 $result = $command
74 ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
75 ->includeStderr( true )
76 ->execute();
78 $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
79 $this->assertSame( '', $result->getStderr() );
82 public function testOutput() {
83 $command = $this->getPhpCommand(
84 'stdout_stderr.php',
85 [ 'correct stdout' ]
87 $result = $command->execute();
88 $this->assertSame( 'correct stdout', $result->getStdout() );
89 $this->assertSame( '', $result->getStderr() );
91 $command = $this->getPhpCommand(
92 'stdout_stderr.php',
93 [ 'correct stdout ', 'correct stderr ' ]
95 $result = $command
96 ->includeStderr()
97 ->execute();
98 $this->assertMatchesRegularExpression( '/correct stdout/m', $result->getStdout() );
99 $this->assertMatchesRegularExpression( '/correct stderr/m', $result->getStdout() );
100 $this->assertSame( '', $result->getStderr() );
102 $command = $this->getPhpCommand(
103 'stdout_stderr.php',
104 [ 'correct stdout', 'correct stderr' ]
106 $result = $command
107 ->execute();
108 $this->assertSame( 'correct stdout', $result->getStdout() );
109 $this->assertSame( 'correct stderr', $result->getStderr() );
113 * Test that null values are skipped by params() and unsafeParams()
115 public function testNullsAreSkipped() {
116 $command = $this->createCommand();
117 $command->params( 'echo', 'a', null, 'b' );
118 $command->unsafeParams( 'c', null, 'd' );
120 if ( wfIsWindows() ) {
121 $this->assertEquals( '"echo" "a" "b" c d', $command->getCommandString() );
122 } else {
123 $this->assertEquals( "'echo' 'a' 'b' c d", $command->getCommandString() );
127 public function testT69870() {
128 // Testing for Bug T69870
129 // wfShellExec() cuts off stdout at multiples of 8192 bytes.
131 // hangs on Windows, see Bug T199989, non-blocking pipes
132 $this->requirePosix();
134 // Test several times because it involves a race condition that may randomly succeed or fail
135 for ( $i = 0; $i < 10; $i++ ) {
136 $command = $this->getPhpCommand( 'echo_333333_stars.php' );
137 $output = $command
138 ->execute()
139 ->getStdout();
140 $this->assertEquals( 333333, strlen( $output ) );
144 public function testLogStderr() {
145 $logger = new TestLogger( true, static function ( $message, $level, $context ) {
146 return $level === Psr\Log\LogLevel::ERROR ? '1' : null;
147 }, true );
148 $command = $this->getPhpCommand( 'echo_args.php' );
149 $command->setLogger( $logger );
150 $command->unsafeParams( 'ThisIsStderr', '1>&2' );
151 $command->execute();
152 $this->assertSame( [], $logger->getBuffer() );
154 $command = $this->getPhpCommand( 'echo_args.php' );
155 $command->setLogger( $logger );
156 $command->logStderr();
157 $command->unsafeParams( 'ThisIsStderr', '1>&2' );
158 $command->execute();
159 $this->assertCount( 1, $logger->getBuffer() );
160 $this->assertSame( 'ThisIsStderr', trim( $logger->getBuffer()[0][2]['error'] ) );
163 public function testInput() {
164 // hangs on Windows, see Bug T199989, non-blocking pipes
165 $this->requirePosix();
167 $command = $this->getPhpCommand( 'echo_stdin.php' );
168 $command->input( 'abc' );
169 $result = $command->execute();
170 $this->assertSame( 'abc', $result->getStdout() );
172 // now try it with something that does not fit into a single block
173 $command = $this->getPhpCommand( 'echo_stdin.php' );
174 $command->input( str_repeat( '!', 1000000 ) );
175 $result = $command->execute();
176 $this->assertSame( 1000000, strlen( $result->getStdout() ) );
178 // And try it with empty input
179 $command = $this->getPhpCommand( 'echo_stdin.php' );
180 $command->input( '' );
181 $result = $command->execute();
182 $this->assertSame( '', $result->getStdout() );
186 * Ensure that it's possible to disable the default shell restrictions
187 * @see T257278
189 public function testDisablingRestrictions() {
190 $command = $this->createCommand();
191 // As CommandFactory does for the firejail case:
192 $command->restrict( Shell::RESTRICT_DEFAULT );
193 // Disable restrictions
194 $command->restrict( Shell::RESTRICT_NONE );
195 $this->assertFalse( $command->getPrivateUserNamespace() );
196 $this->assertFalse( $command->getFirejailDefaultSeccomp() );
197 $this->assertFalse( $command->getNoNewPrivs() );
198 $this->assertFalse( $command->getPrivateDev() );
199 $this->assertFalse( $command->getDisableNetwork() );
200 $this->assertSame( [], $command->getDisabledSyscalls() );
201 $this->assertTrue( $command->getDisableSandbox() );
205 * Creates a command that will execute one of the PHP test scripts by its
206 * file name, using the current PHP_BIN binary.
208 * NOTE: the PHP test scripts are located in the sub directory
209 * "bin".
211 * @param string $fileName a file name in the "bin" sub-directory
212 * @param array $args an array of arguments to pass to the PHP script
214 * @return Command a command instance pointing to the right script
216 private function getPhpCommand( $fileName, array $args = [] ) {
217 $command = new Command( Shellbox::createUnboxedExecutor() );
218 $params = [
219 PHP_BINARY,
220 __DIR__
221 . DIRECTORY_SEPARATOR
222 . 'bin'
223 . DIRECTORY_SEPARATOR
224 . $fileName
226 $params = array_merge( $params, $args );
228 $command->params( $params );
229 $command->limits( [ 'memory' => 0 ] );
230 return $command;