1<?php 2 3const WORKER_ARGV_VALUE = 'RUN_WORKER'; 4 5const WORKER_DEFAULT_NAME = 'server'; 6 7function phpt_notify($worker = WORKER_DEFAULT_NAME) 8{ 9 ServerClientTestCase::getInstance()->notify($worker); 10} 11 12function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null) 13{ 14 ServerClientTestCase::getInstance()->wait($worker, $timeout); 15} 16 17function phpt_has_sslv3() { 18 static $result = null; 19 if (!is_null($result)) { 20 return $result; 21 } 22 $server = @stream_socket_server('sslv3://127.0.0.1:10013'); 23 if ($result = !!$server) { 24 fclose($server); 25 } 26 return $result; 27} 28 29/** 30 * This is a singleton to let the wait/notify functions work 31 * I know it's horrible, but it's a means to an end 32 */ 33class ServerClientTestCase 34{ 35 private $isWorker = false; 36 37 private $workerHandle = []; 38 39 private $workerStdIn = []; 40 41 private $workerStdOut = []; 42 43 private static $instance; 44 45 public static function getInstance($isWorker = false) 46 { 47 if (!isset(self::$instance)) { 48 self::$instance = new self($isWorker); 49 } 50 51 return self::$instance; 52 } 53 54 public function __construct($isWorker = false) 55 { 56 if (!isset(self::$instance)) { 57 self::$instance = $this; 58 } 59 60 $this->isWorker = $isWorker; 61 } 62 63 private function spawnWorkerProcess($worker, $code) 64 { 65 if (defined("PHP_WINDOWS_VERSION_MAJOR")) { 66 $ini = php_ini_loaded_file(); 67 $cmd = sprintf( 68 '%s %s "%s" %s', 69 PHP_BINARY, $ini ? "-n -c $ini" : "", 70 __FILE__, 71 WORKER_ARGV_VALUE 72 ); 73 } else { 74 $cmd = sprintf( 75 '%s "%s" %s %s', 76 PHP_BINARY, 77 __FILE__, 78 WORKER_ARGV_VALUE, 79 $worker 80 ); 81 } 82 $this->workerHandle[$worker] = proc_open( 83 $cmd, 84 [['pipe', 'r'], ['pipe', 'w'], STDERR], 85 $pipes 86 ); 87 $this->workerStdIn[$worker] = $pipes[0]; 88 $this->workerStdOut[$worker] = $pipes[1]; 89 90 fwrite($this->workerStdIn[$worker], $code . "\n---\n"); 91 } 92 93 private function cleanupWorkerProcess($worker) 94 { 95 fclose($this->workerStdIn[$worker]); 96 fclose($this->workerStdOut[$worker]); 97 proc_close($this->workerHandle[$worker]); 98 } 99 100 private function stripPhpTagsFromCode($code) 101 { 102 return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code); 103 } 104 105 public function runWorker() 106 { 107 $code = ''; 108 109 while (1) { 110 $line = fgets(STDIN); 111 112 if (trim($line) === "---") { 113 break; 114 } 115 116 $code .= $line; 117 } 118 119 eval($code); 120 } 121 122 public function run($masterCode, $workerCode) 123 { 124 if (!is_array($workerCode)) { 125 $workerCode = [WORKER_DEFAULT_NAME => $workerCode]; 126 } 127 foreach ($workerCode as $worker => $code) { 128 $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); 129 } 130 eval($this->stripPhpTagsFromCode($masterCode)); 131 foreach ($workerCode as $worker => $code) { 132 $this->cleanupWorkerProcess($worker); 133 } 134 } 135 136 public function wait($worker, $timeout = null) 137 { 138 $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker]; 139 if ($timeout === null) { 140 fgets($handle); 141 return true; 142 } 143 144 stream_set_blocking($handle, false); 145 $read = [$handle]; 146 $result = stream_select($read, $write, $except, $timeout); 147 if (!$result) { 148 return false; 149 } 150 151 fgets($handle); 152 stream_set_blocking($handle, true); 153 return true; 154 } 155 156 public function notify($worker) 157 { 158 fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); 159 } 160} 161 162if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) { 163 ServerClientTestCase::getInstance(true)->runWorker(); 164} 165