diff --git a/CHANGELOG b/CHANGELOG index f434828..22d1d1f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ -Version 0.9.3 (2021-??-??) +Version 0.10.0 (2021-??-??) ========================== New features: - Complete UNIX manual page +- Support for running service as a forking daemon +- Respond to TERM and HUP signals when possible Version 0.9.2 (2021-05-25) ========================== diff --git a/RoboFile.php b/RoboFile.php index 424c88f..216a46e 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -328,8 +328,8 @@ class RoboFile extends \Robo\Tasks { } /** Generates the "arsse" command's manual page (UNIX man page) - * - * This requires that the Pandoc document converter be installed and + * + * This requires that the Pandoc document converter be installed and * available in $PATH. */ public function manpage(): Result { @@ -337,7 +337,7 @@ class RoboFile extends \Robo\Tasks { $man = [ 'en' => "man1/arsse.1", ]; - foreach($man as $src => $out) { + foreach ($man as $src => $out) { $src = BASE."manpages/$src.md"; $out = BASE."dist/man/$out"; $t->addTask($this->taskFilesystemStack()->mkdir(dirname($out), 0755)); @@ -371,7 +371,7 @@ class RoboFile extends \Robo\Tasks { } if ($entry) { $out[] = $entry; - } + } $entry = ['version' => $version, 'date' => $date, 'features' => [], 'fixes' => [], 'changes' => []]; $expected = ["separator"]; } elseif (in_array("separator", $expected) && preg_match('/^=+/', $l)) { @@ -422,7 +422,7 @@ class RoboFile extends \Robo\Tasks { $out[] = $entry; return $out; } - + protected function changelogDebian(array $log, string $targetVersion): string { $latest = $log[0]['version']; $baseVersion = preg_replace('/^(\d+(?:\.\d+)*).*/', "$1", $targetVersion); @@ -481,6 +481,6 @@ class RoboFile extends \Robo\Tasks { // change the user reference in the executable file $t->addTask($this->taskFilesystemStack()->mkdir($dir."dist/debian/bin")); $t->addTask($this->taskFilesystemStack()->copy($dir."dist/arsse", $dir."dist/debian/bin/arsse")); - $t->addTask($this->taskReplaceInFile($dir."dist/debian/bin/arsse")->from('posix_getpwnam("arsse"')->to('posix_getpwnam("www-data"')); + $t->addTask($this->taskReplaceInFile($dir."dist/debian/bin/arsse")->from('posix_getpwnam("arsse"')->to('posix_getpwnam("www-data"')); } } diff --git a/composer.json b/composer.json index 2c31abe..afab259 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "*" }, + "suggest": { + "ext-pcntl": "To respond to signals, particular to reload configuration via SIGHUP" + }, "config": { "platform": { "php": "7.1.33" diff --git a/lib/REST/Miniflux/Token.php b/lib/REST/Miniflux/Token.php index 8052c50..e249182 100644 --- a/lib/REST/Miniflux/Token.php +++ b/lib/REST/Miniflux/Token.php @@ -28,4 +28,4 @@ class Token { } return $out; } -} \ No newline at end of file +} diff --git a/lib/Service.php b/lib/Service.php index 9218a90..4a7f1cd 100644 --- a/lib/Service.php +++ b/lib/Service.php @@ -44,9 +44,11 @@ class Service { if ($this->loop) { do { sleep((int) max(0, $t->getTimestamp() - time())); - pcntl_signal_dispatch(); - if ($this->reload) { - $this->reload(); + if (function_exists("pcntl_signal_dispatch")) { + pcntl_signal_dispatch(); + if ($this->reload) { + $this->reload(); + } } } while ($this->loop && $t->getTimestamp() > time()); } @@ -117,14 +119,14 @@ class Service { } /** Changes the condition for the service loop upon receiving a termination signal - * + * * @codeCoverageIgnore */ protected function sigTerm(int $signo): void { $this->loop = false; } /** Changes the condition for the service loop upon receiving a hangup signal - * + * * @codeCoverageIgnore */ protected function sigHup(int $signo): void { $this->reload = true; diff --git a/lib/Service/Daemon.php b/lib/Service/Daemon.php index da134c1..2b429fd 100644 --- a/lib/Service/Daemon.php +++ b/lib/Service/Daemon.php @@ -18,7 +18,7 @@ class Daemon { } /** Daemonizes the process via the traditional sysvinit double-fork procedure - * + * * @codeCoverageIgnore */ public function fork(string $pidfile): void { @@ -144,7 +144,7 @@ class Daemon { } /** Wrapper around posix_kill (with signal 0) to facilitation testing - * + * * @codeCoverageIgnore */ protected function processExists(int $pid): bool { @@ -182,14 +182,14 @@ class Daemon { } /** Resolves paths with relative components - * + * * This method has fewer filesystem access requirements than the native * realpath() function. The current working directory most be resolvable * for a relative path, but for absolute paths with relativelu components * the filesystem is not involved at all. - * + * * Consequently symbolic links are not resolved. - * + * * @return string|false */ public function resolveRelativePath(string $path) { @@ -216,7 +216,7 @@ class Daemon { } /** Wrapper around posix_getcwd to facilitate testing - * + * * @return string|false * @codeCoverageIgnore */ diff --git a/tests/cases/CLI/TestCLI.php b/tests/cases/CLI/TestCLI.php index e96620b..a17ecbb 100644 --- a/tests/cases/CLI/TestCLI.php +++ b/tests/cases/CLI/TestCLI.php @@ -102,7 +102,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { $this->objMock->get->with(Daemon::class)->returns($daemon->get()); $this->assertConsole("arsse.php daemon --fork=arsse.pid", 10809); $daemon->checkPIDFilePath->calledWith("arsse.pid"); - $daemon->fork->never()->called(); + $daemon->fork->never()->called(); $this->cli->loadConf->never()->called(); $srv->watch->never()->called(); } diff --git a/tests/cases/Database/SeriesSubscription.php b/tests/cases/Database/SeriesSubscription.php index 625ac39..0b5f651 100644 --- a/tests/cases/Database/SeriesSubscription.php +++ b/tests/cases/Database/SeriesSubscription.php @@ -45,8 +45,8 @@ trait SeriesSubscription { ], 'arsse_icons' => [ 'columns' => [ - 'id' => "int", - 'url' => "str", + 'id' => "int", + 'url' => "str", 'data' => "blob", ], 'rows' => [ diff --git a/tests/cases/Db/SQLite3/TestCreation.php b/tests/cases/Db/SQLite3/TestCreation.php index 348e2ae..cc4927d 100644 --- a/tests/cases/Db/SQLite3/TestCreation.php +++ b/tests/cases/Db/SQLite3/TestCreation.php @@ -196,6 +196,6 @@ class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest { // check the mode clearstatcache(); $mode = base_convert((string) stat($f)['mode'], 10, 8); - $this->assertMatchesRegularExpression("/640$/", $mode); + $this->assertMatchesRegularExpression("/640$/", $mode); } } diff --git a/tests/cases/ImportExport/TestOPML.php b/tests/cases/ImportExport/TestOPML.php index ff21542..7e73bc3 100644 --- a/tests/cases/ImportExport/TestOPML.php +++ b/tests/cases/ImportExport/TestOPML.php @@ -11,7 +11,6 @@ use JKingWeb\Arsse\Database; use JKingWeb\Arsse\Test\Result; use JKingWeb\Arsse\ImportExport\OPML; use JKingWeb\Arsse\ImportExport\Exception; -use ReflectionMethod; /** @covers \JKingWeb\Arsse\ImportExport\OPML */ class TestOPML extends \JKingWeb\Arsse\Test\AbstractTest { diff --git a/tests/cases/Misc/TestValueInfo.php b/tests/cases/Misc/TestValueInfo.php index 90b4be9..7b30e11 100644 --- a/tests/cases/Misc/TestValueInfo.php +++ b/tests/cases/Misc/TestValueInfo.php @@ -12,7 +12,6 @@ use JKingWeb\Arsse\Test\Result; /** @covers \JKingWeb\Arsse\Misc\ValueInfo */ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest { - public function testGetIntegerInfo(): void { $tests = [ [null, I::NULL], diff --git a/tests/cases/REST/Fever/TestAPI.php b/tests/cases/REST/Fever/TestAPI.php index 0868d13..a9896c7 100644 --- a/tests/cases/REST/Fever/TestAPI.php +++ b/tests/cases/REST/Fever/TestAPI.php @@ -15,7 +15,6 @@ use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\Transaction; use JKingWeb\Arsse\REST\Fever\API; use Psr\Http\Message\ResponseInterface; -use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\Response\JsonResponse; use Laminas\Diactoros\Response\XmlResponse; use Laminas\Diactoros\Response\EmptyResponse; diff --git a/tests/cases/REST/Fever/TestUser.php b/tests/cases/REST/Fever/TestUser.php index 09027c2..3700e19 100644 --- a/tests/cases/REST/Fever/TestUser.php +++ b/tests/cases/REST/Fever/TestUser.php @@ -34,7 +34,7 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { Arsse::$db = $this->dbMock->get(); // instantiate the handler return new FeverUser; - } + } /** @dataProvider providePasswordCreations */ public function testRegisterAUserPassword(string $user, string $password = null, $exp): void { diff --git a/tests/cases/REST/TestREST.php b/tests/cases/REST/TestREST.php index 55da259..0ba6ead 100644 --- a/tests/cases/REST/TestREST.php +++ b/tests/cases/REST/TestREST.php @@ -306,7 +306,7 @@ class TestREST extends \JKingWeb\Arsse\Test\AbstractTest { if ($called) { $rMock->authenticateRequest->called(); $hMock->dispatch->once()->called(); - $in = $hMock->dispatch->firstCall()->argument();; + $in = $hMock->dispatch->firstCall()->argument(); $this->assertSame($method, $in->getMethod()); $this->assertSame($target, $in->getRequestTarget()); } else { diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index 18d1d24..74a12b9 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -899,7 +899,7 @@ LONG_STRING; [['caption' => " "], [$this->userId, ['name' => " "]], new ExceptionInput("typeViolation"), null, null, $this->respErr("INCORRECT_USAGE")], ]; } - + /** @dataProvider provideLabelRemovals */ public function testRemoveALabel(array $in, ?array $data, $out, ResponseInterface $exp): void { $in = array_merge(['op' => "removeLabel", 'sid' => "PriestsOfSyrinx"], $in); @@ -1356,7 +1356,6 @@ LONG_STRING; ]; } - /** @dataProvider provideArticleChanges */ public function testChangeArticles(array $in, ResponseInterface $exp): void { $in = array_merge(['op' => "updateArticle", 'sid' => "PriestsOfSyrinx"], $in); diff --git a/tests/cases/Service/TestDaemon.php b/tests/cases/Service/TestDaemon.php index d35ef61..b911656 100644 --- a/tests/cases/Service/TestDaemon.php +++ b/tests/cases/Service/TestDaemon.php @@ -20,7 +20,7 @@ class TestDaemon extends \JKingWeb\Arsse\Test\AbstractTest { 'readwrite' => "can neither be read nor written to", ], 'ok' => [ - 'dir' => [], + 'dir' => [], 'file' => "this file can be fully accessed", ], 'pid' => [ diff --git a/tests/cases/Service/TestService.php b/tests/cases/Service/TestService.php index 553895e..6c90358 100644 --- a/tests/cases/Service/TestService.php +++ b/tests/cases/Service/TestService.php @@ -23,7 +23,6 @@ class TestService extends \JKingWeb\Arsse\Test\AbstractTest { $this->srv = new Service(); } - public function testCheckIn(): void { $now = time(); $this->srv->checkIn(); diff --git a/tests/cases/User/TestUser.php b/tests/cases/User/TestUser.php index e6e6d65..0066028 100644 --- a/tests/cases/User/TestUser.php +++ b/tests/cases/User/TestUser.php @@ -21,12 +21,12 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { parent::setUp(); self::setConf(); // create a mock database interface - $this->dbMock= $this->mock(Database::class); + $this->dbMock = $this->mock(Database::class); $this->dbMock->begin->returns($this->mock(\JKingWeb\Arsse\Db\Transaction::class)); // create a mock user driver $this->drv = $this->mock(Driver::class); } - + protected function prepTest(?\Closure $partialMockDef = null): User { Arsse::$db = $this->dbMock->get(); if ($partialMockDef) { @@ -189,7 +189,7 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { $pass = "random password"; $this->drv->userAdd->returns(null)->returns($pass); $this->dbMock->userExists->returns(true); - $u = $this->prepTest(function ($u) use ($pass) { + $u = $this->prepTest(function($u) use ($pass) { $u->generatePassword->returns($pass); }); $this->assertSame($pass, $u->add($user)); @@ -330,7 +330,7 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { $this->drv->userPasswordSet->returns(null)->returns($pass); $this->dbMock->userPasswordSet->returns($pass); $this->dbMock->userExists->returns(true); - $u = $this->prepTest(function ($u) use ($pass) { + $u = $this->prepTest(function($u) use ($pass) { $u->generatePassword->returns($pass); }); $this->assertSame($pass, $u->passwordSet($user, null)); @@ -360,7 +360,7 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { $this->drv->userPasswordSet->returns(null)->returns($pass); $this->dbMock->userPasswordSet->returns($pass); $this->dbMock->userExists->returns(false); - $u = $this->prepTest(function ($u) use ($pass) { + $u = $this->prepTest(function($u) use ($pass) { $u->generatePassword->returns($pass); }); $this->assertSame($pass, $u->passwordSet($user, null)); @@ -374,7 +374,7 @@ class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { $user = "john.doe@example.com"; $pass = "random password"; $this->drv->userPasswordSet->throws(new ExceptionConflict("doesNotExist")); - $u = $this->prepTest(function ($u) use ($pass) { + $u = $this->prepTest(function($u) use ($pass) { $u->generatePassword->returns($pass); }); $this->assertException("doesNotExist", "User", "ExceptionConflict"); diff --git a/tests/lib/AbstractTest.php b/tests/lib/AbstractTest.php index d41b7d3..409a6e9 100644 --- a/tests/lib/AbstractTest.php +++ b/tests/lib/AbstractTest.php @@ -36,7 +36,6 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { protected $langMock; protected $dbMock; protected $userMock; - public function setUp(): void { self::clearData(); @@ -155,7 +154,6 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase { } else { parent::assertFileNotExists($filename, $message); } - } public function assertException($msg = "", string $prefix = "", string $type = "Exception"): void {