1<?php 2/* 3 * This file is part of PHPUnit. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * Default utility for PHP sub-processes. 13 */ 14class PHPUnit_Util_PHP_Default extends PHPUnit_Util_PHP 15{ 16 /** 17 * @var string 18 */ 19 protected $tempFile; 20 21 /** 22 * @var bool 23 */ 24 protected $useTempFile = false; 25 26 /** 27 * Runs a single job (PHP code) using a separate PHP process. 28 * 29 * @param string $job 30 * @param array $settings 31 * 32 * @return array 33 * 34 * @throws PHPUnit_Framework_Exception 35 */ 36 public function runJob($job, array $settings = []) 37 { 38 if ($this->useTempFile || $this->stdin) { 39 if (!($this->tempFile = tempnam(sys_get_temp_dir(), 'PHPUnit')) || 40 file_put_contents($this->tempFile, $job) === false) { 41 throw new PHPUnit_Framework_Exception( 42 'Unable to write temporary file' 43 ); 44 } 45 46 $job = $this->stdin; 47 } 48 49 return $this->runProcess($job, $settings); 50 } 51 52 /** 53 * Returns an array of file handles to be used in place of pipes 54 * 55 * @return array 56 */ 57 protected function getHandles() 58 { 59 return []; 60 } 61 62 /** 63 * Handles creating the child process and returning the STDOUT and STDERR 64 * 65 * @param string $job 66 * @param array $settings 67 * 68 * @return array 69 * 70 * @throws PHPUnit_Framework_Exception 71 */ 72 protected function runProcess($job, $settings) 73 { 74 $handles = $this->getHandles(); 75 76 $env = null; 77 if ($this->env) { 78 $env = isset($_SERVER) ? $_SERVER : []; 79 unset($env['argv'], $env['argc']); 80 $env = array_merge($env, $this->env); 81 82 foreach ($env as $envKey => $envVar) { 83 if (is_array($envVar)) { 84 unset($env[$envKey]); 85 } 86 } 87 } 88 89 $pipeSpec = [ 90 0 => isset($handles[0]) ? $handles[0] : ['pipe', 'r'], 91 1 => isset($handles[1]) ? $handles[1] : ['pipe', 'w'], 92 2 => isset($handles[2]) ? $handles[2] : ['pipe', 'w'], 93 ]; 94 $process = proc_open( 95 $this->getCommand($settings, $this->tempFile), 96 $pipeSpec, 97 $pipes, 98 null, 99 $env 100 ); 101 102 if (!is_resource($process)) { 103 throw new PHPUnit_Framework_Exception( 104 'Unable to spawn worker process' 105 ); 106 } 107 108 if ($job) { 109 $this->process($pipes[0], $job); 110 } 111 fclose($pipes[0]); 112 113 if ($this->timeout) { 114 $stderr = $stdout = ''; 115 unset($pipes[0]); 116 117 while (true) { 118 $r = $pipes; 119 $w = null; 120 $e = null; 121 122 $n = @stream_select($r, $w, $e, $this->timeout); 123 124 if ($n === false) { 125 break; 126 } elseif ($n === 0) { 127 proc_terminate($process, 9); 128 throw new PHPUnit_Framework_Exception(sprintf('Job execution aborted after %d seconds', $this->timeout)); 129 } elseif ($n > 0) { 130 foreach ($r as $pipe) { 131 $pipeOffset = 0; 132 foreach ($pipes as $i => $origPipe) { 133 if ($pipe == $origPipe) { 134 $pipeOffset = $i; 135 break; 136 } 137 } 138 139 if (!$pipeOffset) { 140 break; 141 } 142 143 $line = fread($pipe, 8192); 144 if (strlen($line) == 0) { 145 fclose($pipes[$pipeOffset]); 146 unset($pipes[$pipeOffset]); 147 } else { 148 if ($pipeOffset == 1) { 149 $stdout .= $line; 150 } else { 151 $stderr .= $line; 152 } 153 } 154 } 155 156 if (empty($pipes)) { 157 break; 158 } 159 } 160 } 161 } else { 162 if (isset($pipes[1])) { 163 $stdout = stream_get_contents($pipes[1]); 164 fclose($pipes[1]); 165 } 166 167 if (isset($pipes[2])) { 168 $stderr = stream_get_contents($pipes[2]); 169 fclose($pipes[2]); 170 } 171 } 172 173 if (isset($handles[1])) { 174 rewind($handles[1]); 175 $stdout = stream_get_contents($handles[1]); 176 fclose($handles[1]); 177 } 178 179 if (isset($handles[2])) { 180 rewind($handles[2]); 181 $stderr = stream_get_contents($handles[2]); 182 fclose($handles[2]); 183 } 184 185 proc_close($process); 186 $this->cleanup(); 187 188 return ['stdout' => $stdout, 'stderr' => $stderr]; 189 } 190 191 /** 192 * @param resource $pipe 193 * @param string $job 194 * 195 * @throws PHPUnit_Framework_Exception 196 */ 197 protected function process($pipe, $job) 198 { 199 fwrite($pipe, $job); 200 } 201 202 protected function cleanup() 203 { 204 if ($this->tempFile) { 205 unlink($this->tempFile); 206 } 207 } 208} 209