Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run tests on PHP 7.4 and simplify test matrix and test setup #209

Merged
merged 5 commits into from
Dec 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
language: php

php:
# - 5.3 # requires old distro, see below
# - 5.4 # requires old distro, see below
# - 5.5 # requires old distro, see below
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
# - hhvm # requires legacy phpunit & ignore errors, see below

# lock distro so new future defaults will not break the build
dist: xenial

Expand All @@ -25,9 +14,17 @@ matrix:
- php: 5.5
dist: trusty
before_install: [] # skip libuv
- php: hhvm
install: composer require phpunit/phpunit:^5 --dev --no-interaction
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2
- php: 7.3
- php: 7.4
- php: hhvm-3.18
dist: trusty
before_install: [] # skip libuv
install:
- composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit & skip ./travis-init.sh
- name: "Windows"
os: windows
language: shell # no built-in php support
Expand All @@ -51,8 +48,22 @@ matrix:
- php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);"
install:
- composer install
- name: "Windows PHP 7.4 with ext-uv"
os: windows
language: shell # no built-in php support
before_install:
- curl -OL https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.4-nts-vc15-x64.zip # latest version as of 2019-12-23
- choco install php --version=7.4.0 # latest version supported by ext-uv as of 2019-12-23
- choco install composer
- export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')"
- php -r "\$z=new ZipArchive();\$z->open(glob('php_uv*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_uv.dll');\$z->extractTo(dirname(php_ini_loaded_file()),'libuv.dll');"
- php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);"
- php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-uv
- php -r "file_put_contents(php_ini_loaded_file(),'extension=uv'.PHP_EOL,FILE_APPEND);"
install:
- composer install
allow_failures:
- php: hhvm
- php: hhvm-3.18
- os: windows

