diff --git a/.travis.yml b/.travis.yml index 3464291..9023933 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,25 @@ language: php php: - - 5.3 +# - 5.3 # requires old distro - 5.4 - 5.5 - 5.6 - - 7 - - hhvm + - 7.0 + - 7.1 + - 7.2 + - 7.3 -sudo: false +# lock distro so new future defaults will not break the build +dist: trusty + +matrix: + include: + - php: 5.3 + dist: precise install: - composer install --no-interaction script: - - phpunit --coverage-text + - vendor/bin/phpunit --coverage-text diff --git a/README.md b/README.md index 28a137e..736a6e0 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ execute arbitrary commands within isolated containers, stop running containers a * [JSON streaming](#json-streaming) * [JsonProgressException](#jsonprogressexception) * [Install](#install) +* [Tests](#tests) * [License](#license) ## Quickstart example @@ -389,6 +390,25 @@ The recommended way to install this library is [through composer](http://getcomp } ``` +This project aims to run on any platform and thus does not require any PHP +extensions and supports running on legacy PHP 5.3 through current PHP 7+. +It's *highly recommended to use PHP 7+* for this project. + +## Tests + +To run the test suite, you first need to clone this repo and then install all +dependencies [through Composer](https://getcomposer.org): + +```bash +$ composer install +``` + +To run the test suite, go to the project root and run: + +```bash +$ php vendor/bin/phpunit +``` + ## License MIT diff --git a/composer.json b/composer.json index a1f5940..85f1f1d 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,9 @@ "autoload": { "psr-4": { "Clue\\React\\Docker\\": "src/" } }, + "autoload-dev": { + "psr-4": { "Clue\\Tests\\React\\Docker\\": "tests/" } + }, "require": { "php": ">=5.3", "react/event-loop": "~0.3.0|~0.4.0", @@ -23,8 +26,9 @@ "clue/promise-stream-react": "^0.1" }, "require-dev": { - "clue/tar-react": "~0.1.0", + "clue/block-react": "~0.3.0", "clue/caret-notation": "~0.2.0", - "clue/block-react": "~0.3.0" + "clue/tar-react": "~0.1.0", + "phpunit/phpunit": "^7.0 || ^6.0 || ^5.0 || ^4.8.35" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index afaeae9..f737b41 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ -loop = $this->getMock('React\EventLoop\LoopInterface'); + $this->loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $this->sender = $this->getMockBuilder('Clue\React\Buzz\Io\Sender')->disableOriginalConstructor()->getMock(); $this->browser = new Browser($this->loop, $this->sender); $this->browser = $this->browser->withBase('http://x/'); - $this->parser = $this->getMock('Clue\React\Docker\Io\ResponseParser'); - $this->streamingParser = $this->getMock('Clue\React\Docker\Io\StreamingParser'); + $this->parser = $this->getMockBuilder('Clue\React\Docker\Io\ResponseParser')->getMock(); + $this->streamingParser = $this->getMockBuilder('Clue\React\Docker\Io\StreamingParser')->getMock(); $this->client = new Client($this->browser, $this->parser, $this->streamingParser); } @@ -41,7 +43,7 @@ public function testPing() public function testEvents() { $json = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('GET', '/events', $this->createResponseJsonStream($json)); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -53,7 +55,7 @@ public function testEvents() public function testEventsArgs() { $json = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('GET', '/events?since=10&until=20&filters=%7B%22image%22%3A%5B%22busybox%22%2C%22ubuntu%22%5D%7D', $this->createResponseJsonStream($json)); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -122,7 +124,7 @@ public function testContainerExport() public function testContainerExportStream() { - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('get', '/containers/123/export', $this->createResponse('')); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); @@ -248,7 +250,7 @@ public function testContainerCopy() public function testContainerCopyStream() { - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/containers/123/copy', $this->createResponse('')); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); @@ -276,7 +278,7 @@ public function testImageListAll() public function testImageCreate() { $json = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/images/create?fromImage=busybox', $this->createResponseJsonStream($json)); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -287,7 +289,7 @@ public function testImageCreate() public function testImageCreateStream() { - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/images/create?fromImage=busybox', $this->createResponseJsonStream(array())); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -314,7 +316,7 @@ public function testImageHistory() public function testImagePush() { $json = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/images/123/push', $this->createResponseJsonStream($json)); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -325,7 +327,7 @@ public function testImagePush() public function testImagePushStream() { - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/images/123/push', $this->createResponseJsonStream(array())); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -338,7 +340,7 @@ public function testImagePushCustomRegistry() // TODO: verify headers $auth = array(); $json = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('post', '/images/demo.acme.com%3A5000/123/push?tag=test', $this->createResponseJsonStream($json)); $this->streamingParser->expects($this->once())->method('parseJsonStream')->will($this->returnValue($stream)); @@ -411,7 +413,7 @@ public function testExecStart() { $data = 'hello world'; $config = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('POST', '/exec/123/start', $this->createResponse($data)); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); @@ -424,7 +426,7 @@ public function testExecStart() public function testExecStartStreamWithoutTtyWillDemultiplex() { $config = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('POST', '/exec/123/start', $this->createResponse()); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); @@ -436,7 +438,7 @@ public function testExecStartStreamWithoutTtyWillDemultiplex() public function testExecStartStreamWithTtyWillNotDemultiplex() { $config = array('Tty' => true); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('POST', '/exec/123/start', $this->createResponse()); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); @@ -448,7 +450,7 @@ public function testExecStartStreamWithTtyWillNotDemultiplex() public function testExecStartStreamWithCustomStderrEvent() { $config = array(); - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->expectRequest('POST', '/exec/123/start', $this->createResponse()); $this->streamingParser->expects($this->once())->method('parsePlainStream')->will($this->returnValue($stream)); diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 5709e75..62b7e74 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -1,5 +1,7 @@ factory = new Factory($this->loop, $this->browser); } + /** + * @doesNotPerformAssertions + */ public function testCtorDefaultBrowser() { - $factory = new Factory($this->loop); + new Factory($this->loop); } public function testCreateClientUsesCustomUnixSender() diff --git a/tests/FunctionalClientTest.php b/tests/FunctionalClientTest.php index 4ea8147..1a3e71f 100644 --- a/tests/FunctionalClientTest.php +++ b/tests/FunctionalClientTest.php @@ -1,5 +1,7 @@ loop); - } catch (Exception $e) { + } catch (\Exception $e) { $this->markTestSkipped('Unable to connect to docker ' . $e->getMessage()); } } @@ -34,6 +36,23 @@ public function testPing() $this->loop->run(); } + /** + * @doesNotPerformAssertions + */ + public function testImageInspectCheckIfBusyboxExists() + { + $promise = $this->client->imageInspect('busybox:latest'); + + try { + Block\await($promise, $this->loop); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Image "busybox" not downloaded yet'); + } + } + + /** + * @depends testImageInspectCheckIfBusyboxExists + */ public function testCreateStartAndRemoveContainer() { $config = array( @@ -73,6 +92,9 @@ public function testCreateStartAndRemoveContainer() $this->assertEquals('destroy', $ret[3]['status']); } + /** + * @depends testImageInspectCheckIfBusyboxExists + */ public function testStartRunning() { $config = array( @@ -87,8 +109,6 @@ public function testStartRunning() $this->assertNotNull($container['Id']); $this->assertNull($container['Warnings']); - $start = microtime(true); - $promise = $this->client->containerStart($container['Id']); $ret = Block\await($promise, $this->loop); @@ -177,7 +197,7 @@ public function testExecStringCommandWithOutputWhileRunning($container) */ public function testExecStreamOutputInMultipleChunksWhileRunning($container) { - $promise = $this->client->execCreate($container, 'echo -n hello && sleep 0 && echo -n world'); + $promise = $this->client->execCreate($container, 'echo -n hello && sleep 0.2 && echo -n world'); $exec = Block\await($promise, $this->loop); $this->assertTrue(is_array($exec)); @@ -339,27 +359,40 @@ public function testImageSearch() $this->assertGreaterThan(9, count($ret)); } + /** + * @depends testImageInspectCheckIfBusyboxExists + * @doesNotPerformAssertions + */ public function testImageTag() { // create new tag "bb:now" on "busybox:latest" $promise = $this->client->imageTag('busybox', 'bb', 'now'); - $ret = Block\await($promise, $this->loop); + Block\await($promise, $this->loop); // delete tag "bb:now" again $promise = $this->client->imageRemove('bb:now'); - $ret = Block\await($promise, $this->loop); + Block\await($promise, $this->loop); } public function testImageCreateStreamMissingWillEmitJsonError() { + $promise = $this->client->version(); + $version = Block\await($promise, $this->loop); + + // old API reports a progress with error message, newer API just returns 404 right away + // https://docs.docker.com/engine/api/version-history/ + $old = $version['ApiVersion'] < '1.22'; + $stream = $this->client->imageCreateStream('clue/does-not-exist'); // one "progress" event, but no "data" events - $stream->on('progress', $this->expectCallableOnce()); + $old && $stream->on('progress', $this->expectCallableOnce()); + $old || $stream->on('progress', $this->expectCallableNever()); $stream->on('data', $this->expectCallableNever()); // will emit "error" with JsonProgressException and close - $stream->on('error', $this->expectCallableOnceParameter('Clue\React\Docker\Io\JsonProgressException')); + $old && $stream->on('error', $this->expectCallableOnceParameter('Clue\React\Docker\Io\JsonProgressException')); + $old || $stream->on('error', $this->expectCallableOnceParameter('Clue\React\Buzz\Message\ResponseException')); $stream->on('close', $this->expectCallableOnce()); $this->loop->run(); diff --git a/tests/Io/ReadableDemultiplexStreamTest.php b/tests/Io/ReadableDemultiplexStreamTest.php index e9f549b..cf8c9bd 100644 --- a/tests/Io/ReadableDemultiplexStreamTest.php +++ b/tests/Io/ReadableDemultiplexStreamTest.php @@ -1,7 +1,10 @@ stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $this->stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->stream->expects($this->once())->method('pause'); $this->parser = new ReadableDemultiplexStream($this->stream); @@ -98,7 +101,7 @@ public function testPauseWillBeForwarded() public function testResumeWillBeForwarded() { - $this->stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $this->stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $this->stream->expects($this->once())->method('resume'); $this->parser = new ReadableDemultiplexStream($this->stream); diff --git a/tests/Io/ResponseParserTest.php b/tests/Io/ResponseParserTest.php index a366ab4..4e01cd2 100644 --- a/tests/Io/ResponseParserTest.php +++ b/tests/Io/ResponseParserTest.php @@ -1,6 +1,9 @@ assertTrue($stream->isReadable()); - $exception = new RuntimeException(); + $exception = new \RuntimeException(); $stream->on('error', $this->expectCallableOnceWith($exception)); $stream->on('close', $this->expectCallableOnce()); @@ -67,7 +70,7 @@ public function testPlainPassingRejectedPromiseResolvesWithClosedStream() public function testDeferredClosedStreamWillReject() { - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); $stream->expects($this->once())->method('isReadable')->will($this->returnValue(false)); $promise = $this->parser->deferredStream($stream, 'anything'); @@ -109,9 +112,8 @@ public function testDeferredStreamErrorEventWillRejectPromise() public function testDeferredCancelingPromiseWillCloseStream() { - $this->markTestIncomplete(); - - $stream = $this->getMock('React\Stream\ReadableStreamInterface'); + $stream = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock(); + $stream->expects($this->once())->method('isReadable')->willReturn(true); $promise = $this->parser->deferredStream($stream, 'anything'); if (!($promise instanceof CancellablePromiseInterface)) { diff --git a/tests/bootstrap.php b/tests/TestCase.php similarity index 71% rename from tests/bootstrap.php rename to tests/TestCase.php index 5edb82a..ccf774f 100644 --- a/tests/bootstrap.php +++ b/tests/TestCase.php @@ -1,14 +1,12 @@ expects($this->once()) ->method('__invoke') - ->with($this->equalTo($value)); + ->with($value); return $mock; } @@ -45,21 +43,12 @@ protected function expectCallableNever() protected function expectCallableOnceParameter($type) { - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->isInstanceOf($type)); - - return $mock; + return $this->expectCallableOnceWith($this->isInstanceOf($type)); } - /** - * @link https://github.com/reactphp/react/blob/master/tests/React/Tests/Socket/TestCase.php (taken from reactphp/react) - */ protected function createCallableMock() { - return $this->getMock('CallableStub'); + return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock(); } protected function expectPromiseResolve($promise) @@ -85,11 +74,3 @@ protected function expectPromiseReject($promise) return $promise; } } - -class CallableStub -{ - public function __invoke() - { - } -} -