diff --git a/.travis.yml b/.travis.yml index 3fb051c..207c346 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php # lock distro so new future defaults will not break the build dist: trusty -matrix: +jobs: include: - php: 5.3 dist: precise @@ -20,10 +20,8 @@ matrix: allow_failures: - php: hhvm-3.18 -sudo: false - install: - - composer install --no-interaction + - composer install script: - vendor/bin/phpunit --coverage-text diff --git a/composer.json b/composer.json index 8336e47..b5d7750 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require-dev": { "clue/block-react": "^1.3", - "phpunit/phpunit": "^7.4 || ^6.4 || ^5.0 || ^4.8.36", + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.36", "react/mysql": "^0.5.3" } } diff --git a/src/Io/functions.php b/src/Io/functions.php index 0f2eb8f..622d310 100644 --- a/src/Io/functions.php +++ b/src/Io/functions.php @@ -8,7 +8,7 @@ * Returns a list of active file descriptors (may contain bogus entries) * * @param string $path - * @return array + * @return int[] * @internal */ function fds($path = '/dev/fd') @@ -28,6 +28,10 @@ function fds($path = '/dev/fd') \fclose($copy); } } + } else { + foreach ($fds as $i => $fd) { + $fds[$i] = (int) $fd; + } } return $fds; diff --git a/tests/FunctionalSshProcessConnectorTest.php b/tests/FunctionalSshProcessConnectorTest.php index 67bf39d..e23ebff 100644 --- a/tests/FunctionalSshProcessConnectorTest.php +++ b/tests/FunctionalSshProcessConnectorTest.php @@ -13,7 +13,10 @@ class FunctionalSshProcessConnectorTest extends TestCase private $loop; private $connector; - public function setUp() + /** + * @before + */ + public function setUpConnector() { $url = getenv('SSH_PROXY'); if ($url === false) { @@ -24,38 +27,29 @@ public function setUp() $this->connector = new SshProcessConnector($url, $this->loop); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to example.com:80 failed because SSH client died - */ public function testConnectInvalidProxyUriWillReturnRejectedPromise() { $this->connector = new SshProcessConnector(getenv('SSH_PROXY') . '.invalid', $this->loop); $promise = $this->connector->connect('example.com:80'); + $this->setExpectedException('RuntimeException', 'Connection to example.com:80 failed because SSH client died'); \Clue\React\Block\await($promise, $this->loop, self::TIMEOUT); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to example.invalid:80 rejected: - */ public function testConnectInvalidTargetWillReturnRejectedPromise() { $promise = $this->connector->connect('example.invalid:80'); + $this->setExpectedException('RuntimeException', 'Connection to example.invalid:80 rejected:'); \Clue\React\Block\await($promise, $this->loop, self::TIMEOUT); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to example.com:80 cancelled while waiting for SSH client - */ public function testCancelConnectWillReturnRejectedPromise() { $promise = $this->connector->connect('example.com:80'); $promise->cancel(); + $this->setExpectedException('RuntimeException', 'Connection to example.com:80 cancelled while waiting for SSH client'); \Clue\React\Block\await($promise, $this->loop, 0); } diff --git a/tests/FunctionalSshSocksConnectorTest.php b/tests/FunctionalSshSocksConnectorTest.php index b044679..5228837 100644 --- a/tests/FunctionalSshSocksConnectorTest.php +++ b/tests/FunctionalSshSocksConnectorTest.php @@ -2,7 +2,6 @@ namespace Clue\Tests\React\SshProxy; -use PHPUnit\Framework\TestCase; use React\EventLoop\Factory; use Clue\React\SshProxy\SshSocksConnector; @@ -13,7 +12,10 @@ class FunctionalSshSocksConnectorTest extends TestCase private $loop; private $connector; - public function setUp() + /** + * @before + */ + public function setUpConnector() { $url = getenv('SSH_PROXY'); if ($url === false) { @@ -24,45 +26,39 @@ public function setUp() $this->connector = new SshSocksConnector($url, $this->loop); } - public function tearDown() + /** + * @after + */ + public function tearDownSSHClientProcess() { // run loop in order to shut down SSH client process again \Clue\React\Block\sleep(0.001, $this->loop); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to example.com:80 failed because SSH client process died - */ public function testConnectInvalidProxyUriWillReturnRejectedPromise() { $this->connector = new SshSocksConnector(getenv('SSH_PROXY') . '.invalid', $this->loop); $promise = $this->connector->connect('example.com:80'); + $this->setExpectedException('RuntimeException', 'Connection to example.com:80 failed because SSH client process died'); \Clue\React\Block\await($promise, $this->loop, self::TIMEOUT); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to tcp://example.invalid:80 failed because connection to proxy was lost - */ public function testConnectInvalidTargetWillReturnRejectedPromise() { $promise = $this->connector->connect('example.invalid:80'); + $this->setExpectedException('RuntimeException', 'Connection to tcp://example.invalid:80 failed because connection to proxy was lost'); \Clue\React\Block\await($promise, $this->loop, self::TIMEOUT); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to example.com:80 cancelled while waiting for SSH client - */ public function testCancelConnectWillReturnRejectedPromise() { $promise = $this->connector->connect('example.com:80'); $promise->cancel(); + $this->setExpectedException('RuntimeException', 'Connection to example.com:80 cancelled while waiting for SSH client'); \Clue\React\Block\await($promise, $this->loop, 0); } diff --git a/tests/IntegrationSshProcessConnectorTest.php b/tests/IntegrationSshProcessConnectorTest.php index 899757b..a712c36 100644 --- a/tests/IntegrationSshProcessConnectorTest.php +++ b/tests/IntegrationSshProcessConnectorTest.php @@ -3,7 +3,6 @@ namespace Clue\Tests\React\SshProxy; use Clue\React\SshProxy\SshProcessConnector; -use PHPUnit\Framework\TestCase; use React\EventLoop\Factory; use React\Socket\ConnectionInterface; @@ -72,21 +71,4 @@ public function testConnectWillResolveWithConnectionThatWillEmitImmediateDataFro $loop->run(); } - - protected function expectCallableOnceWith($value) - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($value); - - return $mock; - } - - protected function createCallableMock() - { - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); - } } diff --git a/tests/Io/CompositeConnectionTest.php b/tests/Io/CompositeConnectionTest.php index 061581a..8dcc84c 100644 --- a/tests/Io/CompositeConnectionTest.php +++ b/tests/Io/CompositeConnectionTest.php @@ -1,7 +1,7 @@ assertFalse($stream->isReadable()); $this->assertFalse($stream->isWritable()); } - - protected function expectCallableOnce() - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke'); - - return $mock; - } - - protected function expectCallableNever() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->never()) - ->method('__invoke'); - - return $mock; - } - - protected function createCallableMock() - { - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); - } } diff --git a/tests/Io/FunctionsTest.php b/tests/Io/FunctionsTest.php index 6d17868..3709aef 100644 --- a/tests/Io/FunctionsTest.php +++ b/tests/Io/FunctionsTest.php @@ -1,7 +1,7 @@ assertInternalType('array', $fds); + $this->assertEquals('array', gettype($fds)); } public function testFdsReturnsArrayWithStdioHandles() @@ -38,7 +38,7 @@ public function testFdsWithInvalidPathReturnsArray() { $fds = Io\fds('/dev/null'); - $this->assertInternalType('array', $fds); + $this->assertEquals('array', gettype($fds)); } public function testFdsWithInvalidPathReturnsSubsetOfFdsFromDevFd() @@ -65,9 +65,9 @@ public function testProcessWithoutFdsReturnsProcessWithoutClosingDefaultHandles( $this->assertInstanceOf('React\ChildProcess\Process', $process); - $this->assertNotContains(' 0>&-', $process->getCommand()); - $this->assertNotContains(' 1>&-', $process->getCommand()); - $this->assertNotContains(' 2>&-', $process->getCommand()); + $this->assertNotContainsString(' 0>&-', $process->getCommand()); + $this->assertNotContainsString(' 1>&-', $process->getCommand()); + $this->assertNotContainsString(' 2>&-', $process->getCommand()); } public function testProcessWithoutFdsReturnsProcessWithOriginalCommandPartOfActualCommandWhenDescriptorsNeedToBeClosed() @@ -84,6 +84,6 @@ public function testProcessWithoutFdsReturnsProcessWithOriginalCommandPartOfActu $this->assertInstanceOf('React\ChildProcess\Process', $process); $this->assertNotEquals('sleep 10', $process->getCommand()); - $this->assertContains('sleep 10', $process->getCommand()); + $this->assertContainsString('sleep 10', $process->getCommand()); } } diff --git a/tests/Io/LineSeparatedReaderTest.php b/tests/Io/LineSeparatedReaderTest.php index 2eb64cc..39ecaa1 100644 --- a/tests/Io/LineSeparatedReaderTest.php +++ b/tests/Io/LineSeparatedReaderTest.php @@ -1,7 +1,7 @@ assertSame($dest, $ret); } - - protected function expectCallableOnce() - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke'); - - return $mock; - } - - protected function expectCallableOnceWith($value) - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($value); - - return $mock; - } - - protected function expectCallableNever() - { - $mock = $this->createCallableMock(); - $mock - ->expects($this->never()) - ->method('__invoke'); - - return $mock; - } - - protected function createCallableMock() - { - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); - } } diff --git a/tests/SshProcessConnectorTest.php b/tests/SshProcessConnectorTest.php index ef1acf7..01cdfc9 100644 --- a/tests/SshProcessConnectorTest.php +++ b/tests/SshProcessConnectorTest.php @@ -2,7 +2,6 @@ namespace Clue\Tests\React\SshProxy; -use PHPUnit\Framework\TestCase; use Clue\React\SshProxy\SshProcessConnector; class SshProcessConnectorTest extends TestCase @@ -51,39 +50,35 @@ public function testConstructorAcceptsUriWithPasswordWillPrefixSshCommandWithSsh $this->assertEquals('exec sshpass -p \'pass\' ssh -vv \'user@host\'', $ref->getValue($connector)); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidUri() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshProcessConnector('///', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidUser() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshProcessConnector('-invalid@host', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidPass() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshProcessConnector('user:-invalid@host', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidHost() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshProcessConnector('-host', $loop); } @@ -113,21 +108,4 @@ public function testConnectReturnsRejectedPromiseForInvalidHost() $promise = $connector->connect('-host:80'); $promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('InvalidArgumentException'))); } - - protected function expectCallableOnceWith($value) - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($value); - - return $mock; - } - - protected function createCallableMock() - { - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); - } } diff --git a/tests/SshSocksConnectorTest.php b/tests/SshSocksConnectorTest.php index 19d082c..2d00f16 100644 --- a/tests/SshSocksConnectorTest.php +++ b/tests/SshSocksConnectorTest.php @@ -2,7 +2,6 @@ namespace Clue\Tests\React\SshProxy; -use PHPUnit\Framework\TestCase; use Clue\React\SshProxy\SshSocksConnector; use React\Promise\Deferred; @@ -74,48 +73,42 @@ public function testConstructorAcceptsUriWithCustomBindUrlIpv6() $this->assertEquals('exec ssh -v -o ExitOnForwardFailure=yes -N -o BatchMode=yes -D \'[::1]:2711\' \'host\'', $ref->getValue($connector)); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidUri() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $this->setExpectedException('InvalidArgumentException'); new SshSocksConnector('///', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidUser() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshSocksConnector('-invalid@host', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidPass() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshSocksConnector('user:-invalid@host', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidHost() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshSocksConnector('-host', $loop); } - /** - * @expectedException InvalidArgumentException - */ public function testConstructorThrowsForInvalidBindHost() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $this->setExpectedException('InvalidArgumentException'); new SshSocksConnector('host?bind=example:1080', $loop); } @@ -137,10 +130,6 @@ public function testConnectReturnsRejectedPromiseForInvalidUri() $promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('InvalidArgumentException'))); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to google.com:80 cancelled - */ public function testConnectCancellationShouldReturnRejectedPromiseAndStartIdleTimer() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -155,6 +144,8 @@ public function testConnectCancellationShouldReturnRejectedPromiseAndStartIdleTi $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); + + $this->setExpectedException('RuntimeException', 'Connection to google.com:80 cancelled'); throw $exception; } @@ -198,10 +189,6 @@ public function testConnectWillCancelPendingIdleTimerAndWaitForSshListener() $connector->connect('google.com:80'); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection to google.com:80 failed because SSH client process died (foobar) - */ public function testConnectWithFailingSshListenerShouldReturnRejectedPromise() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -221,6 +208,8 @@ public function testConnectWithFailingSshListenerShouldReturnRejectedPromise() $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); + + $this->setExpectedException('RuntimeException', 'Connection to google.com:80 failed because SSH client process died (foobar)'); throw $exception; } @@ -283,10 +272,6 @@ public function testConnectWithSuccessfulSshListenerWillInvokeSocksConnector() $promise = $connector->connect('google.com:80'); } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage SOCKS cancelled - */ public function testConnectCancellationWithSuccessfulSshListenerWillCancelSocksConnectorAndStartIdleTimer() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -315,13 +300,11 @@ public function testConnectCancellationWithSuccessfulSshListenerWillCancelSocksC $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); + + $this->setExpectedException('RuntimeException', 'SOCKS cancelled'); throw $exception; } - /** - * @expectedException RuntimeException - * @expectedExceptionMessage Connection failed - */ public function testConnectWithSuccessfulSshListenerButFailingSocksConnectorShouldReturnRejectedPromiseAndStartIdleTimer() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -349,6 +332,8 @@ public function testConnectWithSuccessfulSshListenerButFailingSocksConnectorShou $promise->then(null, function ($reason) use (&$exception) { $exception = $reason; }); + + $this->setExpectedException('RuntimeException', 'Connection failed'); throw $exception; } @@ -404,21 +389,4 @@ public function testConnectWithSuccessfulSshListenerAndSuccessfulSocksConnectorW $connection->emit('close'); } - - protected function expectCallableOnceWith($value) - { - $mock = $this->createCallableMock(); - - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($value); - - return $mock; - } - - protected function createCallableMock() - { - return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); - } } diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..a426479 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,82 @@ +createCallableMock(); + $mock->expects($this->once())->method('__invoke'); + + return $mock; + } + + protected function expectCallableOnceWith($value) + { + $mock = $this->createCallableMock(); + $mock->expects($this->once())->method('__invoke') ->with($value); + + return $mock; + } + + protected function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock->expects($this->never())->method('__invoke'); + + return $mock; + } + + protected function createCallableMock() + { + if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + // PHPUnit 8.5+ + return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock(); + } else { + // legacy PHPUnit 4 - PHPUnit 8.4 + return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); + } + } + + public function assertContainsString($needle, $haystack) + { + if (method_exists($this, 'assertStringContainsString')) { + // PHPUnit 7.5+ + $this->assertStringContainsString($needle, $haystack); + } else { + // legacy PHPUnit 4 - PHPUnit 7.4 + $this->assertContains($needle, $haystack); + } + } + + public function assertNotContainsString($needle, $haystack) + { + if (method_exists($this, 'assertStringNotContainsString')) { + // PHPUnit 7.5+ + $this->assertStringNotContainsString($needle, $haystack); + } else { + // legacy PHPUnit 4 - PHPUnit 7.4 + $this->assertNotContains($needle, $haystack); + } + } + + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5.2+ + $this->expectException($exception); + if ($exceptionMessage !== '') { + $this->expectExceptionMessage($exceptionMessage); + } + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit 4 - PHPUnit 5.1 + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } +}