mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2025-01-03 14:32:40 +00:00
Use more reliable database locking strategy; reorganize tests
PostgreSQL and MySQL both have better locking mechanisms than what was previously implemented, as well
This commit is contained in:
parent
17ec6cf669
commit
b3f631e335
34 changed files with 152 additions and 195 deletions
|
@ -4,10 +4,13 @@ namespace JKingWeb\Arsse\Db;
|
||||||
use JKingWeb\DrUUID\UUID as UUID;
|
use JKingWeb\DrUUID\UUID as UUID;
|
||||||
|
|
||||||
abstract class AbstractDriver implements Driver {
|
abstract class AbstractDriver implements Driver {
|
||||||
|
protected $locked = false;
|
||||||
protected $transDepth = 0;
|
protected $transDepth = 0;
|
||||||
protected $transStatus = [];
|
protected $transStatus = [];
|
||||||
|
|
||||||
public abstract function prepareArray(string $query, array $paramTypes): Statement;
|
public abstract function prepareArray(string $query, array $paramTypes): Statement;
|
||||||
|
protected abstract function lock(): bool;
|
||||||
|
protected abstract function unlock(bool $rollback = false) : bool;
|
||||||
|
|
||||||
public function schemaVersion(): int {
|
public function schemaVersion(): int {
|
||||||
try {
|
try {
|
||||||
|
@ -17,11 +20,15 @@ abstract class AbstractDriver implements Driver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function begin(): Transaction {
|
public function begin(bool $lock = false): Transaction {
|
||||||
return new Transaction($this);
|
return new Transaction($this, $lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function savepointCreate(): int {
|
public function savepointCreate(bool $lock = false): int {
|
||||||
|
if($lock && !$this->transDepth) {
|
||||||
|
$this->lock();
|
||||||
|
$this->locked = true;
|
||||||
|
}
|
||||||
$this->exec("SAVEPOINT arsse_".(++$this->transDepth));
|
$this->exec("SAVEPOINT arsse_".(++$this->transDepth));
|
||||||
$this->transStatus[$this->transDepth] = self::TR_PEND;
|
$this->transStatus[$this->transDepth] = self::TR_PEND;
|
||||||
return $this->transDepth;
|
return $this->transDepth;
|
||||||
|
@ -60,6 +67,10 @@ abstract class AbstractDriver implements Driver {
|
||||||
$this->transDepth--;
|
$this->transDepth--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!$this->transDepth && $this->locked) {
|
||||||
|
$this->unlock();
|
||||||
|
$this->locked = false;
|
||||||
|
}
|
||||||
return $out;
|
return $out;
|
||||||
} else {
|
} else {
|
||||||
throw new ExceptionSavepoint("invalid", ['action' => "commit", 'index' => $index]);
|
throw new ExceptionSavepoint("invalid", ['action' => "commit", 'index' => $index]);
|
||||||
|
@ -100,36 +111,16 @@ abstract class AbstractDriver implements Driver {
|
||||||
$this->transDepth--;
|
$this->transDepth--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!$this->transDepth && $this->locked) {
|
||||||
|
$this->unlock(true);
|
||||||
|
$this->locked = false;
|
||||||
|
}
|
||||||
return $out;
|
return $out;
|
||||||
} else {
|
} else {
|
||||||
throw new ExceptionSavepoint("invalid", ['action' => "rollback", 'index' => $index]);
|
throw new ExceptionSavepoint("invalid", ['action' => "rollback", 'index' => $index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function lock(): bool {
|
|
||||||
if($this->schemaVersion() < 1) return true;
|
|
||||||
if($this->isLocked()) return false;
|
|
||||||
$uuid = UUID::mintStr();
|
|
||||||
try {
|
|
||||||
$this->prepare("INSERT INTO arsse_meta(key,value) values(?,?)", "str", "str")->run("lock", $uuid);
|
|
||||||
} catch(ExceptionInput $e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sleep(1);
|
|
||||||
return ($this->query("SELECT value from arsse_meta where key is 'lock'")->getValue() == $uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function unlock(): bool {
|
|
||||||
if($this->schemaVersion() < 1) return true;
|
|
||||||
$this->exec("DELETE from arsse_meta where key is 'lock'");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isLocked(): bool {
|
|
||||||
if($this->schemaVersion() < 1) return false;
|
|
||||||
return ($this->query("SELECT count(*) from arsse_meta where key is 'lock'")->getValue() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function prepare(string $query, ...$paramType): Statement {
|
public function prepare(string $query, ...$paramType): Statement {
|
||||||
return $this->prepareArray($query, $paramType);
|
return $this->prepareArray($query, $paramType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,25 +59,17 @@ abstract class AbstractStatement implements Statement {
|
||||||
case "string":
|
case "string":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
if($t=="binary") $t = "string";
|
if($t=="binary") $t = "string";
|
||||||
$value = $v;
|
if($v instanceof \DateTimeInterface) {
|
||||||
try{
|
if($t=="string") {
|
||||||
settype($value, $t);
|
return $this->dateTransform($v, "sql");
|
||||||
} catch(\Throwable $e) {
|
|
||||||
// handle objects
|
|
||||||
$value = $v;
|
|
||||||
if($value instanceof \DateTimeInterface) {
|
|
||||||
if($t=="string") {
|
|
||||||
$value = $this->dateTransform($value, "sql");
|
|
||||||
} else {
|
|
||||||
$value = $value->getTimestamp();
|
|
||||||
settype($value, $t);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$value = null;
|
$v = $v->getTimestamp();
|
||||||
settype($value, $t);
|
settype($v, $t);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
settype($v, $t);
|
||||||
}
|
}
|
||||||
return $value;
|
return $v;
|
||||||
default:
|
default:
|
||||||
throw new Exception("paramTypeUnknown", $type);
|
throw new Exception("paramTypeUnknown", $type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,17 +15,13 @@ interface Driver {
|
||||||
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
// returns the version of the scheme of the opened database; if uninitialized should return 0
|
||||||
function schemaVersion(): int;
|
function schemaVersion(): int;
|
||||||
// return a Transaction object
|
// return a Transaction object
|
||||||
function begin(): Transaction;
|
function begin(bool $lock = false): Transaction;
|
||||||
// manually begin a real or synthetic transactions, with real or synthetic nesting
|
// manually begin a real or synthetic transactions, with real or synthetic nesting
|
||||||
function savepointCreate(): int;
|
function savepointCreate(): int;
|
||||||
// manually commit either the latest or all pending nested transactions
|
// manually commit either the latest or all pending nested transactions
|
||||||
function savepointRelease(int $index = null): bool;
|
function savepointRelease(int $index = null): bool;
|
||||||
// manually rollback either the latest or all pending nested transactions
|
// manually rollback either the latest or all pending nested transactions
|
||||||
function savepointUndo(int $index = null): bool;
|
function savepointUndo(int $index = null): bool;
|
||||||
// attempt to advise other processes that they should not attempt to access the database; used during live upgrades
|
|
||||||
function lock(): bool;
|
|
||||||
function unlock(): bool;
|
|
||||||
function isLocked(): bool;
|
|
||||||
// attempt to perform an in-place upgrade of the database schema; this may be a no-op which always throws an exception
|
// attempt to perform an in-place upgrade of the database schema; this may be a no-op which always throws an exception
|
||||||
function schemaUpdate(int $to): bool;
|
function schemaUpdate(int $to): bool;
|
||||||
// execute one or more unsanitized SQL queries and return an indication of success
|
// execute one or more unsanitized SQL queries and return an indication of success
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
$file = Data::$conf->dbSQLite3File;
|
$file = Data::$conf->dbSQLite3File;
|
||||||
// if the file exists (or we're initializing the database), try to open it
|
// if the file exists (or we're initializing the database), try to open it
|
||||||
try {
|
try {
|
||||||
$this->db = new \SQLite3($file, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, Data::$conf->dbSQLite3Key);
|
$this->db = $this->makeConnection($file, ($install) ? \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE : \SQLITE3_OPEN_READWRITE, Data::$conf->dbSQLite3Key);
|
||||||
} catch(\Throwable $e) {
|
} catch(\Throwable $e) {
|
||||||
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
|
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
|
||||||
if(!file_exists($file)) {
|
if(!file_exists($file)) {
|
||||||
|
@ -46,6 +46,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function makeConnection(string $file, int $opts, string $key): \SQLite3 {
|
||||||
|
return new \SQLite3($file, $opts, $key);
|
||||||
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
try{$this->db->close();} catch(\Exception $e) {}
|
try{$this->db->close();} catch(\Exception $e) {}
|
||||||
unset($this->db);
|
unset($this->db);
|
||||||
|
@ -66,9 +70,9 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
if($ver >= $to) throw new Exception("updateTooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
if($ver >= $to) throw new Exception("updateTooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
|
||||||
$sep = \DIRECTORY_SEPARATOR;
|
$sep = \DIRECTORY_SEPARATOR;
|
||||||
$path = Data::$conf->dbSchemaBase.$sep."SQLite3".$sep;
|
$path = Data::$conf->dbSchemaBase.$sep."SQLite3".$sep;
|
||||||
$this->lock();
|
// lock the database
|
||||||
$tr = $this->savepointCreate();
|
$this->savepointCreate(true);
|
||||||
for($a = $ver; $a < $to; $a++) {
|
for($a = $this->schemaVersion(); $a < $to; $a++) {
|
||||||
$this->savepointCreate();
|
$this->savepointCreate();
|
||||||
try {
|
try {
|
||||||
$file = $path.$a.".sql";
|
$file = $path.$a.".sql";
|
||||||
|
@ -78,7 +82,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
if($sql===false) throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
if($sql===false) throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
try {
|
try {
|
||||||
$this->exec($sql);
|
$this->exec($sql);
|
||||||
} catch(\Exception $e) {
|
} catch(\Throwable $e) {
|
||||||
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]);
|
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]);
|
||||||
}
|
}
|
||||||
if($this->schemaVersion() != $a+1) throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
if($this->schemaVersion() != $a+1) throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);
|
||||||
|
@ -86,14 +90,12 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
// undo any partial changes from the failed update
|
// undo any partial changes from the failed update
|
||||||
$this->savepointUndo();
|
$this->savepointUndo();
|
||||||
// commit any successful updates if updating by more than one version
|
// commit any successful updates if updating by more than one version
|
||||||
$this->unlock();
|
|
||||||
$this->savepointRelease();
|
$this->savepointRelease();
|
||||||
// throw the error received
|
// throw the error received
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
$this->savepointRelease();
|
$this->savepointRelease();
|
||||||
}
|
}
|
||||||
$this->unlock();
|
|
||||||
$this->savepointRelease();
|
$this->savepointRelease();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -132,4 +134,14 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
return new Statement($this->db, $s, $paramTypes);
|
return new Statement($this->db, $s, $paramTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function lock(): bool {
|
||||||
|
$this->exec("BEGIN EXCLUSIVE TRANSACTION");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unlock(bool $rollback = false): bool {
|
||||||
|
$this->exec((!$rollback) ? "COMMIT" : "ROLLBACK");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,8 +7,8 @@ class Transaction {
|
||||||
protected $pending = false;
|
protected $pending = false;
|
||||||
protected $drv;
|
protected $drv;
|
||||||
|
|
||||||
function __construct(Driver $drv) {
|
function __construct(Driver $drv, bool $lock = false) {
|
||||||
$this->index = $drv->savepointCreate();
|
$this->index = $drv->savepointCreate($lock);
|
||||||
$this->drv = $drv;
|
$this->drv = $drv;
|
||||||
$this->pending = true;
|
$this->pending = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,8 @@ class Context {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(is_string($id)) {
|
if(is_string($id)) {
|
||||||
try {
|
$ch1 = strval(@intval($id));
|
||||||
$ch1 = strval(intval($id));
|
$ch2 = strval($id);
|
||||||
$ch2 = strval($id);
|
|
||||||
} catch(\Throwable $e) {
|
|
||||||
$ch1 = true;
|
|
||||||
$ch2 = false;
|
|
||||||
}
|
|
||||||
if($ch1 !== $ch2 || $id < 1) $id = 0;
|
if($ch1 !== $ch2 || $id < 1) $id = 0;
|
||||||
} else {
|
} else {
|
||||||
$id = 0;
|
$id = 0;
|
||||||
|
|
|
@ -32,12 +32,8 @@ abstract class AbstractHandler implements Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validateInt($id): bool {
|
protected function validateInt($id): bool {
|
||||||
try {
|
$ch1 = strval(@intval($id));
|
||||||
$ch1 = strval(intval($id));
|
$ch2 = strval($id);
|
||||||
$ch2 = strval($id);
|
|
||||||
} catch(\Throwable $e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ($ch1 === $ch2);
|
return ($ch1 === $ch2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,8 @@ class V1_2 extends \JKingWeb\Arsse\REST\AbstractHandler {
|
||||||
if($req->body) {
|
if($req->body) {
|
||||||
// if the entity body is not JSON according to content type, return "415 Unsupported Media Type"
|
// if the entity body is not JSON according to content type, return "415 Unsupported Media Type"
|
||||||
if(!preg_match("<^application/json\b|^$>", $req->type)) return new Response(415, "", "", ['Accept: application/json']);
|
if(!preg_match("<^application/json\b|^$>", $req->type)) return new Response(415, "", "", ['Accept: application/json']);
|
||||||
try {
|
$data = @json_decode($req->body, true);
|
||||||
$data = json_decode($req->body, true);
|
if(json_last_error() != \JSON_ERROR_NONE) {
|
||||||
} catch(\Throwable $e) {
|
|
||||||
// if the body could not be parsed as JSON, return "400 Bad Request"
|
// if the body could not be parsed as JSON, return "400 Bad Request"
|
||||||
return new Response(400);
|
return new Response(400);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
|
||||||
class TestConf extends \PHPUnit\Framework\TestCase {
|
class TestConf extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
static $vfs;
|
static $vfs;
|
||||||
static $path;
|
static $path;
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class TestDatabaseArticleSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDatabaseArticleSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Database\Setup;
|
use Test\Database\Setup;
|
||||||
use Test\Database\DriverSQLite3;
|
use Test\Database\DriverSQLite3;
|
||||||
use Test\Database\SeriesArticle;
|
use Test\Database\SeriesArticle;
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class TestDatabaseFeedSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDatabaseFeedSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Database\Setup;
|
use Test\Database\Setup;
|
||||||
use Test\Database\DriverSQLite3;
|
use Test\Database\DriverSQLite3;
|
||||||
use Test\Database\SeriesFeed;
|
use Test\Database\SeriesFeed;
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class TestDatabaseFolderSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDatabaseFolderSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Database\Setup;
|
use Test\Database\Setup;
|
||||||
use Test\Database\DriverSQLite3;
|
use Test\Database\DriverSQLite3;
|
||||||
use Test\Database\SeriesFolder;
|
use Test\Database\SeriesFolder;
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class TestDatabaseSubscriptionSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDatabaseSubscriptionSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Database\Setup;
|
use Test\Database\Setup;
|
||||||
use Test\Database\DriverSQLite3;
|
use Test\Database\DriverSQLite3;
|
||||||
use Test\Database\SeriesSubscription;
|
use Test\Database\SeriesSubscription;
|
||||||
}
|
}
|
|
@ -2,8 +2,8 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
class TestDatabaseUserSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDatabaseUserSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Database\Setup;
|
use Test\Database\Setup;
|
||||||
use Test\Database\DriverSQLite3;
|
use Test\Database\DriverSQLite3;
|
||||||
use Test\Database\SeriesUser;
|
use Test\Database\SeriesUser;
|
||||||
}
|
}
|
|
@ -3,9 +3,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
|
|
||||||
class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDbDriverSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected $data;
|
protected $data;
|
||||||
protected $drv;
|
protected $drv;
|
||||||
protected $ch;
|
protected $ch;
|
||||||
|
@ -21,6 +19,7 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
Data::$conf = $conf;
|
Data::$conf = $conf;
|
||||||
$this->drv = new Db\SQLite3\Driver(true);
|
$this->drv = new Db\SQLite3\Driver(true);
|
||||||
$this->ch = new \SQLite3(Data::$conf->dbSQLite3File);
|
$this->ch = new \SQLite3(Data::$conf->dbSQLite3File);
|
||||||
|
$this->ch->enableExceptions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tearDown() {
|
function tearDown() {
|
||||||
|
@ -68,7 +67,7 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMakeAValidQuery() {
|
function testMakeAValidQuery() {
|
||||||
$this->assertInstanceOf(Db\SQLite3\Result::class, $this->drv->query("SELECT 1"));
|
$this->assertInstanceOf(Db\Result::class, $this->drv->query("SELECT 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function testMakeAnInvalidQuery() {
|
function testMakeAnInvalidQuery() {
|
||||||
|
@ -96,7 +95,7 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
|
|
||||||
function testPrepareAValidQuery() {
|
function testPrepareAValidQuery() {
|
||||||
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
|
$s = $this->drv->prepare("SELECT ?, ?", "int", "int");
|
||||||
$this->assertInstanceOf(Db\SQLite3\Statement::class, $s);
|
$this->assertInstanceOf(Db\Statement::class, $s);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testPrepareAnInvalidQuery() {
|
function testPrepareAnInvalidQuery() {
|
||||||
|
@ -295,25 +294,17 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
$this->assertSame(1, $this->drv->schemaVersion());
|
$this->assertSame(1, $this->drv->schemaVersion());
|
||||||
$this->drv->exec("PRAGMA user_version=2");
|
$this->drv->exec("PRAGMA user_version=2");
|
||||||
$this->assertSame(2, $this->drv->schemaVersion());
|
$this->assertSame(2, $this->drv->schemaVersion());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function testManipulateAdvisoryLock() {
|
function testLockTheDatabase() {
|
||||||
$this->assertTrue($this->drv->unlock());
|
$this->drv->savepointCreate(true);
|
||||||
$this->assertFalse($this->drv->isLocked());
|
$this->assertException();
|
||||||
$this->assertTrue($this->drv->lock());
|
$this->ch->exec("CREATE TABLE test(id integer primary key)");
|
||||||
$this->assertFalse($this->drv->isLocked());
|
}
|
||||||
$this->drv->exec("CREATE TABLE arsse_meta(key text primary key, value text); PRAGMA user_version=1");
|
|
||||||
$this->assertTrue($this->drv->lock());
|
function testUnlockTheDatabase() {
|
||||||
$this->assertTrue($this->drv->isLocked());
|
$this->drv->savepointCreate(true);
|
||||||
$this->assertFalse($this->drv->lock());
|
$this->drv->savepointRelease();
|
||||||
$this->drv->exec("PRAGMA user_version=0");
|
$this->assertSame(true, $this->ch->exec("CREATE TABLE test(id integer primary key)"));
|
||||||
$this->assertFalse($this->drv->isLocked());
|
|
||||||
$this->assertTrue($this->drv->lock());
|
|
||||||
$this->assertFalse($this->drv->isLocked());
|
|
||||||
$this->drv->exec("PRAGMA user_version=1");
|
|
||||||
$this->assertTrue($this->drv->isLocked());
|
|
||||||
$this->assertTrue($this->drv->unlock());
|
|
||||||
$this->assertFalse($this->drv->isLocked());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,8 +3,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
|
|
||||||
class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDbResultSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected $c;
|
protected $c;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace JKingWeb\Arsse;
|
||||||
use JKingWeb\Arsse\Db\Statement;
|
use JKingWeb\Arsse\Db\Statement;
|
||||||
|
|
||||||
|
|
||||||
class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDbStatementSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Db\BindingTests;
|
use Test\Db\BindingTests;
|
||||||
|
|
||||||
protected $c;
|
protected $c;
|
||||||
static protected $imp = Db\SQLite3\Statement::class;
|
static protected $imp = Db\SQLite3\Statement::class;
|
||||||
|
@ -20,7 +20,6 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
function tearDown() {
|
function tearDown() {
|
||||||
try {$this->s->close();} catch(\Exception $e) {}
|
|
||||||
$this->c->close();
|
$this->c->close();
|
||||||
unset($this->c);
|
unset($this->c);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +31,7 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
|
||||||
foreach($types as $type) {
|
foreach($types as $type) {
|
||||||
$s->rebindArray([$strict ? "strict $type" : $type]);
|
$s->rebindArray([$strict ? "strict $type" : $type]);
|
||||||
$val = $s->runArray([$input])->getRow()['value'];
|
$val = $s->runArray([$input])->getRow()['value'];
|
||||||
$this->assertSame($expectations[$type], $val, "Type $type failed comparison.");
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
|
||||||
class TestDbUpdateSQLite3 extends \PHPUnit\Framework\TestCase {
|
class TestDbUpdateSQLite3 extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected $data;
|
protected $data;
|
||||||
protected $drv;
|
protected $drv;
|
||||||
protected $vfs;
|
protected $vfs;
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
Use Phake;
|
Use Phake;
|
||||||
|
|
||||||
|
|
||||||
class TestException extends \PHPUnit\Framework\TestCase {
|
class TestException extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
$this->clearData(false);
|
$this->clearData(false);
|
||||||
// create a mock Lang object so as not to create a dependency loop
|
// create a mock Lang object so as not to create a dependency loop
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
Use Phake;
|
Use Phake;
|
||||||
|
|
||||||
|
|
||||||
class TestFeed extends \PHPUnit\Framework\TestCase {
|
class TestFeed extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected static $host = "http://localhost:8000/";
|
protected static $host = "http://localhost:8000/";
|
||||||
protected $base = "";
|
protected $base = "";
|
||||||
protected $latest = [
|
protected $latest = [
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
Use Phake;
|
Use Phake;
|
||||||
|
|
||||||
|
|
||||||
class TestFeedFetching extends \PHPUnit\Framework\TestCase {
|
class TestFeedFetching extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected static $host = "http://localhost:8000/";
|
protected static $host = "http://localhost:8000/";
|
||||||
protected $base = "";
|
protected $base = "";
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace JKingWeb\Arsse;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
|
||||||
class TestLang extends \PHPUnit\Framework\TestCase {
|
class TestLang extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Lang\Setup;
|
use Test\Lang\Setup;
|
||||||
|
|
||||||
public $files;
|
public $files;
|
||||||
public $path;
|
public $path;
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace JKingWeb\Arsse;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
|
||||||
class TestLangErrors extends \PHPUnit\Framework\TestCase {
|
class TestLangErrors extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Lang\Setup;
|
use Test\Lang\Setup;
|
||||||
|
|
||||||
public $files;
|
public $files;
|
||||||
public $path;
|
public $path;
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace JKingWeb\Arsse;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
|
||||||
|
|
||||||
class TestLangComplex extends \PHPUnit\Framework\TestCase {
|
class TestLangComplex extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\Lang\Setup;
|
use Test\Lang\Setup;
|
||||||
|
|
||||||
public $files;
|
public $files;
|
||||||
public $path;
|
public $path;
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
use JKingWeb\Arsse\Misc\Context;
|
use JKingWeb\Arsse\Misc\Context;
|
||||||
|
|
||||||
|
|
||||||
class TestContext extends \PHPUnit\Framework\TestCase {
|
class TestContext extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
function testVerifyInitialState() {
|
function testVerifyInitialState() {
|
||||||
$c = new Context;
|
$c = new Context;
|
||||||
foreach((new \ReflectionObject($c))->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
|
foreach((new \ReflectionObject($c))->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
|
||||||
|
|
|
@ -8,9 +8,7 @@ use JKingWeb\Arsse\Misc\Context;
|
||||||
use Phake;
|
use Phake;
|
||||||
|
|
||||||
|
|
||||||
class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
|
class TestNCNV1_2 extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
protected $h;
|
protected $h;
|
||||||
protected $feeds = [ // expected sample output of a feed list from the database, and the resultant expected transformation by the REST handler
|
protected $feeds = [ // expected sample output of a feed list from the database, and the resultant expected transformation by the REST handler
|
||||||
'db' => [
|
'db' => [
|
||||||
|
@ -25,7 +23,7 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
|
||||||
'err_count' => 0,
|
'err_count' => 0,
|
||||||
'err_msg' => '',
|
'err_msg' => '',
|
||||||
'order_type' => 0,
|
'order_type' => 0,
|
||||||
'added' => 1495287354,
|
'added' => '2017-05-20 13:35:54',
|
||||||
'title' => 'First example feed',
|
'title' => 'First example feed',
|
||||||
'unread' => 50048,
|
'unread' => 50048,
|
||||||
],
|
],
|
||||||
|
@ -40,7 +38,7 @@ class TestNCNV1_2 extends \PHPUnit\Framework\TestCase {
|
||||||
'err_count' => 0,
|
'err_count' => 0,
|
||||||
'err_msg' => '',
|
'err_msg' => '',
|
||||||
'order_type' => 2,
|
'order_type' => 2,
|
||||||
'added' => 1495287354,
|
'added' => '2017-05-20 13:35:54',
|
||||||
'title' => 'Second example feed',
|
'title' => 'Second example feed',
|
||||||
'unread' => 23,
|
'unread' => 23,
|
||||||
],
|
],
|
||||||
|
|
|
@ -5,9 +5,7 @@ use JKingWeb\Arsse\REST\Request;
|
||||||
use JKingWeb\Arsse\REST\Response;
|
use JKingWeb\Arsse\REST\Response;
|
||||||
|
|
||||||
|
|
||||||
class TestNCNVersionDiscovery extends \PHPUnit\Framework\TestCase {
|
class TestNCNVersionDiscovery extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
$this->clearData();
|
$this->clearData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,7 @@ namespace JKingWeb\Arsse;
|
||||||
use Phake;
|
use Phake;
|
||||||
|
|
||||||
|
|
||||||
class TestAuthorization extends \PHPUnit\Framework\TestCase {
|
class TestAuthorization extends Test\AbstractTest {
|
||||||
use Test\Tools;
|
|
||||||
|
|
||||||
const USERS = [
|
const USERS = [
|
||||||
'user@example.com' => User\Driver::RIGHTS_NONE,
|
'user@example.com' => User\Driver::RIGHTS_NONE,
|
||||||
'user@example.org' => User\Driver::RIGHTS_NONE,
|
'user@example.org' => User\Driver::RIGHTS_NONE,
|
||||||
|
|
|
@ -3,8 +3,8 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
|
|
||||||
class TestUserInternalDriver extends \PHPUnit\Framework\TestCase {
|
class TestUserInternalDriver extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\User\CommonTests;
|
use Test\User\CommonTests;
|
||||||
|
|
||||||
const USER1 = "john.doe@example.com";
|
const USER1 = "john.doe@example.com";
|
||||||
const USER2 = "jane.doe@example.com";
|
const USER2 = "jane.doe@example.com";
|
||||||
|
|
|
@ -3,8 +3,8 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
|
|
||||||
class TestUserMockExternal extends \PHPUnit\Framework\TestCase {
|
class TestUserMockExternal extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\User\CommonTests;
|
use Test\User\CommonTests;
|
||||||
|
|
||||||
const USER1 = "john.doe@example.com";
|
const USER1 = "john.doe@example.com";
|
||||||
const USER2 = "jane.doe@example.com";
|
const USER2 = "jane.doe@example.com";
|
||||||
|
|
|
@ -3,8 +3,8 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse;
|
namespace JKingWeb\Arsse;
|
||||||
|
|
||||||
|
|
||||||
class TestUserMockInternal extends \PHPUnit\Framework\TestCase {
|
class TestUserMockInternal extends Test\AbstractTest {
|
||||||
use Test\Tools, Test\User\CommonTests;
|
use Test\User\CommonTests;
|
||||||
|
|
||||||
const USER1 = "john.doe@example.com";
|
const USER1 = "john.doe@example.com";
|
||||||
const USER2 = "jane.doe@example.com";
|
const USER2 = "jane.doe@example.com";
|
||||||
|
|
44
tests/lib/AbstractTest.php
Normal file
44
tests/lib/AbstractTest.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
namespace JKingWeb\Arsse\Test;
|
||||||
|
use JKingWeb\Arsse\Exception;
|
||||||
|
use JKingWeb\Arsse\Data;
|
||||||
|
|
||||||
|
abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
|
use \JKingWeb\Arsse\Misc\DateFormatter;
|
||||||
|
|
||||||
|
function assertException(string $msg = "", string $prefix = "", string $type = "Exception") {
|
||||||
|
if(func_num_args()) {
|
||||||
|
$class = \JKingWeb\Arsse\NS_BASE . ($prefix !== "" ? str_replace("/", "\\", $prefix) . "\\" : "") . $type;
|
||||||
|
$msgID = ($prefix !== "" ? $prefix . "/" : "") . $type. ".$msg";
|
||||||
|
if(array_key_exists($msgID, Exception::CODES)) {
|
||||||
|
$code = Exception::CODES[$msgID];
|
||||||
|
} else {
|
||||||
|
$code = 0;
|
||||||
|
}
|
||||||
|
$this->expectException($class);
|
||||||
|
$this->expectExceptionCode($code);
|
||||||
|
} else {
|
||||||
|
// expecting a standard PHP exception
|
||||||
|
$this->expectException(\Exception::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertTime($exp, $test) {
|
||||||
|
$exp = $this->dateTransform($exp, "unix");
|
||||||
|
$test = $this->dateTransform($test, "unix");
|
||||||
|
$this->assertSame($exp, $test);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearData(bool $loadLang = true): bool {
|
||||||
|
$r = new \ReflectionClass(\JKingWeb\Arsse\Data::class);
|
||||||
|
$props = array_keys($r->getStaticProperties());
|
||||||
|
foreach($props as $prop) {
|
||||||
|
Data::$$prop = null;
|
||||||
|
}
|
||||||
|
if($loadLang) {
|
||||||
|
Data::$l = new \JKingWeb\Arsse\Lang();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
namespace JKingWeb\Arsse\Test;
|
|
||||||
use JKingWeb\Arsse\Exception;
|
|
||||||
use JKingWeb\Arsse\Data;
|
|
||||||
|
|
||||||
trait Tools {
|
|
||||||
use \JKingWeb\Arsse\Misc\DateFormatter;
|
|
||||||
|
|
||||||
function assertException(string $msg, string $prefix = "", string $type = "Exception") {
|
|
||||||
$class = \JKingWeb\Arsse\NS_BASE . ($prefix !== "" ? str_replace("/", "\\", $prefix) . "\\" : "") . $type;
|
|
||||||
$msgID = ($prefix !== "" ? $prefix . "/" : "") . $type. ".$msg";
|
|
||||||
if(array_key_exists($msgID, Exception::CODES)) {
|
|
||||||
$code = Exception::CODES[$msgID];
|
|
||||||
} else {
|
|
||||||
$code = 0;
|
|
||||||
}
|
|
||||||
$this->expectException($class);
|
|
||||||
$this->expectExceptionCode($code);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertTime($exp, $test) {
|
|
||||||
$exp = $this->dateTransform($exp, "unix");
|
|
||||||
$test = $this->dateTransform($test, "unix");
|
|
||||||
$this->assertSame($exp, $test);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearData(bool $loadLang = true): bool {
|
|
||||||
$r = new \ReflectionClass(\JKingWeb\Arsse\Data::class);
|
|
||||||
$props = array_keys($r->getStaticProperties());
|
|
||||||
foreach($props as $prop) {
|
|
||||||
Data::$$prop = null;
|
|
||||||
}
|
|
||||||
if($loadLang) {
|
|
||||||
Data::$l = new \JKingWeb\Arsse\Lang();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,9 +2,9 @@
|
||||||
<phpunit
|
<phpunit
|
||||||
colors="true"
|
colors="true"
|
||||||
bootstrap="../bootstrap.php"
|
bootstrap="../bootstrap.php"
|
||||||
convertErrorsToExceptions="true"
|
convertErrorsToExceptions="false"
|
||||||
convertNoticesToExceptions="true"
|
convertNoticesToExceptions="false"
|
||||||
convertWarningsToExceptions="true"
|
convertWarningsToExceptions="false"
|
||||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||||
beStrictAboutOutputDuringTests="true"
|
beStrictAboutOutputDuringTests="true"
|
||||||
beStrictAboutTestSize="true"
|
beStrictAboutTestSize="true"
|
||||||
|
|
Loading…
Reference in a new issue