Skip to content

Commit c141918

Browse files
committed
Update test suite to ensure 100% code coverage
1 parent 23dd72c commit c141918

File tree

6 files changed

+94
-13
lines changed

6 files changed

+94
-13
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ jobs:
2525
- 5.6
2626
- 5.5
2727
- 5.4
28+
exclude: # ignore flaky results for legacy PHP on Windows
29+
- os: windows-2022
30+
php: 5.4
2831
steps:
2932
- uses: actions/checkout@v3
3033
- uses: shivammathur/setup-php@v2
@@ -34,10 +37,17 @@ jobs:
3437
coverage: xdebug
3538
ini-file: development
3639
- run: composer install
37-
- run: vendor/bin/phpunit --coverage-text
40+
- run: vendor/bin/phpunit --coverage-text --coverage-clover=clover.xml
3841
if: ${{ matrix.php >= 7.3 }}
39-
- run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy
42+
- run: vendor/bin/phpunit --coverage-text --coverage-clover=clover.xml -c phpunit.xml.legacy
4043
if: ${{ matrix.php < 7.3 }}
44+
- name: Check 100% code coverage
45+
if: ${{ matrix.os != 'windows-2022' }}
46+
shell: php {0}
47+
run: |
48+
<?php
49+
$metrics = simplexml_load_file('clover.xml')->project->metrics;
50+
exit((int) $metrics['statements'] === (int) $metrics['coveredstatements'] ? 0 : 1);
4151
- run: cd tests/install-as-dep && composer install && php query.php
4252
- run: cd tests/install-as-dep && php -d phar.readonly=0 vendor/bin/phar-composer build . query.phar && php query.phar
4353
- run: cd tests/install-as-dep && mv query.phar query.ext && php query.ext

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# clue/reactphp-sqlite
22

