diff --git a/.gitattributes b/.gitattributes index a0650eb..9426ba0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,7 +2,7 @@ /.* export-ignore /tests export-ignore -/phpunit.xml* export-ignore +/testo.php export-ignore /psalm.* export-ignore /psalm-baseline.xml export-ignore /infection.* export-ignore diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml deleted file mode 100644 index 3b86822..0000000 --- a/.github/workflows/phpunit.yml +++ /dev/null @@ -1,16 +0,0 @@ -on: - pull_request: null - push: - branches: - - '*.*' - -name: phpunit - -jobs: - phpunit: - uses: spiral/gh-actions/.github/workflows/phpunit.yml@master - with: - os: >- - ['ubuntu-latest'] - stability: >- - ['prefer-lowest', 'prefer-stable'] diff --git a/.github/workflows/testo.yml b/.github/workflows/testo.yml new file mode 100644 index 0000000..6fb3b4a --- /dev/null +++ b/.github/workflows/testo.yml @@ -0,0 +1,16 @@ +on: + pull_request: null + push: + branches: + - 4.x + +name: testo + +jobs: + testo: + uses: spiral/gh-actions/.github/workflows/testo.yml@master + with: + os: >- + ["ubuntu-latest"] + dependencies: >- + ["lowest","highest"] diff --git a/README.md b/README.md index 8cb8559..fc95925 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ vendor/bin/rr get Ensure that your server is configured with the following PHP versions and extensions: -- PHP >=8.1 +- PHP >=8.2 - ext-protobuf: This extension is optional but **highly recommended for installation**. Without it, performance may be up to 50% lower. - RoadRunner ^3.0 diff --git a/composer.json b/composer.json index 4bc0db2..cfcd3bb 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "chat": "https://discord.gg/V6EK4he" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-json": "*", "psr/http-factory": "^1.0.1", "psr/http-message": "^1.0.1 || ^2.0", @@ -48,12 +48,14 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.0", + "mockery/mockery": "^1.6.12", "nyholm/psr7": "^1.3", - "phpunit/phpunit": "^10.5", "spiral/code-style": "^2.3.1", "spiral/dumper": "^3.3", "symfony/process": "^6.2 || ^7.0", - "vimeo/psalm": "^6.13" + "testo/bridge-mockery": "^0.1.2", + "testo/testo": "^0.10.55", + "vimeo/psalm": "^6 || ^7" }, "autoload": { "psr-4": { @@ -91,10 +93,12 @@ "psalm": "psalm", "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", "psalm:ci": "psalm --output-format=github --shepherd --show-info=false --stats --threads=4", - "test": "phpunit --color=always --testdox", + "test": "testo", "test:cc": [ "@putenv XDEBUG_MODE=coverage", - "phpunit --coverage-clover=runtime/phpunit/logs/clover.xml --color=always" - ] + "testo --coverage --coverage-clover=runtime/logs/clover.xml" + ], + "test:unit": "testo --suite=Unit", + "test:feature": "testo --suite=Feature" } } diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 43def94..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - tests - - - - - - - - - - - - - - - src - - - tests - - - diff --git a/testo.php b/testo.php new file mode 100644 index 0000000..5c390be --- /dev/null +++ b/testo.php @@ -0,0 +1,24 @@ +respond(new Payload('Hello, World!')); \usleep(100_000); - self::assertSame('Hello, World!', \trim(ServerRunner::getBuffer())); + Assert::same(\trim(ServerRunner::getBuffer()), 'Hello, World!'); } /** @@ -50,7 +55,7 @@ public function testStreamResponseWithMultipleFrames(): void ); \usleep(100_000); - self::assertSame(\implode("\n", $chunks), \trim(ServerRunner::getBuffer())); + Assert::same(\trim(ServerRunner::getBuffer()), \implode("\n", $chunks)); } public function testStopStreamResponse(): void @@ -78,14 +83,14 @@ public function testStopStreamResponse(): void \usleep(100_000); - self::assertSame(\implode("\n", ['Hel', 'lo,']), \trim(ServerRunner::getBuffer())); + Assert::same(\trim(ServerRunner::getBuffer()), \implode("\n", ['Hel', 'lo,'])); } public function testSend1xxWithBody(): void { $httpWorker = $this->makeHttpWorker(); - $this->expectExceptionMessage('Unable to send a body with informational status code'); + Expect::exception(\Throwable::class)->withMessageContaining('Unable to send a body with informational status code'); $httpWorker->respond( 103, @@ -114,7 +119,7 @@ public function testExceptionInGenerator(): void \usleep(100_000); - self::assertSame(\implode("\n", ['Hel', 'lo,']), \trim(ServerRunner::getBuffer())); + Assert::same(\trim(ServerRunner::getBuffer()), \implode("\n", ['Hel', 'lo,'])); } /** @@ -136,30 +141,30 @@ public function testStopStreamAfterStreamEnd(): void })(), ); - $this->assertFalse($this->getWorker()->hasPayload(\Spiral\RoadRunner\Message\Command\StreamStop::class)); + Assert::false($this->getWorker()->hasPayload(\Spiral\RoadRunner\Message\Command\StreamStop::class)); $this->sendCommand(new StreamStop()); \usleep(200_000); - $this->assertSame(\implode("\n", ['Hello', 'World!']), \trim(ServerRunner::getBuffer())); - $this->assertTrue($this->getWorker()->hasPayload(\Spiral\RoadRunner\Message\Command\StreamStop::class)); + Assert::same(\trim(ServerRunner::getBuffer()), \implode("\n", ['Hello', 'World!'])); + Assert::true($this->getWorker()->hasPayload(\Spiral\RoadRunner\Message\Command\StreamStop::class)); $this->getWorker()->getPayload(\Spiral\RoadRunner\Message\Command\StreamStop::class); $this->getWorker()->getPayload(GetProcessId::class); - $this->assertFalse($this->getWorker()->hasPayload()); + Assert::false($this->getWorker()->hasPayload()); } + #[BeforeTest] protected function setUp(): void { - parent::setUp(); ServerRunner::start(); ServerRunner::getBuffer(); } + #[AfterTest] protected function tearDown(): void { unset($this->relay, $this->worker); ServerRunner::stop(); - parent::tearDown(); } private function getRelay(): SocketRelay @@ -177,7 +182,7 @@ private function makeHttpWorker(): HttpWorker return new HttpWorker($this->getWorker()); } - private function sendCommand(BaseCommand $command) + private function sendCommand(BaseCommand $command): void { $this->getRelay()->send($command->getRequestFrame()); \usleep(500_000); diff --git a/tests/Unit/GlobalStateTest.php b/tests/Unit/GlobalStateTest.php index 5ca36d5..e3d4941 100644 --- a/tests/Unit/GlobalStateTest.php +++ b/tests/Unit/GlobalStateTest.php @@ -4,12 +4,14 @@ namespace Spiral\RoadRunner\Tests\Http\Unit; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; use Spiral\RoadRunner\Http\GlobalState; use Spiral\RoadRunner\Http\Request; +use Testo\Assert; +use Testo\Data\DataProvider; +use Testo\Test; -final class GlobalStateTest extends TestCase +#[Test] +final class GlobalStateTest { /** * @return iterable}> @@ -60,12 +62,11 @@ public function testEnrichServerVars(Request $request, array $expected): void $server = GlobalState::enrichServerVars($request); - $this->assertArrayHasKey('REQUEST_TIME', $server); - $this->assertArrayHasKey('REQUEST_TIME_FLOAT', $server); + Assert::array($server)->hasKeys('REQUEST_TIME', 'REQUEST_TIME_FLOAT'); unset($server['REQUEST_TIME']); unset($server['REQUEST_TIME_FLOAT']); - $this->assertEquals($server, $expected); + Assert::equals($server, $expected); } } diff --git a/tests/Unit/HttpWorkerTest.php b/tests/Unit/HttpWorkerTest.php index cbbb603..e951d2e 100644 --- a/tests/Unit/HttpWorkerTest.php +++ b/tests/Unit/HttpWorkerTest.php @@ -4,8 +4,6 @@ namespace Spiral\RoadRunner\Tests\Http\Unit; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; use RoadRunner\HTTP\DTO\V1\HeaderValue; use RoadRunner\HTTP\DTO\V1\Response; use Spiral\Goridge\Frame; @@ -13,8 +11,14 @@ use Spiral\RoadRunner\Http\Request; use Spiral\RoadRunner\Payload; use Spiral\RoadRunner\WorkerInterface; - -final class HttpWorkerTest extends TestCase +use Testo\Assert; +use Testo\Data\DataProvider; +use Testo\Expect; +use Testo\Lifecycle\AfterTest; +use Testo\Test; + +#[Test] +final class HttpWorkerTest { private const REQUIRED_PAYLOAD_DATA = [ 'rawQuery' => 'first=value&arr[]=foo+bar&arr[]=baz', @@ -198,14 +202,12 @@ public static function headersDataProvider(): \Traversable #[DataProvider('requestDataProvider')] public function testWaitRequestFromArray(array $header, array $expected): void { - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('waitPayload') - ->willReturn(new Payload('foo', \json_encode($header))); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('waitPayload')->once()->andReturn(new Payload('foo', \json_encode($header))); $worker = new HttpWorker($worker); - $this->assertEquals(new Request(...$expected), $worker->waitRequest()); + Assert::equals($worker->waitRequest(), new Request(...$expected)); } #[DataProvider('requestDataProvider')] @@ -213,50 +215,43 @@ public function testWaitRequestFromProto(array $header, array $expected): void { $request = self::createProtoRequest($header); - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('waitPayload') - ->willReturn(new Payload('foo', $request->serializeToString())); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('waitPayload')->once()->andReturn(new Payload('foo', $request->serializeToString())); $worker = new HttpWorker($worker); - $this->assertEquals(new Request(...$expected), $worker->waitRequest()); + Assert::equals($worker->waitRequest(), new Request(...$expected)); } #[DataProvider('emptyRequestDataProvider')] public function testWaitRequestWithEmptyData(?Payload $payload): void { - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('waitPayload') - ->willReturn($payload); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('waitPayload')->once()->andReturn($payload); $worker = new HttpWorker($worker); - $this->assertEquals(null, $worker->waitRequest()); + Assert::equals($worker->waitRequest(), null); } public function testEmptyBodyShouldBeConvertedIntoEmptyArrayWithParsedTrue(): void { $request = self::createProtoRequest(\array_merge(self::REQUIRED_PAYLOAD_DATA, ['parsed' => true])); - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('waitPayload') - ->willReturn(new Payload('', $request->serializeToString())); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('waitPayload')->once()->andReturn(new Payload('', $request->serializeToString())); $worker = new HttpWorker($worker); $request = $worker->waitRequest(); - $this->assertSame([], $request->getParsedBody()); + Assert::same($request->getParsedBody(), []); } public function testRespondUnableToSendBodyWithInfoStatusException(): void { - $worker = new HttpWorker($this->createMock(WorkerInterface::class)); + $worker = new HttpWorker(\Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing()); - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to send a body with informational status code.'); + Expect::exception(\InvalidArgumentException::class)->withMessageContaining('Unable to send a body with informational status code.'); $worker->respond(100, 'foo'); } @@ -267,10 +262,8 @@ public function testRespondWithProtoCodec(): void 'headers' => ['Content-Type' => new HeaderValue(['value' => ['application/x-www-form-urlencoded']])], ]); - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('respond') - ->with(new Payload('foo', $expectedHeader->serializeToString()), Frame::CODEC_PROTO); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('respond')->once()->with(\Mockery::isEqual(new Payload('foo', $expectedHeader->serializeToString())), Frame::CODEC_PROTO, \Mockery::andAnyOtherArgs()); (new \ReflectionProperty(HttpWorker::class, 'codec'))->setValue(Frame::CODEC_PROTO); $worker = new HttpWorker($worker); @@ -283,10 +276,8 @@ public function testRespondWithProtoCodecWithHeaders(array $headers, array $expe { $expectedHeader = new Response(['status' => 200, 'headers' => $expected]); - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('respond') - ->with(new Payload('foo', $expectedHeader->serializeToString()), Frame::CODEC_PROTO); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('respond')->once()->with(\Mockery::isEqual(new Payload('foo', $expectedHeader->serializeToString())), Frame::CODEC_PROTO, \Mockery::andAnyOtherArgs()); (new \ReflectionProperty(HttpWorker::class, 'codec'))->setValue(Frame::CODEC_PROTO); $worker = new HttpWorker($worker); @@ -296,13 +287,11 @@ public function testRespondWithProtoCodecWithHeaders(array $headers, array $expe public function testRespondWithJsonCodec(): void { - $worker = $this->createMock(WorkerInterface::class); - $worker->expects($this->once()) - ->method('respond') - ->with(new Payload('foo', \json_encode([ - 'status' => 200, - 'headers' => ['Content-Type' => ['application/x-www-form-urlencoded']], - ])), Frame::CODEC_JSON); + $worker = \Mockery::mock(WorkerInterface::class)->shouldIgnoreMissing(); + $worker->shouldReceive('respond')->once()->with(\Mockery::isEqual(new Payload('foo', \json_encode([ + 'status' => 200, + 'headers' => ['Content-Type' => ['application/x-www-form-urlencoded']], + ]))), Frame::CODEC_JSON, \Mockery::andAnyOtherArgs()); (new \ReflectionProperty(HttpWorker::class, 'codec'))->setValue(Frame::CODEC_JSON); $worker = new HttpWorker($worker); @@ -310,6 +299,7 @@ public function testRespondWithJsonCodec(): void $worker->respond(200, 'foo', ['Content-Type' => ['application/x-www-form-urlencoded']]); } + #[AfterTest] protected function tearDown(): void { (new \ReflectionProperty(HttpWorker::class, 'codec'))->setValue(null); diff --git a/tests/Unit/PSR7WorkerTest.php b/tests/Unit/PSR7WorkerTest.php index 52f8f7d..c0a60f4 100644 --- a/tests/Unit/PSR7WorkerTest.php +++ b/tests/Unit/PSR7WorkerTest.php @@ -5,19 +5,20 @@ namespace Spiral\RoadRunner\Tests\Http\Unit; use Nyholm\Psr7\Factory\Psr17Factory; -use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\RunClassInSeparateProcess; -use PHPUnit\Framework\TestCase; use Spiral\Goridge\Frame; use Spiral\RoadRunner\Http\GlobalState; use Spiral\RoadRunner\Http\PSR7Worker; use Spiral\RoadRunner\Tests\Http\Unit\Stub\TestRelay; use Spiral\RoadRunner\Worker; +use Testo\Assert; +use Testo\Codecov\Covers; +use Testo\Lifecycle\AfterTest; +use Testo\Test; -#[CoversClass(PSR7Worker::class)] -#[CoversClass(GlobalState::class)] -#[RunClassInSeparateProcess] -final class PSR7WorkerTest extends TestCase +#[Covers(PSR7Worker::class)] +#[Covers(GlobalState::class)] +#[Test] +final class PSR7WorkerTest { public function testStateServerLeak(): void { @@ -84,10 +85,11 @@ public function testStateServerLeak(): void unset($_SERVER['REQUEST_TIME']); unset($_SERVER['REQUEST_TIME_FLOAT']); - self::assertEquals($expectedServer, $_SERVER); + Assert::equals($_SERVER, $expectedServer); } } + #[AfterTest] protected function tearDown(): void { // Clean all extra output buffers diff --git a/tests/Unit/StreamResponseTest.php b/tests/Unit/StreamResponseTest.php index fd73172..5fe834f 100644 --- a/tests/Unit/StreamResponseTest.php +++ b/tests/Unit/StreamResponseTest.php @@ -4,13 +4,16 @@ namespace Spiral\RoadRunner\Tests\Http\Unit; -use PHPUnit\Framework\TestCase; use Spiral\RoadRunner\Http\HttpWorker; use Spiral\RoadRunner\Payload; use Spiral\RoadRunner\Tests\Http\Unit\Stub\TestRelay; use Spiral\RoadRunner\Worker; +use Testo\Assert; +use Testo\Lifecycle\AfterTest; +use Testo\Test; -final class StreamResponseTest extends TestCase +#[Test] +final class StreamResponseTest { private TestRelay $relay; private Worker $worker; @@ -24,9 +27,9 @@ public function testRegularCase(): void $this->getRelay() ->addFrame(status: 200, body: 'Hello, World!', headers: ['Content-Type' => 'text/plain'], stream: true); - self::assertTrue($worker->hasPayload()); - self::assertInstanceOf(Payload::class, $payload = $worker->waitPayload()); - self::assertSame('Hello, World!', $payload->body); + Assert::true($worker->hasPayload()); + Assert::instanceOf($payload = $worker->waitPayload(), Payload::class); + Assert::same($payload->body, 'Hello, World!'); } /** @@ -44,8 +47,8 @@ public function testStreamResponseWithMultipleFrames(): void yield '!'; })()); - self::assertFalse($this->worker->hasPayload()); - self::assertSame('Hello, World!', $this->getRelay()->getReceivedBody()); + Assert::false($this->worker->hasPayload()); + Assert::same($this->getRelay()->getReceivedBody(), 'Hello, World!'); } public function testStopStreamResponse(): void @@ -65,9 +68,10 @@ public function testStopStreamResponse(): void yield '!'; })()); - self::assertSame('Hello,', $this->getRelay()->getReceivedBody()); + Assert::same($this->getRelay()->getReceivedBody(), 'Hello,'); } + #[AfterTest] protected function tearDown(): void { unset($this->relay, $this->worker);