3 use MediaWiki\Shell\Command
;
4 use MediaWiki\Shell\Shell
;
8 * @covers \MediaWiki\Shell\Command
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() );
27 * @dataProvider provideExecute
29 public function testExecute( $command, $args, $expectedExitCode, $expectedOutput ) {
30 $command = $this->getPhpCommand( $command );
35 $this->assertSame( $expectedExitCode, $result->getExitCode() );
36 $this->assertSame( $expectedOutput, $result->getStdout() );
39 public static function provideExecute() {
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' );
51 ->environment( [ 'FOO' => 'bar' ] )
53 $this->assertSame( "bar", $result->getStdout() );
56 public function testStdout() {
57 $command = $this->getPhpCommand( 'echo_args.php' );
60 ->unsafeParams( 'ThisIsStderr', '1>&2' )
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();
74 ->params( 'bash', '-c', 'echo ThisIsStderr 1>&2' )
75 ->includeStderr( true )
78 $this->assertEquals( "ThisIsStderr\n", $result->getStdout() );
79 $this->assertSame( '', $result->getStderr() );
82 public function testOutput() {
83 $command = $this->getPhpCommand(
87 $result = $command->execute();
88 $this->assertSame( 'correct stdout', $result->getStdout() );
89 $this->assertSame( '', $result->getStderr() );
91 $command = $this->getPhpCommand(
93 [ 'correct stdout ', 'correct stderr ' ]
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(
104 [ 'correct stdout', 'correct stderr' ]
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() );
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' );
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;
148 $command = $this->getPhpCommand( 'echo_args.php' );
149 $command->setLogger( $logger );
150 $command->unsafeParams( 'ThisIsStderr', '1>&2' );
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' );
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
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
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() );
221 . DIRECTORY_SEPARATOR
223 . DIRECTORY_SEPARATOR
226 $params = array_merge( $params, $args );
228 $command->params( $params );
229 $command->limits( [ 'memory' => 0 ] );