Skip to content

Commit b6fb675

Browse files
committed
fix mutant tests
1 parent 34c50f3 commit b6fb675

File tree

9 files changed

+191
-20
lines changed

9 files changed

+191
-20
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"require": {
1717
"php": "^7.4|^8.0",
1818
"antidot-fw/framework": "^0.1.3",
19+
"beberlei/assert": "^3.3",
1920
"psr/container": "^1.0.0",
2021
"ramsey/uuid": "^4.1",
2122
"react/http": "^1.2"

src/Child.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
11
<?php
22

3+
declare(strict_types=1);
34

45
namespace Antidot\React;
56

7+
use RuntimeException;
8+
use function pcntl_fork;
9+
use function pcntl_waitpid;
10+
611
class Child
712
{
8-
public static function fork(int $numberOfWorkers, callable $asyncServer, int $numberOfFork = 0): void
9-
{
13+
private const DEFAULT_NUMBER_OF_FORKS = 0;
14+
15+
public static function fork(
16+
int $numberOfWorkers,
17+
callable $asyncServer,
18+
int $numberOfFork = self::DEFAULT_NUMBER_OF_FORKS
19+
): int {
20+
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
21+
throw new RuntimeException('The PHP pcntl extension is not available for Windows systems');
22+
}
23+
1024
$pid = pcntl_fork();
1125
if (-1 === $pid) {
12-
// @fail
13-
die('Fork failed');
26+
throw new RuntimeException('Fork Failed');
1427
}
1528

1629
if (0 === $pid) {
1730
$asyncServer();
1831
pcntl_waitpid($pid, $status);
19-
return;
32+
return $pid;
2033
}
2134

2235
// @parent
2336
$numberOfWorkers--;
2437
++$numberOfFork;
25-
if ($numberOfWorkers > 0) {
38+
if (self::DEFAULT_NUMBER_OF_FORKS < $numberOfWorkers) {
2639
self::fork($numberOfWorkers, $asyncServer, $numberOfFork);
2740
}
2841

2942
pcntl_waitpid($pid, $status);
43+
return $pid;
3044
}
3145
}

src/Container/Config/ConfigProvider.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313

1414
class ConfigProvider
1515
{
16+
private const DEFAULT_HOST = '0.0.0.0';
17+
private const DEFAULT_PORT = 8080;
18+
private const DEFAULT_CONCURRENCY = 100;
19+
private const DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024;
20+
1621
public function __invoke(): array
1722
{
1823
return [
@@ -25,7 +30,11 @@ public function __invoke(): array
2530
],
2631
],
2732
'server' => [
28-
'workers' => 1
33+
'workers' => 1,
34+
'host' => self::DEFAULT_HOST,
35+
'port' => self::DEFAULT_PORT,
36+
'max_concurrency' => self::DEFAULT_CONCURRENCY,
37+
'buffer_size' => self::DEFAULT_BUFFER_SIZE,
2938
]
3039
];
3140
}

src/ServerFactory.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Antidot\React;
66

77
use Antidot\Application\Http\Application;
8+
use Assert\Assertion;
89
use Psr\Container\ContainerInterface;
910
use Psr\Http\Message\ServerRequestInterface;
1011
use React\EventLoop\LoopInterface;
@@ -14,26 +15,32 @@
1415
use React\Http\Middleware\RequestBodyParserMiddleware;
1516
use React\Http\Middleware\StreamingRequestMiddleware;
1617
use React\Promise\PromiseInterface;
18+
use function PHPUnit\Framework\assertArrayHasKey;
19+
use function PHPUnit\Framework\assertIsInt;
1720
use function React\Promise\resolve;
1821

1922
class ServerFactory
2023
{
2124
public function __invoke(ContainerInterface $container): Server
2225
{
2326
$application = $container->get(Application::class);
24-
assert($application instanceof ReactApplication);
27+
Assertion::isInstanceOf($application, ReactApplication::class);
2528
/** @var LoopInterface $loop */
2629
$loop = $container->get(LoopInterface::class);
2730
/** @var array<string, array> $globalConfig */
2831
$globalConfig = $container->get('config');
2932
/** @var array<string, int|null> $config */
3033
$config = $globalConfig['server'];
34+
Assertion::keyExists($config, 'max_concurrency');
35+
Assertion::keyExists($config, 'buffer_size');
36+
Assertion::integer($config['max_concurrency']);
37+
Assertion::integer($config['buffer_size']);
3138

3239
$server = new Server(
3340
$loop,
3441
new StreamingRequestMiddleware(),
35-
new LimitConcurrentRequestsMiddleware(($config['max_concurrency']) ?? 100),
36-
new RequestBodyBufferMiddleware($config['buffer_size'] ?? 4 * 1024 * 1024), // 4 MiB
42+
new LimitConcurrentRequestsMiddleware($config['max_concurrency']),
43+
new RequestBodyBufferMiddleware($config['buffer_size']),
3744
new RequestBodyParserMiddleware(),
3845
static fn (ServerRequestInterface $request): PromiseInterface => resolve($application->handle($request))
3946
);

src/SocketFactory.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44

55
namespace Antidot\React;
66

7+
use Assert\Assertion;
78
use Psr\Container\ContainerInterface;
89
use React\EventLoop\LoopInterface;
910
use React\Socket\Server as Socket;
1011

1112
class SocketFactory
1213
{
14+
private const DEFAULT_TCP_CONFIG = ['tcp' => ['so_reuseport' => false]];
15+
private const REUSE_PORT = true;
16+
private const DEFAULT_WORKERS_NUMBER = 1;
17+
1318
public function __invoke(ContainerInterface $container): Socket
1419
{
1520
/** @var LoopInterface $loop */
@@ -18,11 +23,28 @@ public function __invoke(ContainerInterface $container): Socket
1823
$globalConfig = $container->get('config');
1924
/** @var array<string, string|null> $config */
2025
$config = $globalConfig['server'];
26+
Assertion::notEmptyKey($config, 'host');
27+
Assertion::notEmptyKey($config, 'port');
28+
Assertion::keyExists($config, 'workers');
29+
/** @var string $host */
30+
$host = $config['host'];
31+
Assertion::ipv4($host);
32+
/** @var int $port */
33+
$port = $config['port'];
34+
Assertion::integer($port);
35+
/** @var int $workersNumber */
36+
$workersNumber = $config['workers'];
37+
Assertion::integer($workersNumber);
38+
$tcpConfig = self::DEFAULT_TCP_CONFIG;
39+
if ($this->needMoreThanOne($workersNumber)) {
40+
$tcpConfig['tcp']['so_reuseport'] = self::REUSE_PORT;
41+
}
42+
43+
return new Socket(sprintf('%s:%s', $host, $port), $loop, $tcpConfig);
44+
}
2145

22-
return new Socket(
23-
sprintf('%s:%s', $config['host'] ?? '0.0.0.0', $config['port'] ?? '8080'),
24-
$loop,
25-
['tcp' => ['so_reuseport' => 2 <= $config['workers']]]
26-
);
46+
private function needMoreThanOne(int $workersNumber): bool
47+
{
48+
return self::DEFAULT_WORKERS_NUMBER < $workersNumber;
2749
}
2850
}

test/ChildTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AntidotTest\React;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Symfony\Component\Process\Process;
9+
10+
class ChildTest extends TestCase
11+
{
12+
public function testItShouldBeConstructedStatically(): void
13+
{
14+
$process = new Process([
15+
'php',
16+
'-r',
17+
'include "src/Child.php";\Antidot\React\Child::fork(1, function() { echo "test passed"; }, 0);'
18+
]);
19+
$process->start();
20+
$process->wait();
21+
$this->assertSame('test passed', $process->getOutput());
22+
}
23+
}

test/Container/Config/ConfigProviderTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ public function testItShouldReturnTheConfigArray(): void
2929
Socket::class => SocketFactory::class,
3030
]
3131
],
32-
'server' => []
32+
'server' => [
33+
'workers' => 1,
34+
'host' => '0.0.0.0',
35+
'port' => 8080,
36+
'max_concurrency' => 100,
37+
'buffer_size' => 4194304,
38+
]
3339
],
3440
$configProvider(),
3541
);

test/ServerFactoryTest.php

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,59 @@ public function testItShouldCreateReactServerInstances(): void
2424
->willReturnOnConsecutiveCalls(
2525
$this->createMock(ReactApplication::class),
2626
$this->createMock(LoopInterface::class),
27-
['server' => [
28-
29-
]]
27+
['server' => ['max_concurrency' => 100, 'buffer_size' => 43242]]
3028
);
3129
$factory = new ServerFactory();
3230
$server = $factory($container);
3331
$this->assertInstanceOf(Server::class, $server);
3432
}
33+
34+
public function testItShouldThrowExceptionWithNonReactApplicationInstance(): void
35+
{
36+
$this->expectException(\InvalidArgumentException::class);
37+
$container = $this->createMock(ContainerInterface::class);
38+
$container->expects($this->once())
39+
->method('get')
40+
->with(Application::class)
41+
->willReturn(
42+
$this->createMock(Application::class)
43+
);
44+
$factory = new ServerFactory();
45+
$factory($container);
46+
}
47+
48+
/** @dataProvider getInvalidConfig */
49+
public function testItShouldThrowExceptionWithInvalidConfig(array $config): void
50+
{
51+
$this->expectException(\InvalidArgumentException::class);
52+
$container = $this->createMock(ContainerInterface::class);
53+
$container->expects($this->exactly(3))
54+
->method('get')
55+
->withConsecutive([Application::class], [LoopInterface::class], ['config'])
56+
->willReturnOnConsecutiveCalls(
57+
$this->createMock(ReactApplication::class),
58+
$this->createMock(LoopInterface::class),
59+
$config
60+
);
61+
$factory = new ServerFactory();
62+
$factory($container);
63+
}
64+
65+
public function getInvalidConfig()
66+
{
67+
return [
68+
[
69+
['server' => ['max_concurrency' => 100]]
70+
],
71+
[
72+
['server' => ['buffer_size' => 43525]]
73+
],
74+
[
75+
['server' => ['max_concurrency' => 'hello', 'buffer_size' => 43525]]
76+
],
77+
[
78+
['server' => ['max_concurrency' => 100, 'buffer_size' => []]]
79+
],
80+
];
81+
}
3582
}

