Эх сурвалжийг харах

Merge branch 'master' into status_files

Anton 2 жил өмнө
parent
commit
e1fec7bb85

+ 7 - 15
src/Events/Ev.php

@@ -18,47 +18,47 @@ namespace Workerman\Events;
 /**
  * Ev eventloop
  */
-class Ev implements EventInterface
+final class Ev implements EventInterface
 {
     /**
      * All listeners for read event.
      *
      * @var array<int, \EvIo>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for write event.
      *
      * @var array<int, \EvIo>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * Event listeners of signal.
      *
      * @var array<int, \EvSignal>
      */
-    protected array $eventSignal = [];
+    private array $eventSignal = [];
 
     /**
      * All timer event listeners.
      *
      * @var array<int, \EvTimer>
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * @var ?callable
      */
-    protected $errorHandler = null;
+    private $errorHandler = null;
 
     /**
      * Timer id.
      *
      * @var int
      */
-    protected static int $timerId = 1;
+    private static int $timerId = 1;
 
     /**
      * {@inheritdoc}
@@ -219,14 +219,6 @@ class Ev implements EventInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->errorHandler;
-    }
-
-    /**
      * @param callable $func
      * @param array $args
      * @return void

+ 9 - 17
src/Events/Event.php

@@ -19,61 +19,61 @@ namespace Workerman\Events;
 /**
  * libevent eventloop
  */
-class Event implements EventInterface
+final class Event implements EventInterface
 {
     /**
      * Event base.
      *
      * @var \EventBase
      */
-    protected \EventBase $eventBase;
+    private \EventBase $eventBase;
 
     /**
      * All listeners for read event.
      *
      * @var array<int, \Event>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for write event.
      *
      * @var array<int, \Event>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * Event listeners of signal.
      *
      * @var array<int, \Event>
      */
-    protected array $eventSignal = [];
+    private array $eventSignal = [];
 
     /**
      * All timer event listeners.
      *
      * @var array<int, \Event>
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * Timer id.
      *
      * @var int
      */
-    protected int $timerId = 0;
+    private int $timerId = 0;
 
     /**
      * Event class name.
      *
      * @var string
      */
-    protected string $eventClassName = '';
+    private string $eventClassName = '';
 
     /**
      * @var ?callable
      */
-    protected $errorHandler = null;
+    private $errorHandler = null;
 
     /**
      * Construct.
@@ -273,14 +273,6 @@ class Event implements EventInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->errorHandler;
-    }
-
-    /**
      * @param callable $func
      * @param array $args
      * @return void

+ 0 - 7
src/Events/EventInterface.php

@@ -140,11 +140,4 @@ interface EventInterface
      * @return void
      */
     public function setErrorHandler(callable $errorHandler): void;
-
-    /**
-     * Get error handler
-     *
-     * @return null|callable (\Throwable): void
-     */
-    public function getErrorHandler(): ?callable;
 }

+ 7 - 15
src/Events/Revolt.php

@@ -25,47 +25,47 @@ use function pcntl_signal;
 /**
  * Revolt eventloop
  */
-class Revolt implements EventInterface
+final class Revolt implements EventInterface
 {
     /**
      * @var Driver
      */
-    protected Driver $driver;
+    private Driver $driver;
 
     /**
      * All listeners for read event.
      *
      * @var array<int, string>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for write event.
      *
      * @var array<int, string>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * Event listeners of signal.
      *
      * @var array<int, string>
      */
-    protected array $eventSignal = [];
+    private array $eventSignal = [];
 
     /**
      * Event listeners of timer.
      *
      * @var array<int, string>
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * Timer id.
      *
      * @var int
      */
-    protected int $timerId = 1;
+    private int $timerId = 1;
 
     /**
      * Construct.
@@ -262,12 +262,4 @@ class Revolt implements EventInterface
     {
         $this->driver->setErrorHandler($errorHandler);
     }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->driver->getErrorHandler();
-    }
 }

+ 14 - 22
src/Events/Select.php

@@ -26,61 +26,61 @@ use const DIRECTORY_SEPARATOR;
 /**
  * select eventloop
  */
-class Select implements EventInterface
+final class Select implements EventInterface
 {
     /**
      * Running.
      *
      * @var bool
      */
-    protected bool $running = true;
+    private bool $running = true;
 
     /**
      * All listeners for read/write event.
      *
      * @var array<int, callable>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for read/write event.
      *
      * @var array<int, callable>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * @var array<int, callable>
      */
-    protected array $exceptEvents = [];
+    private array $exceptEvents = [];
 
     /**
      * Event listeners of signal.
      *
      * @var array<int, callable>
      */
-    protected array $signalEvents = [];
+    private array $signalEvents = [];
 
     /**
      * Fds waiting for read event.
      *
      * @var array<int, resource>
      */
-    protected array $readFds = [];
+    private array $readFds = [];
 
     /**
      * Fds waiting for write event.
      *
      * @var array<int, resource>
      */
-    protected array $writeFds = [];
+    private array $writeFds = [];
 
     /**
      * Fds waiting for except event.
      *
      * @var array<int, resource>
      */
-    protected array $exceptFds = [];
+    private array $exceptFds = [];
 
     /**
      * Timer scheduler.
@@ -88,7 +88,7 @@ class Select implements EventInterface
      *
      * @var \SplPriorityQueue
      */
-    protected \SplPriorityQueue $scheduler;
+    private \SplPriorityQueue $scheduler;
 
     /**
      * All timer event listeners.
@@ -96,26 +96,26 @@ class Select implements EventInterface
      *
      * @var array
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * Timer id.
      *
      * @var int
      */
-    protected int $timerId = 1;
+    private int $timerId = 1;
 
     /**
      * Select timeout.
      *
      * @var int
      */
-    protected int $selectTimeout = 100000000;
+    private int $selectTimeout = 100000000;
 
     /**
      * @var ?callable
      */
-    protected $errorHandler = null;
+    private $errorHandler = null;
 
     /**
      * Construct.
@@ -442,14 +442,6 @@ class Select implements EventInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->errorHandler;
-    }
-
-    /**
      * @param callable $func
      * @param array $args
      * @return void

+ 5 - 13
src/Events/Swoole.php

@@ -19,33 +19,33 @@ use Swoole\Event;
 use Swoole\Process;
 use Swoole\Timer;
 
-class Swoole implements EventInterface
+final class Swoole implements EventInterface
 {
     /**
      * All listeners for read timer
      *
      * @var array<int, int>
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * All listeners for read event.
      *
      * @var array<int, resource>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for write event.
      *
      * @var array<int, resource>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * @var ?callable
      */
-    protected $errorHandler = null;
+    private $errorHandler = null;
 
     /**
      * {@inheritdoc}
@@ -234,14 +234,6 @@ class Swoole implements EventInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->errorHandler;
-    }
-
-    /**
      * @param callable $func
      * @param array $args
      * @return void

+ 6 - 14
src/Events/Swow.php

@@ -9,40 +9,40 @@ use Swow\Signal;
 use Swow\SignalException;
 use function Swow\Sync\waitAll;
 
-class Swow implements EventInterface
+final class Swow implements EventInterface
 {
     /**
      * All listeners for read timer.
      *
      * @var array<int, int>
      */
-    protected array $eventTimer = [];
+    private array $eventTimer = [];
 
     /**
      * All listeners for read event.
      *
      * @var array<int, Coroutine>
      */
-    protected array $readEvents = [];
+    private array $readEvents = [];
 
     /**
      * All listeners for write event.
      *
      * @var array<int, Coroutine>
      */
-    protected array $writeEvents = [];
+    private array $writeEvents = [];
 
     /**
      * All listeners for signal.
      *
      * @var array<int, Coroutine>
      */
-    protected array $signalListener = [];
+    private array $signalListener = [];
 
     /**
      * @var ?callable
      */
-    protected $errorHandler = null;
+    private $errorHandler = null;
 
     /**
      * Get timer count.
@@ -278,14 +278,6 @@ class Swow implements EventInterface
     }
 
     /**
-     * {@inheritdoc}
-     */
-    public function getErrorHandler(): ?callable
-    {
-        return $this->errorHandler;
-    }
-
-    /**
      * @param callable $func
      * @param array $args
      * @return void

+ 86 - 95
src/Worker.php

@@ -256,6 +256,13 @@ class Worker
     public static bool $daemonize = false;
 
     /**
+     * Standard output stream
+     *
+     * @var resource
+     */
+    public static $outputStream;
+
+    /**
      * Stdout file.
      *
      * @var string
@@ -528,16 +535,11 @@ class Worker
     protected static bool $gracefulStop = false;
 
     /**
-     * Standard output stream
-     * @var ?resource
-     */
-    protected static $outputStream = null;
-
-    /**
      * If $outputStream support decorated
+     *
      * @var bool
      */
-    protected static bool $outputDecorated = false;
+    protected static bool $outputDecorated;
 
     /**
      * Worker object's hash id(unique identifier).
@@ -556,6 +558,7 @@ class Worker
     {
         try {
             static::checkSapiEnv();
+            self::initStdOut();
             static::init();
             static::parseCommand();
             static::lock();
@@ -582,8 +585,46 @@ class Worker
     {
         // Only for cli and micro.
         if (!in_array(\PHP_SAPI, ['cli', 'micro'])) {
-            throw new \RuntimeException("Only run in command line mode");
+            exit("Only run in command line mode\n");
+        }
+    }
+
+    private static function initStdOut(): void
+    {
+        $defaultStream = fn () => \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w'));
+        static::$outputStream ??= $defaultStream(); //@phpstan-ignore-line
+        if (!\is_resource(self::$outputStream) || get_resource_type(self::$outputStream) !== 'stream') {
+            $type = get_debug_type(self::$outputStream);
+            static::$outputStream = $defaultStream();
+            throw new \RuntimeException(sprintf('The $outputStream must to be a stream, %s given', $type));
         }
+
+        static::$outputDecorated ??= self::hasColorSupport();
+    }
+
+    /**
+     * Borrowed from the symfony console
+     * @link https://github.com/symfony/console/blob/0d14a9f6d04d4ac38a8cea1171f4554e325dae92/Output/StreamOutput.php#L92
+     */
+    private static function hasColorSupport(): bool
+    {
+        // Follow https://no-color.org/
+        if (getenv('NO_COLOR') !== false) {
+            return false;
+        }
+
+        if (getenv('TERM_PROGRAM') === 'Hyper') {
+            return true;
+        }
+
+        if (\DIRECTORY_SEPARATOR === '\\') {
+            return (\function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(self::$outputStream))
+                || getenv('ANSICON') !== false
+                || getenv('ConEmuANSI') === 'ON'
+                || getenv('TERM') === 'xterm';
+        }
+
+        return stream_isatty(self::$outputStream);
     }
 
     /**
@@ -1273,50 +1314,32 @@ class Worker
     }
 
     /**
-     * Redirect standard input and output.
+     * Redirect standard output to stdoutFile.
      *
-     * @param bool $throwException
      * @return void
-     * @throws Exception
      */
-    public static function resetStd(bool $throwException = true): void
+    public static function resetStd(): void
     {
         if (!static::$daemonize || DIRECTORY_SEPARATOR !== '/') {
             return;
         }
-        global $STDOUT, $STDERR;
-        $handle = fopen(static::$stdoutFile, "a");
-        if ($handle) {
-            unset($handle);
-            set_error_handler(static fn (): bool => true);
-            if ($STDOUT) {
-                fclose($STDOUT);
-            }
-            if ($STDERR) {
-                fclose($STDERR);
-            }
-            if (is_resource(STDOUT)) {
-                fclose(STDOUT);
-            }
-            if (is_resource(STDERR)) {
-                fclose(STDERR);
-            }
-            $STDOUT = fopen(static::$stdoutFile, "a");
-            $STDERR = fopen(static::$stdoutFile, "a");
-            // Fix standard output cannot redirect of PHP 8.1.8's bug
-            if (function_exists('posix_isatty') && posix_isatty(2)) {
-                ob_start(function ($string) {
-                    file_put_contents(static::$stdoutFile, $string, FILE_APPEND);
-                }, 1);
-            }
-            // change output stream
-            static::$outputStream = null;
-            self::outputStream($STDOUT);
-            restore_error_handler();
+
+        set_error_handler(static fn (): bool => true);
+        $stdOutStream = fopen(static::$stdoutFile, 'a');
+        restore_error_handler();
+
+        if ($stdOutStream === false) {
             return;
         }
-        if ($throwException) {
-            throw new RuntimeException('Can not open stdoutFile ' . static::$stdoutFile);
+
+        fclose(static::$outputStream);
+        static::$outputStream = $stdOutStream;
+
+        // Fix standard output cannot redirect of PHP 8.1.8's bug
+        if (function_exists('posix_isatty') && posix_isatty(2)) {
+            ob_start(function (string $string) {
+                file_put_contents(static::$stdoutFile, $string, FILE_APPEND);
+            }, 1);
         }
     }
 
@@ -1805,7 +1828,7 @@ class Worker
                 static::log("Workerman[" . basename(static::$startFile) . "] reloading");
                 static::$status = static::STATUS_RELOADING;
 
-                static::resetStd(false);
+                static::resetStd();
                 // Try to emit onMasterReload callback.
                 if (static::$onMasterReload) {
                     try {
@@ -1862,7 +1885,7 @@ class Worker
             if ($worker->reloadable) {
                 static::stopAll();
             } else {
-                static::resetStd(false);
+                static::resetStd();
             }
         }
     }
@@ -2169,62 +2192,30 @@ class Worker
      * Safe Echo.
      *
      * @param string $msg
-     * @param bool $decorated
-     * @return bool
-     */
-    public static function safeEcho(string $msg, bool $decorated = false): bool
-    {
-        $stream = self::outputStream();
-        if (!$stream) {
-            return false;
-        }
-        if (!$decorated) {
-            $line = $white = $green = $end = '';
-            if (static::$outputDecorated) {
-                $line = "\033[1A\n\033[K";
-                $white = "\033[47;30m";
-                $green = "\033[32;40m";
-                $end = "\033[0m";
-            }
-            $msg = str_replace(['<n>', '<w>', '<g>'], [$line, $white, $green], $msg);
-            $msg = str_replace(['</n>', '</w>', '</g>'], $end, $msg);
-        } elseif (!static::$outputDecorated) {
-            return false;
-        }
-        fwrite($stream, $msg);
-        fflush($stream);
-        return true;
-    }
-
-    /**
-     * set and get output stream.
-     *
-     * @param resource|null $stream
-     * @return false|resource
+     * @return void
      */
-    private static function outputStream($stream = null): mixed
+    public static function safeEcho(string $msg, bool $decorated = false): void
     {
-        if (!$stream) {
-            $stream = static::$outputStream ?: STDOUT;
-        }
-        // @phpstan-ignore-next-line Negated boolean expression is always false.
-        if (!$stream || !is_resource($stream) || 'stream' !== get_resource_type($stream)) {
-            return false;
-        }
-        $stat = fstat($stream);
-        if (!$stat) {
-            return false;
+        if (!(static::$outputDecorated ?? false) && $decorated) {
+            return;
         }
 
-        if (($stat['mode'] & 0170000) === 0100000) { // whether is regular file
-            static::$outputDecorated = false;
+        if (static::$outputDecorated ?? false) {
+            $line = "\033[1A\n\033[K";
+            $white = "\033[47;30m";
+            $green = "\033[32;40m";
+            $end = "\033[0m";
         } else {
-            static::$outputDecorated =
-                DIRECTORY_SEPARATOR === '/' && // linux or unix
-                function_exists('posix_isatty') &&
-                posix_isatty($stream); // whether is interactive terminal
+            $line = '';
+            $white = '';
+            $green = '';
+            $end = '';
         }
-        return static::$outputStream = $stream;
+
+        $msg = str_replace(['<n>', '<w>', '<g>'], [$line, $white, $green], $msg);
+        $msg = str_replace(['</n>', '</w>', '</g>'], $end, $msg);
+        fwrite(self::$outputStream, $msg);
+        fflush(self::$outputStream);
     }
 
     /**

+ 2 - 3
tests/Feature/HttpConnectionTest.php

@@ -7,10 +7,9 @@ use Symfony\Component\Process\PhpProcess;
 
 $process = null;
 beforeAll(function () use (&$process) {
-    $code = file_get_contents(__DIR__ . '/Stub/HttpServer.php');
-    $process = new PhpProcess($code);
+    $process = new PhpProcess(file_get_contents(__DIR__ . '/Stub/HttpServer.php'));
     $process->start();
-    sleep(1);
+    usleep(250000);
 });
 
 afterAll(function () use (&$process) {

+ 3 - 1
tests/Feature/Stub/HttpServer.php

@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 use Workerman\Connection\TcpConnection;
 use Workerman\Protocols\Http\Request;
 use Workerman\Protocols\Http\Response;
@@ -49,4 +51,4 @@ $worker->onMessage = function (TcpConnection $connection, Request $request) {
 };
 
 Worker::$command = 'start';
-Worker::runAll();
+Worker::runAll();

+ 20 - 0
tests/Feature/Stub/UdpServer.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+use Workerman\Worker;
+
+require './vendor/autoload.php';
+
+if(!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
+if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
+if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
+
+$server = new Worker('udp://127.0.0.1:8083');
+
+$server->onMessage = function ($connection, $data) {
+    $connection->send('received: ' . $data);
+};
+
+Worker::$command = 'start';
+Worker::runAll();

+ 23 - 0
tests/Feature/Stub/WebsocketClient.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+use Workerman\Connection\AsyncTcpConnection;
+use Workerman\Worker;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+if (!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
+if (!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
+if (!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
+
+$worker = new Worker();
+$worker->onWorkerStart = function($worker) {
+    $con = new AsyncTcpConnection('ws://127.0.0.1:8081');
+    //%action%
+    $con->connect();
+};
+
+Worker::$pidFile = sprintf('%s/test-websocket-client.pid', sys_get_temp_dir());
+Worker::$command = 'start';
+Worker::runAll();

+ 20 - 0
tests/Feature/Stub/WebsocketServer.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+use Workerman\Connection\TcpConnection;
+use Workerman\Protocols\Http\Request;
+use Workerman\Worker;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+if (!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
+if (!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
+if (!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
+
+$worker = new Worker("websocket://127.0.0.1:8081");
+//%action%
+
+Worker::$pidFile = sprintf('%s/test-websocket-server.pid', sys_get_temp_dir());
+Worker::$command = 'start';
+Worker::runAll();

+ 5 - 22
tests/Feature/UdpConnectionTest.php

@@ -1,29 +1,12 @@
 <?php
 
 use Symfony\Component\Process\PhpProcess;
-use Workerman\Worker;
 
-$serverAddress = 'udp://127.0.0.1:6789';
 $process = null;
-beforeAll(function () use ($serverAddress, &$process) {
-    $process = new PhpProcess(<<<PHP
-        <?php    
-        if(!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
-        if(!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
-        if(!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
-        require './vendor/autoload.php';
-        use Workerman\Worker;
-        
-        \$server = new Worker('$serverAddress');
-        \$server->onMessage = function (\$connection, \$data) {
-            \$connection->send('received: '.\$data);
-        };
-        Worker::\$command = 'start';
-        Worker::runAll();
-    PHP
-    );
+beforeAll(function () use (&$process) {
+    $process = new PhpProcess(file_get_contents(__DIR__ . '/Stub/UdpServer.php'));
     $process->start();
-    sleep(1);
+    usleep(250000);
 });
 
 afterAll(function () use (&$process) {
@@ -31,8 +14,8 @@ afterAll(function () use (&$process) {
     $process->stop();
 });
 
-it('tests udp connection', function () use ($serverAddress) {
-    $socket = stream_socket_client($serverAddress, $errno, $errstr, 1);
+it('tests udp connection', function () {
+    $socket = stream_socket_client('udp://127.0.0.1:8083', $errno, $errstr, 1);
     expect($errno)->toBeInt()->toBe(0);
     fwrite($socket, 'xiami');
     $data = fread($socket, 1024);

+ 26 - 66
tests/Feature/WebsocketServiceTest.php

@@ -2,60 +2,26 @@
 
 use Symfony\Component\Process\PhpProcess;
 
-$serverCode = <<<PHP
-<?php
-use Workerman\Connection\TcpConnection;
-use Workerman\Protocols\Http\Request;
-use Workerman\Worker;
-require_once __DIR__ . '/vendor/autoload.php';
-if (!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
-if (!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
-if (!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
-\$worker = new Worker("websocket://127.0.0.1:8081");
-%s
-Worker::\$pidFile = __DIR__ . '/WebsocketServer.pid';
-Worker::\$command = 'start';
-Worker::runAll();
-PHP;
-
-$clientCode = <<<PHP
-<?php
-use Workerman\Connection\AsyncTcpConnection;
-use Workerman\Worker;
-require_once __DIR__ . '/vendor/autoload.php';
-if (!defined('STDIN')) define('STDIN', fopen('php://stdin', 'r'));
-if (!defined('STDOUT')) define('STDOUT', fopen('php://stdout', 'w'));
-if (!defined('STDERR')) define('STDERR', fopen('php://stderr', 'w'));
-\$worker = new Worker();
-\$worker->onWorkerStart = function(\$worker){
-    \$con = new AsyncTcpConnection('ws://127.0.0.1:8081');
-    %s
-    \$con->connect();
-};
-Worker::\$pidFile = __DIR__ . '/WebsocketClient.pid';
-Worker::\$command = 'start';
-Worker::runAll();
-PHP;
+$serverCode = file_get_contents(__DIR__ . '/Stub/WebsocketServer.php');
+$clientCode = file_get_contents(__DIR__ . '/Stub/WebsocketClient.php');
 
 it('tests websocket connection', function () use ($serverCode, $clientCode) {
-    $serverProcess = new PhpProcess(sprintf($serverCode, <<<PHP
+    $serverProcess = new PhpProcess(str_replace(subject: $serverCode, search: '//%action%', replace: <<<PHP
         \$worker->onWebSocketConnect = function () {
             echo "connected";
         };
         \$worker->onMessage = function () {};
-    PHP
-    ));
+    PHP));
     $serverProcess->start();
-    sleep(1);
+    usleep(250000);
 
-    $clientProcess = new PhpProcess(sprintf($clientCode, <<<PHP
+    $clientProcess = new PhpProcess(str_replace(subject: $clientCode, search: '//%action%', replace: <<<PHP
         \$con->onWebSocketConnect = function(AsyncTcpConnection \$con) {
             \$con->send('connect');
         };
-    PHP
-    ));
+    PHP));
     $clientProcess->start();
-    sleep(1);
+    usleep(250000);
 
     expect(getNonFrameOutput($serverProcess->getOutput()))->toBe('connected')
         ->and(getNonFrameOutput($clientProcess->getOutput()))->toBe('');
@@ -65,27 +31,25 @@ it('tests websocket connection', function () use ($serverCode, $clientCode) {
 });
 
 it('tests server and client sending and receiving messages', function () use ($serverCode, $clientCode) {
-    $serverProcess = new PhpProcess(sprintf($serverCode, <<<PHP
+    $serverProcess = new PhpProcess(str_replace(subject: $serverCode, search: '//%action%', replace: <<<PHP
         \$worker->onMessage = function (TcpConnection \$connection, \$data) {
             echo \$data;
             \$connection->send('Hi');
         };
-    PHP
-    ));
+    PHP));
     $serverProcess->start();
-    sleep(1);
+    usleep(250000);
 
-    $clientProcess = new PhpProcess(sprintf($clientCode, <<<PHP
+    $clientProcess = new PhpProcess(str_replace(subject: $clientCode, search: '//%action%', replace: <<<PHP
         \$con->onWebSocketConnect = function(AsyncTcpConnection \$con) {
             \$con->send('Hello Chance');
         };
         \$con->onMessage = function(\$con, \$data) {
             echo \$data;
         };
-    PHP
-    ));
+    PHP));
     $clientProcess->start();
-    sleep(1);
+    usleep(250000);
 
     expect(getNonFrameOutput($serverProcess->getOutput()))->toBe('Hello Chance')
         ->and(getNonFrameOutput($clientProcess->getOutput()))->toBe('Hi');
@@ -95,28 +59,26 @@ it('tests server and client sending and receiving messages', function () use ($s
 });
 
 it('tests server close connection', function () use ($serverCode, $clientCode) {
-    $serverProcess = new PhpProcess(sprintf($serverCode, <<<PHP
+    $serverProcess = new PhpProcess(str_replace(subject: $serverCode, search: '//%action%', replace: <<<PHP
         \$worker->onWebSocketConnect = function (TcpConnection \$connection) {
             echo 'close connection';
             \$connection->close();
         };
         \$worker->onMessage = function () {};
-    PHP
-    ));
+    PHP));
     $serverProcess->start();
-    sleep(1);
+    usleep(250000);
 
-    $clientProcess = new PhpProcess(sprintf($clientCode, <<<PHP
+    $clientProcess = new PhpProcess(str_replace(subject: $clientCode, search: '//%action%', replace: <<<PHP
         \$con->onWebSocketConnect = function(AsyncTcpConnection \$con) {
             \$con->send('connect');
         };
         \$con->onClose = function () {
             echo 'closed';
         };
-    PHP
-    ));
+    PHP));
     $clientProcess->start();
-    sleep(1);
+    usleep(250000);
 
     expect(getNonFrameOutput($serverProcess->getOutput()))->toBe('close connection')
         ->and(getNonFrameOutput($clientProcess->getOutput()))->toBe('closed');
@@ -126,26 +88,24 @@ it('tests server close connection', function () use ($serverCode, $clientCode) {
 });
 
 it('tests client close connection', function () use ($serverCode, $clientCode) {
-    $serverProcess = new PhpProcess(sprintf($serverCode, <<<PHP
+    $serverProcess = new PhpProcess(str_replace(subject: $serverCode, search: '//%action%', replace: <<<PHP
         \$worker->onMessage = function () {};
         \$worker->onClose = function () {
             echo 'closed';
         };
-    PHP
-    ));
+    PHP));
     $serverProcess->start();
-    sleep(1);
+    usleep(250000);
 
-    $clientProcess = new PhpProcess(sprintf($clientCode, <<<PHP
+    $clientProcess = new PhpProcess(str_replace(subject: $clientCode, search: '//%action%', replace: <<<PHP
         \$con->onWebSocketConnect = function(AsyncTcpConnection \$con) {
             \$con->send('connect');
             echo 'close connection';
             \$con->close();
         };
-    PHP
-    ));
+    PHP));
     $clientProcess->start();
-    sleep(1);
+    usleep(250000);
 
     expect(getNonFrameOutput($serverProcess->getOutput()))->toBe('closed')
         ->and(getNonFrameOutput($clientProcess->getOutput()))->toBe('close connection');

+ 1 - 1
tests/Unit/Connection/UdpConnectionTest.php

@@ -16,7 +16,7 @@ beforeAll(function () use ($remoteAddress, &$process) {
     PHP
     );
     $process->start();
-    sleep(1);
+    usleep(250000);
 });
 
 afterAll(function () use (&$process) {