33
[![CI status](https://github.com/clue/reactphp-sqlite/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-sqlite/actions)
4+
[![code coverage](https://img.shields.io/badge/code%20coverage-100%25-success)](#tests)
45
[![installs on Packagist](https://img.shields.io/packagist/dt/clue/reactphp-sqlite?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/reactphp-sqlite)
56

67
Async SQLite database, lightweight non-blocking process wrapper around file-based database extension (`ext-sqlite3`),
@@ -470,6 +471,15 @@ To run the test suite, go to the project root and run:
470471
vendor/bin/phpunit
471472
```
472473

474+
The test suite is set up to always ensure 100% code coverage across all
475+
supported environments (except the platform-specific code that does not execute
476+
on Windows). If you have the Xdebug extension installed, you can also generate a
477+
code coverage report locally like this:
478+
479+
```bash
480+
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
481+
```
482+
473483
## License
474484

475485
This project is released under the permissive [MIT license](LICENSE).

src/Factory.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,9 @@ public function open($filename, $flags = null)
128128
return \React\Promise\resolve(new BlockingDatabase($filename, $flags));
129129
} catch (\Exception $e) {
130130
return \React\Promise\reject(new \RuntimeException($e->getMessage()) );
131-
} catch (\Error $e) {
132-
return \React\Promise\reject(new \RuntimeException($e->getMessage()));
131+
} catch (\Error $e) { // @codeCoverageIgnore
132+
assert(\PHP_VERSION_ID >= 70000); // @codeCoverageIgnore
133+
return \React\Promise\reject(new \RuntimeException($e->getMessage())); // @codeCoverageIgnore
133134
}
134135
}
135136

@@ -229,12 +230,15 @@ private function openProcessIo($filename, $flags = null)
229230
$cwd = null;
230231
$worker = \dirname(__DIR__) . '/res/sqlite-worker.php';
231232

233+
// launch worker process directly or inside Phar by mapping relative paths (covered by functional test suite)
234+
// @codeCoverageIgnoreStart
232235
if (\class_exists('Phar', false) && ($phar = \Phar::running(false)) !== '') {
233-
$worker = '-r' . 'Phar::loadPhar(' . var_export($phar, true) . ');require(' . \var_export($worker, true) . ');'; // @codeCoverageIgnore
236+
$worker = '-r' . 'Phar::loadPhar(' . var_export($phar, true) . ');require(' . \var_export($worker, true) . ');';
234237
} else {
235238
$cwd = __DIR__ . '/../res';
236239
$worker = \basename($worker);
237240
}
241+
// @codeCoverageIgnoreEnd
238242
$command = 'exec ' . \escapeshellarg($this->bin) . ' ' . escapeshellarg($worker);
239243

240244
// Try to get list of all open FDs (Linux/Mac and others)
@@ -297,12 +301,15 @@ private function openSocketIo($filename, $flags = null)
297301
$cwd = null;
298302
$worker = \dirname(__DIR__) . '/res/sqlite-worker.php';
299303

304+
// launch worker process directly or inside Phar by mapping relative paths (covered by functional test suite)
305+
// @codeCoverageIgnoreStart
300306
if (\class_exists('Phar', false) && ($phar = \Phar::running(false)) !== '') {
301-
$worker = '-r' . 'Phar::loadPhar(' . var_export($phar, true) . ');require(' . \var_export($worker, true) . ');'; // @codeCoverageIgnore
307+
$worker = '-r' . 'Phar::loadPhar(' . var_export($phar, true) . ');require(' . \var_export($worker, true) . ');';
302308
} else {
303309
$cwd = __DIR__ . '/../res';
304310
$worker = \basename($worker);
305311
}
312+
// @codeCoverageIgnoreEnd
306313
$command = \escapeshellarg($this->bin) . ' ' . escapeshellarg($worker);
307314

308315
// launch process without default STDIO pipes, but inherit STDERR
@@ -316,9 +323,12 @@ private function openSocketIo($filename, $flags = null)
316323
// start temporary socket on random address
317324
$server = @stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr);
318325
if ($server === false) {
326+
// report error if temporary socket server can not be started (unlikely)
327+
// @codeCoverageIgnoreStart
319328
return \React\Promise\reject(
320329
new \RuntimeException('Unable to start temporary socket I/O server: ' . $errstr, $errno)
321330
);
331+
// @codeCoverageIgnoreEnd
322332
}
323333

324334
// pass random server address to child process to connect back to parent process
@@ -342,7 +352,7 @@ private function openSocketIo($filename, $flags = null)
342352
fclose($server);
343353
$process->terminate();
344354

345-
$deferred->reject(new \RuntimeException('No connection detected'));
355+
$deferred->reject(new \RuntimeException('Opening database socket timed out'));
346356
});
347357

348358
$process->on('exit', function () use ($deferred, $server, $timeout) {

src/Io/ProcessIoDatabase.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ public function query($sql, array $params = array())
8080
foreach ($params as &$value) {
8181
if (\is_string($value) && \preg_match('/[\x00-\x08\x11\x12\x14-\x1f\x7f]/u', $value) !== 0) {
8282
$value = ['base64' => \base64_encode($value)];
83-
} elseif (\is_float($value) && \PHP_VERSION_ID < 50606) {
83+
} elseif (\is_float($value) && \PHP_VERSION_ID < 50606) { // @codeCoverageIgnoreStart
8484
$value = ['float' => $value];
85-
}
85+
} // @codeCoverageIgnoreEnd
8686
}
8787

8888
return $this->send('query', array($sql, $params))->then(function ($data) {
@@ -98,9 +98,10 @@ public function query($sql, array $params = array())
9898
foreach ($row as &$value) {
9999
if (isset($value['base64'])) {
100100
$value = \base64_decode($value['base64']);
101-
} elseif (isset($value['float'])) {
101+
} elseif (isset($value['float'])) { // @codeCoverageIgnoreStart
102+
assert(\PHP_VERSION_ID < 50606);
102103
$value = (float)$value['float'];
103-
}
104+
} // @codeCoverageIgnoreEnd
104105
}
105106
}
106107
}

tests/FunctionalDatabaseTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,56 @@ public function testQuerySelectEmptyResolvesWithEmptyResultSetWithColumnsAndNoRo
797797
$this->assertSame([], $data->rows);
798798
}
799799

800+
public function testCancelOpenWithSocketRejectsPromise()
801+
{
802+
$factory = new Factory();
803+
804+
$ref = new \ReflectionProperty($factory, 'useSocket');
805+
$ref->setAccessible(true);
806+
$ref->setValue($factory, true);
807+
808+
$promise = $factory->open(':memory:');
809+
$promise->cancel();
810+
811+
$exception = null;
812+
$promise->then(null, function ($reason) use (&$exception) {
813+
$exception = $reason;
814+
});
815+
816+
$this->assertInstanceOf('RuntimeException', $exception);
817+
$this->assertEquals('Opening database cancelled', $exception->getMessage());
818+
}
819+
820+
public function testOpenWithSocketWillRejectWhenSocketConnectionTimesOut()
821+
{
822+
$timer = null;
823+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
824+
$loop->expects($this->once())->method('addTimer')->with(5.0, $this->callback(function (callable $callback) use (&$timer) {
825+
$timer = $callback;
826+
return true;
827+
}));
828+
$loop->expects($this->never())->method('cancelTimer');
829+
830+
$factory = new Factory($loop);
831+
832+
$ref = new \ReflectionProperty($factory, 'useSocket');
833+
$ref->setAccessible(true);
834+
$ref->setValue($factory, true);
835+
836+
$promise = $factory->open(':memory:');
837+
838+
$this->assertNotNull($timer);
839+
$timer();
840+
841+
$exception = null;
842+
$promise->then(null, function ($reason) use (&$exception) {
843+
$exception = $reason;
844+
});
845+
846+
$this->assertInstanceOf('RuntimeException', $exception);
847+
$this->assertEquals('Opening database socket timed out', $exception->getMessage());
848+
}
849+
800850
protected function expectCallableNever()
801851
{
802852
$mock = $this->createCallableMock();

tests/FunctionalExampleTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testQueryExampleExecutedWithCgiReturnsDefaultValueAfterContentTy
2626
$this->markTestSkipped('Unable to execute "php-cgi"');
2727
}
2828

29-
$output = $this->execExample('php-cgi query.php');
29+
$output = $this->execExample('php-cgi -dopcache.jit=off query.php');
3030

3131
$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
3232
}
@@ -55,7 +55,7 @@ public function testQueryExampleExecutedWithCgiAndOpenBasedirRestrictedRunsDefau
5555
$this->markTestSkipped('Unable to execute "php-cgi" or "php"');
5656
}
5757

58-
$output = $this->execExample('php-cgi -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');
58+
$output = $this->execExample('php-cgi -dopcache.jit=off -dopen_basedir=' . escapeshellarg(dirname(__DIR__)) . ' query.php');
5959

6060
$this->assertStringEndsWith("\n\n" . 'value' . "\n" . '42' . "\n", $output);
6161
}

0 commit comments

Comments
 (0)