MaxSem has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/399768 )

Change subject: Allow programmatic input in Command
......................................................................


Allow programmatic input in Command

Bug: T182463
Change-Id: Ib68180c7af12558686f4864c24fd85f01201d6fb
---
M includes/shell/Command.php
M includes/shell/Shell.php
M tests/phpunit/includes/shell/CommandTest.php
3 files changed, 78 insertions(+), 28 deletions(-)

Approvals:
  MaxSem: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/includes/shell/Command.php b/includes/shell/Command.php
index f887ada..4f65e4d 100644
--- a/includes/shell/Command.php
+++ b/includes/shell/Command.php
@@ -56,6 +56,9 @@
        /** @var string */
        private $method;
 
+       /** @var string|null */
+       private $inputString;
+
        /** @var bool */
        private $doIncludeStderr = false;
 
@@ -185,6 +188,18 @@
         */
        public function profileMethod( $method ) {
                $this->method = $method;
+
+               return $this;
+       }
+
+       /**
+        * Sends the provided input to the command.
+        * When set to null (default), the command will use the standard input.
+        * @param string|null $inputString
+        * @return $this
+        */
+       public function input( $inputString ) {
+               $this->inputString = is_null( $inputString ) ? null : 
(string)$inputString;
 
                return $this;
        }
@@ -350,7 +365,7 @@
                }
 
                $desc = [
-                       0 => [ 'file', 'php://stdin', 'r' ],
+                       0 => $this->inputString === null ? [ 'file', 
'php://stdin', 'r' ] : [ 'pipe', 'r' ],
                        1 => [ 'pipe', 'w' ],
                        2 => [ 'pipe', 'w' ],
                ];
@@ -364,8 +379,13 @@
                        $this->logger->error( "proc_open() failed: {command}", 
[ 'command' => $cmd ] );
                        throw new ProcOpenError();
                }
-               $outBuffer = $logBuffer = '';
-               $errBuffer = null;
+
+               $buffers = [
+                       0 => $this->inputString, // input
+                       1 => '', // stdout
+                       2 => null, // stderr
+                       3 => '', // log
+               ];
                $emptyArray = [];
                $status = false;
                $logMsg = false;
@@ -399,8 +419,6 @@
                                }
                        }
 
-                       $readyPipes = $pipes;
-
                        // clear get_last_error without actually raising an 
error
                        // from 
http://php.net/manual/en/function.error-get-last.php#113518
                        // TODO replace with clear_last_error when requirements 
are bumped to PHP7
@@ -410,8 +428,17 @@
                        @trigger_error( '' );
                        restore_error_handler();
 
+                       $readPipes = wfArrayFilterByKey( $pipes, function ( $fd 
) use ( $desc ) {
+                               return $desc[$fd][0] === 'pipe' && 
$desc[$fd][1] === 'r';
+                       } );
+                       $writePipes = wfArrayFilterByKey( $pipes, function ( 
$fd ) use ( $desc ) {
+                               return $desc[$fd][0] === 'pipe' && 
$desc[$fd][1] === 'w';
+                       } );
+                       // stream_select parameter names are from the POV of us 
being able to do the operation;
+                       // proc_open desriptor types are from the POV of the 
process doing it.
+                       // So $writePipes is passed as the $read parameter and 
$readPipes as $write.
                        // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
-                       $numReadyPipes = @stream_select( $readyPipes, 
$emptyArray, $emptyArray, $timeout );
+                       $numReadyPipes = @stream_select( $writePipes, 
$readPipes, $emptyArray, $timeout );
                        if ( $numReadyPipes === false ) {
                                $error = error_get_last();
                                if ( strncmp( $error['message'], $eintrMessage, 
strlen( $eintrMessage ) ) == 0 ) {
@@ -422,31 +449,36 @@
                                        break;
                                }
                        }