sudo: false
Expand Down
16 changes: 9 additions & 7 deletions src/ExtUvLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,15 @@ private function pollStream($stream)
private function createStreamListener()
{
$callback = function ($event, $status, $events, $stream) {
if (!isset($this->streamEvents[(int) $stream])) {
return;
}

if (($events | 4) === 4) {
// Disconnected
return;
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
if ($status !== 0) {
$this->pollStream($stream);

// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
// re-enable both readable and writable, correct listeners will be checked below anyway
if ($events === 0) {
$events = \UV::READABLE | \UV::WRITABLE;
}
}

if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
Expand Down
22 changes: 21 additions & 1 deletion src/StreamSelectLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,30 @@ private function waitForStreamActivity($timeout)
private function streamSelect(array &$read, array &$write, $timeout)
{
if ($read || $write) {
// We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
// However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
// Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
// We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
// This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
// Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
$except = null;
if (\DIRECTORY_SEPARATOR === '\\') {
$except = array();
foreach ($write as $key => $socket) {
if (!isset($read[$key]) && @\ftell($socket) === 0) {
$except[$key] = $socket;
}
}
}

// suppress warnings that occur, when stream_select is interrupted by a signal
return @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
$ret = @\stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);

if ($except) {
$write = \array_merge($write, $except);
}
return $ret;
}

if ($timeout > 0) {
Expand Down
149 changes: 149 additions & 0 deletions tests/AbstractLoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace React\Tests\EventLoop;

use React\EventLoop\StreamSelectLoop;
use React\EventLoop\ExtUvLoop;

abstract class AbstractLoopTest extends TestCase
{
/**
Expand Down Expand Up @@ -36,8 +39,111 @@ public function createSocketPair()
return $sockets;
}

public function testAddReadStreamTriggersWhenSocketReceivesData()
{
list ($input, $output) = $this->createSocketPair();

$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
$loop->removeReadStream($input);
});

$called = 0;
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
++$called;
$loop->removeReadStream($input);
$loop->cancelTimer($timeout);
});

fwrite($output, "foo\n");

$this->loop->run();

$this->assertEquals(1, $called);
}

public function testAddReadStreamTriggersWhenSocketCloses()
{
list ($input, $output) = $this->createSocketPair();

$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
$loop->removeReadStream($input);
});

$called = 0;
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
++$called;
$loop->removeReadStream($input);
$loop->cancelTimer($timeout);
});

fclose($output);

$this->loop->run();

$this->assertEquals(1, $called);
}

public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds()
{
$server = stream_socket_server('127.0.0.1:0');

$errno = $errstr = null;
$connecting = stream_socket_client(stream_socket_get_name($server, false), $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);

$loop = $this->loop;
$timeout = $loop->addTimer(0.1, function () use ($connecting, $loop) {
$loop->removeWriteStream($connecting);
});

$called = 0;
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
++$called;
$loop->removeWriteStream($connecting);
$loop->cancelTimer($timeout);
});

$this->loop->run();

$this->assertEquals(1, $called);
}

public function testAddWriteStreamTriggersWhenSocketConnectionRefused()
{
// first verify the operating system actually refuses the connection and no firewall is in place
// use higher timeout because Windows retires multiple times and has a noticeable delay
// @link https://stackoverflow.com/questions/19440364/why-do-failed-attempts-of-socket-connect-take-1-sec-on-windows
$errno = $errstr = null;
if (@stream_socket_client('127.0.0.1:1', $errno, $errstr, 10.0) !== false || (defined('SOCKET_ECONNREFUSED') && $errno !== SOCKET_ECONNREFUSED)) {
$this->markTestSkipped('Expected host to refuse connection, but got error ' . $errno . ': ' . $errstr);
}

$connecting = stream_socket_client('127.0.0.1:1', $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);

$loop = $this->loop;
$timeout = $loop->addTimer(10.0, function () use ($connecting, $loop) {
$loop->removeWriteStream($connecting);
});

$called = 0;
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
++$called;
$loop->removeWriteStream($connecting);
$loop->cancelTimer($timeout);
});

$this->loop->run();

$this->assertEquals(1, $called);
}

public function testAddReadStream()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

$this->loop->addReadStream($input, $this->expectCallableExactly(2));
Expand All @@ -51,6 +157,10 @@ public function testAddReadStream()

public function testAddReadStreamIgnoresSecondCallable()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

$this->loop->addReadStream($input, $this->expectCallableExactly(2));
Expand Down Expand Up @@ -100,6 +210,10 @@ private function subAddReadStreamReceivesDataFromStreamReference()

public function testAddWriteStream()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input) = $this->createSocketPair();

$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
Expand All @@ -109,6 +223,10 @@ public function testAddWriteStream()

public function testAddWriteStreamIgnoresSecondCallable()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input) = $this->createSocketPair();

$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
Expand All @@ -119,6 +237,10 @@ public function testAddWriteStreamIgnoresSecondCallable()

public function testRemoveReadStreamInstantly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

$this->loop->addReadStream($input, $this->expectCallableNever());
Expand All @@ -130,6 +252,10 @@ public function testRemoveReadStreamInstantly()

public function testRemoveReadStreamAfterReading()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

$this->loop->addReadStream($input, $this->expectCallableOnce());
Expand All @@ -145,6 +271,10 @@ public function testRemoveReadStreamAfterReading()

public function testRemoveWriteStreamInstantly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input) = $this->createSocketPair();

$this->loop->addWriteStream($input, $this->expectCallableNever());
Expand All @@ -154,6 +284,10 @@ public function testRemoveWriteStreamInstantly()

public function testRemoveWriteStreamAfterWriting()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input) = $this->createSocketPair();

$this->loop->addWriteStream($input, $this->expectCallableOnce());
Expand All @@ -165,6 +299,10 @@ public function testRemoveWriteStreamAfterWriting()

public function testRemoveStreamForReadOnly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

$this->loop->addReadStream($input, $this->expectCallableNever());
Expand All @@ -177,6 +315,10 @@ public function testRemoveStreamForReadOnly()

public function testRemoveStreamForWriteOnly()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($input, $output) = $this->createSocketPair();

fwrite($output, "foo\n");
Expand Down Expand Up @@ -399,6 +541,10 @@ public function testFutureTick()

public function testFutureTickFiresBeforeIO()
{
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
}

list ($stream) = $this->createSocketPair();

$this->loop->addWriteStream(
Expand All @@ -419,6 +565,9 @@ function () {
$this->tickLoop($this->loop);
}

/**
* @depends testFutureTickFiresBeforeIO
*/
public function testRecursiveFutureTick()
{
list ($stream) = $this->createSocketPair();
Expand Down
Loading