diff --git a/src/Factory.php b/src/Factory.php index ca31ea0..d8ab5ef 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -30,8 +30,7 @@ public function createClient($address, $timeout = null) $socket->connectTimeout($address, $timeout); $socket->setBlocking(true); } - } - catch (Exception $e) { + } catch (Exception $e) { $socket->close(); throw $e; } @@ -59,8 +58,7 @@ public function createServer($address) if ($socket->getType() === SOCK_STREAM) { $socket->listen(); } - } - catch (Exception $e) { + } catch (Exception $e) { $socket->close(); throw $e; } diff --git a/src/Socket.php b/src/Socket.php index e3fdd86..536855f 100644 --- a/src/Socket.php +++ b/src/Socket.php @@ -141,10 +141,9 @@ public function connectTimeout($address, $timeout) // socket is already connected immediately? return $this; - } - catch (Exception $e) { - // non-blocking connect() should be EINPROGRESS => otherwise re-throw - if ($e->getCode() !== SOCKET_EINPROGRESS) { + } catch (Exception $e) { + // non-blocking connect() should be EINPROGRESS (or EWOULDBLOCK on Windows) => otherwise re-throw + if ($e->getCode() !== SOCKET_EINPROGRESS && $e->getCode() !== SOCKET_EWOULDBLOCK) { throw $e; } diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index a18979b..0f4fb12 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -94,8 +94,7 @@ public function testCreateClientTcp4UnboundFails() { try { $this->factory->createClient('tcp://localhost:2'); - } - catch (Exception $e) { + } catch (Exception $e) { if ($e->getCode() === SOCKET_ECONNREFUSED) { return; } @@ -160,13 +159,18 @@ public function testCreateServerUdp4Random() */ public function testCreateServerUnix() { - $path = '/tmp/randomfactorytests.sock'; + if (DIRECTORY_SEPARATOR !== '\\') { + $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'test-' . md5(microtime(true)) . '.sock'; + } else { + // create test socket in local directory on Windows + $path = 'test-' . md5(microtime(true)) . '.sock'; + } $socket = $this->factory->createServer('unix://' . $path); $this->assertInstanceOf('Socket\Raw\Socket', $socket); - unlink($path); + $this->assertTrue(unlink($path), 'Unable to remove temporary socket ' . $path); } /** @@ -179,8 +183,7 @@ public function testCreateServerUnixFailInvalidPath() try { $this->factory->createServer('unix://' . $path); - } - catch (Exception $e) { + } catch (Exception $e) { return; } @@ -192,7 +195,10 @@ public function testCreateServerUnixFailInvalidPath() */ public function testCreateServerUdg() { - // skip if not unix + if (DIRECTORY_SEPARATOR === '\\') { + // https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-comes-to-windows/ + $this->markTestSkipped('Unix datagram sockets not supported on Windows'); + } $path = '/tmp/randomfactorytests.sock'; @@ -207,8 +213,7 @@ public function testCreateServerIcmp4() { try { $socket = $this->factory->createServer('icmp://0.0.0.0'); - } - catch (Exception $e) { + } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); @@ -226,8 +231,7 @@ public function testCreateServerIcmp6() { try { $socket = $this->factory->createServer('icmp6://[::1]'); - } - catch (Exception $e) { + } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); @@ -287,6 +291,11 @@ public function testCreateUnix() */ public function testCreateUdg() { + if (DIRECTORY_SEPARATOR === '\\') { + // https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-comes-to-windows/ + $this->markTestSkipped('Unix datagram sockets not supported on Windows'); + } + $socket = $this->factory->createUdg(); $this->assertInstanceOf('Socket\Raw\Socket', $socket); @@ -296,8 +305,7 @@ public function testCreateIcmp4() { try { $socket = $this->factory->createIcmp4(); - } - catch (Exception $e) { + } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root return $this->markTestSkipped('No access to ICMPv4 socket (only root can do so)'); @@ -315,8 +323,7 @@ public function testCreateIcmp6() { try { $socket = $this->factory->createIcmp6(); - } - catch (Exception $e) { + } catch (Exception $e) { if ($e->getCode() === SOCKET_EPERM) { // skip if not root return $this->markTestSkipped('No access to ICMPv6 socket (only root can do so)'); @@ -353,8 +360,7 @@ public function testCreateInvalid() { try { $this->factory->create(0, 1, 2); - } - catch (Exception $e) { + } catch (Exception $e) { return; } $this->fail(); @@ -365,6 +371,10 @@ public function testCreateInvalid() */ public function testCreatePair() { + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestSkipped('Unix socket pair not supported on Windows'); + } + $sockets = $this->factory->createPair(AF_UNIX, SOCK_STREAM, 0); $this->assertCount(2, $sockets); @@ -379,8 +389,7 @@ public function testCreatePairInvalid() { try { $this->factory->createPair(0, 1, 2); - } - catch (Exception $e) { + } catch (Exception $e) { return; } $this->fail(); @@ -473,8 +482,7 @@ public function testCreateFromStringInvalid() $address = 'invalid://whatever'; try { $this->factory->createFromString($address, $scheme); - } - catch (Exception $e) { + } catch (Exception $e) { return; } $this->fail('Creating socket for invalid scheme should fail'); diff --git a/tests/SocketTest.php b/tests/SocketTest.php index e5f55b4..2ffa6b2 100644 --- a/tests/SocketTest.php +++ b/tests/SocketTest.php @@ -63,8 +63,7 @@ public function testConnectFailUnbound() try { $this->factory->createClient('localhost:2'); $this->fail('Expected connection to fail'); - } - catch (Exception $e) { + } catch (Exception $e) { $this->assertEquals(SOCKET_ECONNREFUSED, $e->getCode()); } } @@ -83,10 +82,12 @@ public function testConnectAsyncGoogle() try { $this->assertSame($socket, $socket->connect('www.google.com:80')); $this->fail('Calling connect() succeeded immediately'); - } - catch (Exception $e) { - // non-blocking connect() should be EINPROGRESS - $this->assertEquals(SOCKET_EINPROGRESS, $e->getCode()); + } catch (Exception $e) { + // non-blocking connect() should be EINPROGRESS or EWOULDBLOCK on Windows + $this->assertEquals( + DIRECTORY_SEPARATOR !== '\\' ? SOCKET_EINPROGRESS : SOCKET_EWOULDBLOCK, + $e->getCode() + ); // connection should be established within 5.0 seconds $this->assertTrue($socket->selectWrite(5.0)); @@ -100,6 +101,10 @@ public function testConnectAsyncGoogle() public function testConnectAsyncFailUnbound() { + if (PHP_OS !== 'Linux') { + $this->markTestSkipped('Only Linux is known to refuse connections to unbound addresses'); + } + $socket = $this->factory->createTcp4(); $this->assertInstanceOf('Socket\Raw\Socket', $socket); @@ -109,8 +114,7 @@ public function testConnectAsyncFailUnbound() try { $this->assertSame($socket, $socket->connect('localhost:2')); $this->fail('Calling connect() succeeded immediately'); - } - catch (Exception $e) { + } catch (Exception $e) { // non-blocking connect() should be EINPROGRESS $this->assertEquals(SOCKET_EINPROGRESS, $e->getCode()); @@ -139,7 +143,8 @@ public function testSelectFloat() $this->assertFalse($socket->selectRead(0.5)); $time = microtime(true) - $time; - $this->assertGreaterThan(0.5, $time); + // check timer interval, but add some grace time to cope with inaccurate timers + $this->assertGreaterThan(0.48, $time); $this->assertLessThan(1.0, $time); } @@ -176,6 +181,10 @@ public function testConnectTimeoutFailTimeout() public function testConnectTimeoutFailUnbound() { + if (PHP_OS !== 'Linux') { + $this->markTestSkipped('Only Linux is known to refuse connections to unbound addresses'); + } + $socket = $this->factory->createTcp4(); $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ECONNREFUSED); @@ -225,7 +234,9 @@ public function testServerNonBlockingAcceptClient(Socket $server) // create local client connected to the given server $client = $this->factory->createClient($server->getSockName()); - // client connected, so we can not accept() this socket + // client connected, so we should be able to accept() this socket immediately + // on Windows, there appears to be a race condition, so first wait for server to be ready + $server->selectRead(0.1); $peer = $server->accept(); // peer should be writable right away