test/SocketFactoryTest.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace AntidotTest\React;
66

77
use Antidot\React\SocketFactory;
8+
use Assert\AssertionFailedException;
89
use PHPUnit\Framework\TestCase;
910
use Psr\Container\ContainerInterface;
1011
use React\EventLoop\LoopInterface;
@@ -20,11 +21,52 @@ public function testItShouldcreateReactSocketInstances(): void
2021
->withConsecutive([LoopInterface::class], ['config'])
2122
->willReturnOnConsecutiveCalls(
2223
$this->createMock(LoopInterface::class),
23-
['server' => []]
24+
['server' => ['workers' => 1, 'host' => '0.0.0.0', 'port' => 8080]]
2425
);
2526

2627
$factory = new SocketFactory();
2728
$socket = $factory($container);
2829
$this->assertInstanceOf(Socket::class, $socket);
2930
}
31+
32+
/** @dataProvider getInvalidConfig */
33+
public function testItShouldThrowExceptionWithInvalidConfig(array $config): void
34+
{
35+
$this->expectException(AssertionFailedException::class);
36+
$container = $this->createMock(ContainerInterface::class);
37+
$container->expects($this->exactly(2))
38+
->method('get')
39+
->withConsecutive([LoopInterface::class], ['config'])
40+
->willReturnOnConsecutiveCalls(
41+
$this->createMock(LoopInterface::class),
42+
$config
43+
);
44+
45+
$factory = new SocketFactory();
46+
$factory($container);
47+
}
48+
49+
public function getInvalidConfig()
50+
{
51+
return [
52+
'Bad Host' => [
53+
['server' => ['port' => 8080, 'workers' => 3, 'host' => '756875.67867.7668.787']]
54+
],
55+
[
56+
['server' => ['host' => '0.0.0.0', 'port' => ['test'], 'workers' => 3]]
57+
],
58+
[
59+
['server' => ['host' => '0.0.0.0', 'port' => 8888, 'workers' => 'some']]
60+
],
61+
[
62+
['server' => ['port' => 43525, 'workers' => 3]]
63+
],
64+
[
65+
['server' => ['port' => 43525, 'host' => '0.0.0.0']]
66+
],
67+
[
68+
['server' => ['host' => '0.0.0.0', 'workers' => 3]]
69+
],
70+
];
71+
}
3072
}

0 commit comments

Comments
 (0)