-
-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #311 from hosteurope/feature/310_add_sqlite_runner
#310 Implement new SqliteRunner which uses sqlite to dispatch tests.
- Loading branch information
Showing
16 changed files
with
622 additions
and
284 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
if (!isset($argv[1])) { | ||
fwrite(STDERR, 'First parameter for sqlite database file required.'); | ||
exit(1); | ||
} | ||
|
||
$db = new PDO('sqlite:' . $argv[1]); | ||
|
||
// git working copy | ||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) { | ||
require_once __DIR__ . '/../vendor/autoload.php'; | ||
} | ||
// Composer installation | ||
if (file_exists(__DIR__ . '/../../../autoload.php')) { | ||
require_once __DIR__ . '/../../../autoload.php'; | ||
} | ||
|
||
while ($test = $db->query('SELECT id, command FROM tests WHERE reserved_by_process_id IS NULL ORDER BY file_name LIMIT 1')->fetch()) { | ||
$statement = $db->prepare('UPDATE tests SET reserved_by_process_id = :procId WHERE id = :id AND reserved_by_process_id IS NULL'); | ||
$statement->execute([ | ||
':procId' => getmypid(), | ||
':id' => $test['id'], | ||
]); | ||
|
||
if ($statement->rowCount() !== 1) { | ||
// Seems like this test has already been reserved. Continue to the next one. | ||
continue; | ||
} | ||
|
||
try { | ||
if (!preg_match_all('/\'([^\']*)\'[ ]?/', $test['command'], $arguments)) { | ||
throw new \Exception("Failed to parse arguments from command line: \"" . $test['command'] . "\""); | ||
} | ||
$_SERVER['argv'] = $arguments[1]; | ||
|
||
PHPUnit\TextUI\Command::main(false); | ||
} finally { | ||
$db->prepare('UPDATE tests SET completed = 1 WHERE id = :id') | ||
->execute([':id' => $test['id']]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ParaTest\Runners\PHPUnit; | ||
|
||
use Exception; | ||
use ParaTest\Runners\PHPUnit\Worker\SqliteWorker; | ||
use PDO; | ||
use RuntimeException; | ||
|
||
class SqliteRunner extends WrapperRunner | ||
{ | ||
/** @var PDO */ | ||
private $db; | ||
|
||
/** @var string */ | ||
private $dbFileName = null; | ||
|
||
public function __construct(array $opts = []) | ||
{ | ||
parent::__construct($opts); | ||
|
||
$this->dbFileName = (string)($opts['database'] ?? tempnam(sys_get_temp_dir(), 'paratest_db_')); | ||
$this->db = new PDO('sqlite:' . $this->dbFileName); | ||
} | ||
|
||
public function run() | ||
{ | ||
$this->initialize(); | ||
|
||
$this->createTable(); | ||
$this->assignAllPendingTests(); | ||
$this->startWorkers(); | ||
$this->waitForAllToFinish(); | ||
$this->complete(); | ||
$this->checkIfWorkersCrashed(); | ||
} | ||
|
||
/** | ||
* Start all workers. | ||
*/ | ||
protected function startWorkers(): void | ||
{ | ||
$wrapper = realpath(__DIR__ . '/../../../bin/phpunit-sqlite-wrapper'); | ||
|
||
for ($i = 1; $i <= $this->options->processes; ++$i) { | ||
$worker = new SqliteWorker($this->dbFileName); | ||
if ($this->options->noTestTokens) { | ||
$token = null; | ||
$uniqueToken = null; | ||
} else { | ||
$token = $i; | ||
$uniqueToken = uniqid(); | ||
} | ||
$worker->start($wrapper, $token, $uniqueToken); | ||
$this->workers[] = $worker; | ||
} | ||
} | ||
|
||
/** | ||
* Wait for all workers to complete their tests and print output. | ||
*/ | ||
private function waitForAllToFinish(): void | ||
{ | ||
do { | ||
foreach ($this->workers as $key => $worker) { | ||
if (!$worker->isRunning()) { | ||
unset($this->workers[$key]); | ||
} | ||
} | ||
usleep(10000); | ||
$this->printOutput(); | ||
} while (count($this->workers) > 0); | ||
} | ||
|
||
/** | ||
* Initialize test queue table. | ||
* | ||
* @throws Exception | ||
*/ | ||
private function createTable(): void | ||
{ | ||
$statement = 'CREATE TABLE tests ( | ||
id INTEGER PRIMARY KEY, | ||
command TEXT NOT NULL UNIQUE, | ||
file_name TEXT NOT NULL, | ||
reserved_by_process_id INTEGER, | ||
completed INTEGER DEFAULT 0 | ||
)'; | ||
|
||
if ($this->db->exec($statement) === false) { | ||
throw new Exception('Error while creating sqlite database table: ' . $this->db->errorCode()); | ||
} | ||
} | ||
|
||
/** | ||
* Push all tests onto test queue. | ||
*/ | ||
private function assignAllPendingTests(): void | ||
{ | ||
foreach ($this->pending as $fileName => $test) { | ||
$this->db->prepare('INSERT INTO tests (command, file_name) VALUES (:command, :fileName)') | ||
->execute([ | ||
':command' => $test->command($this->options->phpunit, $this->options->filtered), | ||
':fileName' => $fileName | ||
]); | ||
} | ||
} | ||
|
||
/** | ||
* Loop through all completed tests and print their output. | ||
*/ | ||
private function printOutput(): void | ||
{ | ||
foreach ($this->db->query('SELECT id, file_name FROM tests WHERE completed = 1')->fetchAll() as $test) { | ||
$this->printer->printFeedback($this->pending[$test['file_name']]); | ||
$this->db->prepare('DELETE FROM tests WHERE id = :id')->execute([ | ||
'id' => $test['id'] | ||
]); | ||
} | ||
} | ||
|
||
public function __destruct() | ||
{ | ||
if ($this->db !== null) { | ||
unset($this->db); | ||
unlink($this->dbFileName); | ||
} | ||
} | ||
|
||
/** | ||
* Make sure that all tests were executed successfully. | ||
*/ | ||
private function checkIfWorkersCrashed(): void | ||
{ | ||
if ($this->db->query('SELECT COUNT(id) FROM tests')->fetchColumn(0) === "0") { | ||
return; | ||
} | ||
|
||
throw new RuntimeException( | ||
'Some workers have crashed.' . PHP_EOL | ||
. '----------------------' . PHP_EOL | ||
. 'All workers have quit, but some tests are still to be executed.' . PHP_EOL | ||
. 'This may be the case if some tests were killed forcefully (for example, using exit()).' . PHP_EOL | ||
. '----------------------' . PHP_EOL | ||
. 'Failed test command(s):' . PHP_EOL | ||
. '----------------------' . PHP_EOL | ||
. implode(PHP_EOL, $this->db->query('SELECT command FROM tests')->fetchAll(PDO::FETCH_COLUMN)) | ||
); | ||
} | ||
} |
Oops, something went wrong.