-                       foreach ( $readyPipes as $fd => $pipe ) {
-                               $block = fread( $pipe, 65536 );
-                               if ( $block === '' ) {
+                       foreach ( $writePipes + $readPipes as $fd => $pipe ) {
+                               // True if a pipe is unblocked for us to write 
into, false if for reading from
+                               $isWrite = array_key_exists( $fd, $readPipes );
+
+                               if ( $isWrite ) {
+                                       $res = fwrite( $pipe, $buffers[$fd], 
65536 );
+                               } else {
+                                       $res = fread( $pipe, 65536 );
+                               }
+
+                               if ( $res === false ) {
+                                       $logMsg = 'Error ' . ( $isWrite ? 
'writing to' : 'reading from' ) . ' pipe';
+                                       break 2;
+                               }
+
+                               if ( $res === '' || $res === 0 ) {
                                        // End of file
                                        fclose( $pipes[$fd] );
                                        unset( $pipes[$fd] );
                                        if ( !$pipes ) {
                                                break 2;
                                        }
-                               } elseif ( $block === false ) {
-                                       // Read error
-                                       $logMsg = "Error reading from pipe";
-                                       break 2;
-                               } elseif ( $fd == 1 ) {
-                                       // From stdout
-                                       $outBuffer .= $block;
-                               } elseif ( $fd == 2 ) {
-                                       // From stderr
-                                       $errBuffer .= $block;
-                               } elseif ( $fd == 3 ) {
-                                       // From log FD
-                                       $logBuffer .= $block;
-                                       if ( strpos( $block, "\n" ) !== false ) 
{
-                                               $lines = explode( "\n", 
$logBuffer );
-                                               $logBuffer = array_pop( $lines 
);
+                               } elseif ( $isWrite ) {
+                                       $buffers[$fd] = substr( $buffers[$fd], 
$res );
+                               } else {
+                                       $buffers[$fd] .= $res;
+                                       if ( $fd === 3 && strpos( $res, "\n" ) 
!== false ) {
+                                               // For the log FD, every line 
is a separate log entry.
+                                               $lines = explode( "\n", 
$buffers[3] );
+                                               $buffers[3] = array_pop( $lines 
);
                                                foreach ( $lines as $line ) {
                                                        $this->logger->info( 
$line );
                                                }
@@ -491,15 +523,15 @@
                        $this->logger->warning( "$logMsg: {command}", [ 
'command' => $cmd ] );
                }
 
-               if ( $errBuffer && $this->doLogStderr ) {
+               if ( $buffers[2] && $this->doLogStderr ) {
                        $this->logger->error( "Error running {command}: 
{error}", [
                                'command' => $cmd,
-                               'error' => $errBuffer,
+                               'error' => $buffers[2],
                                'exitcode' => $retval,
                                'exception' => new Exception( 'Shell error' ),
                        ] );
                }
 
-               return new Result( $retval, $outBuffer, $errBuffer );
+               return new Result( $retval, $buffers[1], $buffers[2] );
        }
 }
diff --git a/includes/shell/Shell.php b/includes/shell/Shell.php
index 05463db..d57bf4f 100644
--- a/includes/shell/Shell.php
+++ b/includes/shell/Shell.php
@@ -31,6 +31,7 @@
  *
  * Use call chaining with this class for expressiveness:
  *  $result = Shell::command( 'some command' )
+ *       ->input( 'foo' )
  *       ->environment( [ 'ENVIRONMENT_VARIABLE' => 'VALUE' ] )
  *       ->limits( [ 'time' => 300 ] )
  *       ->execute();
diff --git a/tests/phpunit/includes/shell/CommandTest.php 
b/tests/phpunit/includes/shell/CommandTest.php
index 2bafa03..2ba7bdc 100644
--- a/tests/phpunit/includes/shell/CommandTest.php
+++ b/tests/phpunit/includes/shell/CommandTest.php
@@ -154,4 +154,21 @@
                $this->assertSame( 1, count( $logger->getBuffer() ) );
                $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 
'ThisIsStderr' );
        }
+
+       public function testInput() {
+               $this->requirePosix();
+
+               $command = new Command();
+               $command->params( 'cat' );
+               $command->input( 'abc' );
+               $result = $command->execute();
+               $this->assertSame( 'abc', $result->getStdout() );
+
+               // now try it with something that does not fit into a single 
block
+               $command = new Command();
+               $command->params( 'cat' );
+               $command->input( str_repeat( '!', 1000000 ) );
+               $result = $command->execute();
+               $this->assertSame( 1000000, strlen( $result->getStdout() ) );
+       }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/399768
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib68180c7af12558686f4864c24fd85f01201d6fb
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: GergÅ‘ Tisza <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: GergÅ‘ Tisza <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: MaxSem <[email protected]>
Gerrit-Reviewer: Reedy <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to