Refactored TestRunner and core

This commit is contained in:
Marcin Kurczewski 2014-05-15 09:39:48 +02:00
parent c7250ae0a9
commit 22b18bfbc9
8 changed files with 450 additions and 383 deletions

View file

@ -19,77 +19,175 @@ require_once $rootDir . 'lib' . DS . 'chibi-core' . DS . 'include.php';
require_once $rootDir . 'src' . DS . 'routes.php';
final class Core
{
private static $context;
private static $config;
static function getConfig()
{
return self::$config;
}
static function getContext()
{
return self::$context;
}
static function prepareConfig($testEnvironment)
{
//load config manually
global $config;
global $rootDir;
$configPaths = [];
if (!$testEnvironment)
{
$configPaths []= $rootDir . DS . 'data' . DS . 'config.ini';
$configPaths []= $rootDir . DS . 'data' . DS . 'local.ini';
}
else
{
$configPaths []= $rootDir . DS . 'tests' . DS . 'config.ini';
}
self::$config = new \Chibi\Config();
foreach ($configPaths as $path)
if (file_exists($path))
self::$config->loadIni($path);
self::$config->rootDir = $rootDir;
}
static function prepareEnvironment($testEnvironment)
{
//prepare context
global $startTime;
self::$context = new StdClass;
self::$context->startTime = $startTime;
$config = self::getConfig();
TransferHelper::createDirectory($config->main->filesPath);
TransferHelper::createDirectory($config->main->thumbsPath);
//extension sanity checks
$requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'gd', 'openssl', 'fileinfo'];
foreach ($requiredExtensions as $ext)
if (!extension_loaded($ext))
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);
if (\Chibi\Database::connected())
\Chibi\Database::disconnect();
if ($testEnvironment)
Auth::setCurrentUser(null);
Access::init();
Logger::init();
Mailer::init();
PropertyModel::init();
\Chibi\Database::connect(
$config->main->dbDriver,
TextHelper::absolutePath($config->main->dbLocation),
isset($config->main->dbUser) ? $config->main->dbUser : null,
isset($config->main->dbPass) ? $config->main->dbPass : null);
}
static function getDbVersion()
{
try
{
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
}
catch (Exception $e)
{
return [null, null];
}
if (strpos($dbVersion, '.') !== false)
{
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
}
elseif ($dbVersion)
{
$dbVersionMajor = $dbVersion;
$dbVersionMinor = null;
}
else
{
$dbVersionMajor = 0;
$dbVersionMinor = 0;
}
return [$dbVersionMajor, $dbVersionMinor];
}
static function upgradeDatabase()
{
$config = self::getConfig();
$upgradesPath = TextHelper::absolutePath($config->rootDir
. DS . 'src' . DS . 'Upgrades' . DS . $config->main->dbDriver);
$upgrades = glob($upgradesPath . DS . '*.sql');
natcasesort($upgrades);
foreach ($upgrades as $upgradePath)
{
preg_match('/(\d+)\.sql/', $upgradePath, $matches);
$upgradeVersionMajor = intval($matches[1]);
list ($dbVersionMajor, $dbVersionMinor) = self::getDbVersion();
if (($upgradeVersionMajor > $dbVersionMajor)
or ($upgradeVersionMajor == $dbVersionMajor and $dbVersionMinor !== null))
{
printf('%s: executing' . PHP_EOL, $upgradePath);
$upgradeSql = file_get_contents($upgradePath);
$upgradeSql = preg_replace('/^[ \t]+(.*);/m', '\0--', $upgradeSql);
$queries = preg_split('/;\s*[\r\n]+/s', $upgradeSql);
$queries = array_map('trim', $queries);
$queries = array_filter($queries);
$upgradeVersionMinor = 0;
foreach ($queries as $query)
{
$query = preg_replace('/\s*--(.*?)$/m', '', $query);
++ $upgradeVersionMinor;
if ($upgradeVersionMinor > $dbVersionMinor)
{
try
{
\Chibi\Database::execUnprepared(new \Chibi\Sql\RawStatement($query));
}
catch (Exception $e)
{
echo $e . PHP_EOL;
echo $query . PHP_EOL;
die;
}
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor . '.' . $upgradeVersionMinor);
}
}
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor);
}
else
{
printf('%s: no need to execute' . PHP_EOL, $upgradePath);
}
}
list ($dbVersionMajor, $dbVersionMinor) = self::getDbVersion();
printf('Database version: %d.%d' . PHP_EOL, $dbVersionMajor, $dbVersionMinor);
}
}
//legacy
function getConfig()
{
global $config;
return $config;
return Core::getConfig();
}
function getContext()
{
global $context;
return $context;
return Core::getContext();
}
function prepareConfig($testEnvironment)
{
//load config manually
global $config;
global $rootDir;
$configPaths = [];
if (!$testEnvironment)
{
$configPaths []= $rootDir . DS . 'data' . DS . 'config.ini';
$configPaths []= $rootDir . DS . 'data' . DS . 'local.ini';
}
else
{
$configPaths []= $rootDir . DS . 'tests' . DS . 'config.ini';
}
$config = new \Chibi\Config();
foreach ($configPaths as $path)
if (file_exists($path))
$config->loadIni($path);
$config->rootDir = $rootDir;
}
function prepareEnvironment($testEnvironment)
{
//prepare context
global $context;
global $startTime;
$context = new StdClass;
$context->startTime = $startTime;
$config = getConfig();
TransferHelper::createDirectory($config->main->filesPath);
TransferHelper::createDirectory($config->main->thumbsPath);
//extension sanity checks
$requiredExtensions = ['pdo', 'pdo_' . $config->main->dbDriver, 'gd', 'openssl', 'fileinfo'];
foreach ($requiredExtensions as $ext)
if (!extension_loaded($ext))
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);
if (\Chibi\Database::connected())
\Chibi\Database::disconnect();
if ($testEnvironment)
Auth::setCurrentUser(null);
Access::init();
Logger::init();
Mailer::init();
PropertyModel::init();
\Chibi\Database::connect(
$config->main->dbDriver,
TextHelper::absolutePath($config->main->dbLocation),
isset($config->main->dbUser) ? $config->main->dbUser : null,
isset($config->main->dbPass) ? $config->main->dbPass : null);
}
prepareConfig(false);
prepareEnvironment(false);
Core::prepareConfig(false);
Core::prepareEnvironment(false);

View file

@ -1,85 +0,0 @@
<?php
function getDbVersion()
{
try
{
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
}
catch (Exception $e)
{
return [null, null];
}
if (strpos($dbVersion, '.') !== false)
{
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
}
elseif ($dbVersion)
{
$dbVersionMajor = $dbVersion;
$dbVersionMinor = null;
}
else
{
$dbVersionMajor = 0;
$dbVersionMinor = 0;
}
return [$dbVersionMajor, $dbVersionMinor];
}
function upgradeDatabase()
{
$config = getConfig();
$upgradesPath = TextHelper::absolutePath($config->rootDir
. DS . 'src' . DS . 'Upgrades' . DS . $config->main->dbDriver);
$upgrades = glob($upgradesPath . DS . '*.sql');
natcasesort($upgrades);
foreach ($upgrades as $upgradePath)
{
preg_match('/(\d+)\.sql/', $upgradePath, $matches);
$upgradeVersionMajor = intval($matches[1]);
list ($dbVersionMajor, $dbVersionMinor) = getDbVersion();
if (($upgradeVersionMajor > $dbVersionMajor)
or ($upgradeVersionMajor == $dbVersionMajor and $dbVersionMinor !== null))
{
printf('%s: executing' . PHP_EOL, $upgradePath);
$upgradeSql = file_get_contents($upgradePath);
$upgradeSql = preg_replace('/^[ \t]+(.*);/m', '\0--', $upgradeSql);
$queries = preg_split('/;\s*[\r\n]+/s', $upgradeSql);
$queries = array_map('trim', $queries);
$queries = array_filter($queries);
$upgradeVersionMinor = 0;
foreach ($queries as $query)
{
$query = preg_replace('/\s*--(.*?)$/m', '', $query);
++ $upgradeVersionMinor;
if ($upgradeVersionMinor > $dbVersionMinor)
{
try
{
\Chibi\Database::execUnprepared(new \Chibi\Sql\RawStatement($query));
}
catch (Exception $e)
{
echo $e . PHP_EOL;
echo $query . PHP_EOL;
die;
}
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor . '.' . $upgradeVersionMinor);
}
}
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor);
}
else
{
printf('%s: no need to execute' . PHP_EOL, $upgradePath);
}
}
list ($dbVersionMajor, $dbVersionMinor) = getDbVersion();
printf('Database version: %d.%d' . PHP_EOL, $dbVersionMajor, $dbVersionMinor);
}

10
tests/ITestRunner.php Normal file
View file

@ -0,0 +1,10 @@
<?php
interface ITestRunner
{
public function setFilter($filter);
public function setTestsPath($testsPath);
public function setEnvironmentPrepareAction($callback);
public function setEnvironmentCleanAction($callback);
public function setTestWrapperAction($callback);
public function run();
}

8
tests/TestResult.php Normal file
View file

@ -0,0 +1,8 @@
<?php
class TestResult
{
public $duration;
public $success;
public $exception;
public $origin;
}

200
tests/TestRunner.php Normal file
View file

@ -0,0 +1,200 @@
<?php
class TestRunner implements ITestRunner
{
protected $filter = null;
protected $testsPath = __DIR__;
protected $environmentPrepareAction = null;
protected $environmentCleanAction = null;
protected $testWrapperAction = null;
public function setFilter($filter)
{
$this->filter = $filter;
}
public function setTestsPath($testsPath)
{
$this->testsPath = $testsPath;
}
public function setEnvironmentPrepareAction($callback)
{
$this->environmentPrepareAction = $callback;
}
public function setEnvironmentCleanAction($callback)
{
$this->environmentCleanAction = $callback;
}
public function setTestWrapperAction($callback)
{
$this->testWrapperAction = $callback;
}
public function run()
{
$testFixtures = $this->getTestFixtures($this->filter);
$this->runAll($testFixtures);
}
protected function getTestFixtures($filter)
{
$testFiles = [];
$path = $this->testsPath;
if (!$path)
$path = __DIR__;
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $fileName)
{
$path = $fileName->getPathname();
if (preg_match('/.*Test.php$/', $path))
$testFiles []= $path;
}
$testClasses = \Chibi\Util\Reflection::loadClasses($testFiles);
$classFilter = $filter;
$methodFilter = null;
if ($filter !== null and strpos($filter, '::') !== false)
{
list ($classFilter, $methodFilter) = explode('::', $filter);
}
if ($classFilter)
$testClasses = array_filter($testClasses, function($className) use ($classFilter)
{
return stripos($className, $classFilter) !== false;
});
$testFixtures = [];
foreach ($testClasses as $class)
{
$reflectionClass = new ReflectionClass($class);
if ($reflectionClass->isAbstract())
continue;
$testFixture = new StdClass;
$testFixture->class = $reflectionClass;
$testFixture->methods = [];
foreach ($reflectionClass->getMethods() as $method)
{
if ($methodFilter and stripos($method->name, $methodFilter) === false)
continue;
if (preg_match('/test/i', $method->name)
and $method->isPublic()
and $method->getNumberOfParameters() == 0)
{
$testFixture->methods []= $method;
}
}
if (!empty($testFixture->methods))
$testFixtures []= $testFixture;
}
return $testFixtures;
}
protected function runAll($testFixtures)
{
$startTime = microtime(true);
$testNumber = 0;
$resultPrinter = function($result) use (&$testNumber)
{
printf('%3d %-65s ', ++ $testNumber, $result->origin->class . '::' . $result->origin->name);
if ($result->success)
echo 'OK';
else
echo 'FAIL';
printf(' [%.03fs]' . PHP_EOL, $result->duration);
if ($result->exception)
{
echo '---' . PHP_EOL;
echo $result->exception->getMessage() . PHP_EOL;
echo $result->exception->getTraceAsString() . PHP_EOL;
echo '---' . PHP_EOL . PHP_EOL;
}
};
//run all the methods
echo 'Starting tests' . PHP_EOL;
$success = true;
foreach ($testFixtures as $className => $testFixture)
{
$results = $this->runTestFixture($testFixture, $resultPrinter);
foreach ($results as $result)
$success &= $result->success;
}
printf('%3s %-65s %s [%.03fs]' . PHP_EOL,
' ',
'All tests',
$success ? 'OK' : 'FAIL',
microtime(true) - $startTime);
return $success;
}
protected function runTestFixture($testFixture, $resultPrinter)
{
$instance = $testFixture->class->newInstance();
$instance->setup();
$results = [];
foreach ($testFixture->methods as $method)
{
$result = $this->runTest(function() use ($method, $instance)
{
$method->invoke($instance);
});
$result->origin = $method;
if ($resultPrinter !== null)
$resultPrinter($result);
$results []= $result;
}
$instance->teardown();
return $results;
}
protected function runTest($callback)
{
if ($this->environmentPrepareAction !== null)
call_user_func($this->environmentPrepareAction);
$startTime = microtime(true);
$result = new TestResult();
try
{
if ($this->testWrapperAction !== null)
call_user_func($this->testWrapperAction, $callback);
else
$callback();
$result->success = true;
}
catch (Exception $e)
{
$result->exception = $e;
$result->success = false;
}
finally
{
if ($this->environmentCleanAction !== null)
call_user_func($this->environmentCleanAction);
}
$endTime = microtime(true);
$result->duration = $endTime - $startTime;
return $result;
}
}

View file

@ -17,7 +17,7 @@ class TextHelperTest extends AbstractTest
}
}
public function testToIntegerOrNulll()
public function testToIntegerOrNull()
{
$this->assert->areEqual(1, TextHelper::toIntegerOrNull(1));
$this->assert->areEqual(1, TextHelper::toIntegerOrNull('1'));

View file

@ -1,247 +1,84 @@
<?php
require_once __DIR__ . '/../src/core.php';
require_once __DIR__ . '/../src/upgrade.php';
\Chibi\Autoloader::registerFileSystem(__DIR__);
class TestRunner
$dbPath = __DIR__ . '/db.sqlite';
function cleanDatabase()
{
protected $dbPath;
global $dbPath;
if (file_exists($dbPath))
unlink($dbPath);
}
public function __construct()
function removeTestFolders()
{
$folders =
[
realpath(Core::getConfig()->main->filesPath),
realpath(Core::getConfig()->main->thumbsPath),
realpath(dirname(Core::getConfig()->main->logsPath)),
];
foreach ($folders as $folder)
{
$this->dbPath = __DIR__ . '/db.sqlite';
}
if (!file_exists($folder))
continue;
public function run($options)
{
$cleanDatabase = (isset($options['c']) or isset($options['clean']));
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$folder,
FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST);
if (isset($options['f']))
$filter = $options['f'];
elseif (isset($options['filter']))
$filter = $options['filter'];
else
$filter = null;
if ($cleanDatabase)
$this->cleanDatabase();
try
foreach ($it as $path)
{
$this->resetEnvironment();
upgradeDatabase();
$testFixtures = $this->getTestFixtures($filter);
$this->runAll($testFixtures);
$path->isFile()
? unlink($path->getPathname())
: rmdir($path->getPathname());
}
finally
{
$this->removeTestFolders();
}
}
protected function cleanDatabase()
{
if (file_exists($this->dbPath))
unlink($this->dbPath);
}
protected function resetEnvironment()
{
$_SESSION = [];
prepareConfig(true);
getConfig()->main->dbDriver = 'sqlite';
getConfig()->main->dbLocation = $this->dbPath;
$this->removeTestFolders();
prepareEnvironment(true);
}
protected function removeTestFolders()
{
$folders =
[
realpath(getConfig()->main->filesPath),
realpath(getConfig()->main->thumbsPath),
realpath(dirname(getConfig()->main->logsPath)),
];
foreach ($folders as $folder)
{
if (!file_exists($folder))
continue;
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$folder,
FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST);
foreach ($it as $path)
{
$path->isFile()
? unlink($path->getPathname())
: rmdir($path->getPathname());
}
rmdir($folder);
}
}
protected function getTestFixtures($filter)
{
$testFiles = [];
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__)) as $fileName)
{
$path = $fileName->getPathname();
if (preg_match('/.*Test.php$/', $path))
$testFiles []= $path;
}
$testClasses = \Chibi\Util\Reflection::loadClasses($testFiles);
$classFilter = $filter;
$methodFilter = null;
if ($filter !== null and strpos($filter, '::') !== false)
{
list ($classFilter, $methodFilter) = explode('::', $filter);
}
if ($classFilter)
$testClasses = array_filter($testClasses, function($className) use ($classFilter)
{
return stripos($className, $classFilter) !== false;
});
$testFixtures = [];
foreach ($testClasses as $class)
{
$reflectionClass = new ReflectionClass($class);
if ($reflectionClass->isAbstract())
continue;
$testFixture = new StdClass;
$testFixture->class = $reflectionClass;
$testFixture->methods = [];
foreach ($reflectionClass->getMethods() as $method)
{
if ($methodFilter and stripos($method->name, $methodFilter) === false)
continue;
if (preg_match('/test/i', $method->name)
and $method->isPublic()
and $method->getNumberOfParameters() == 0)
{
$testFixture->methods []= $method;
}
}
if (!empty($testFixture->methods))
$testFixtures []= $testFixture;
}
return $testFixtures;
}
protected function runAll($testFixtures)
{
$startTime = microtime(true);
$testNumber = 0;
$resultPrinter = function($result) use (&$testNumber)
{
printf('%3d %-65s ', ++ $testNumber, $result->origin->class . '::' . $result->origin->name);
if ($result->success)
echo 'OK';
else
echo 'FAIL';
printf(' [%.03fs]' . PHP_EOL, $result->duration);
if ($result->exception)
{
echo '---' . PHP_EOL;
echo $result->exception->getMessage() . PHP_EOL;
echo $result->exception->getTraceAsString() . PHP_EOL;
echo '---' . PHP_EOL . PHP_EOL;
}
};
//run all the methods
echo 'Starting tests' . PHP_EOL;
$success = true;
foreach ($testFixtures as $className => $testFixture)
{
$results = $this->runTestFixture($testFixture, $resultPrinter);
foreach ($results as $result)
$success &= $result->success;
}
printf('%3s %-65s %s [%.03fs]' . PHP_EOL,
' ',
'All tests',
$success ? 'OK' : 'FAIL',
microtime(true) - $startTime);
return $success;
}
protected function runTestFixture($testFixture, $resultPrinter)
{
$instance = $testFixture->class->newInstance();
$instance->setup();
$results = [];
foreach ($testFixture->methods as $method)
{
$result = $this->runTest(function() use ($method, $instance)
{
$method->invoke($instance);
});
$result->origin = $method;
if ($resultPrinter !== null)
$resultPrinter($result);
$results []= $result;
}
$instance->teardown();
return $results;
}
protected function runTest($callback)
{
$this->resetEnvironment();
$startTime = microtime(true);
$result = new TestResult();
try
{
\Chibi\Database::rollback(function() use ($callback)
{
$callback();
});
$result->success = true;
}
catch (Exception $e)
{
$result->exception = $e;
$result->success = false;
}
$endTime = microtime(true);
$result->duration = $endTime - $startTime;
return $result;
rmdir($folder);
}
}
class TestResult
function resetEnvironment()
{
public $duration;
public $success;
public $exception;
public $origin;
global $dbPath;
$_SESSION = [];
Core::prepareConfig(true);
Core::getConfig()->main->dbDriver = 'sqlite';
Core::getConfig()->main->dbLocation = $dbPath;
removeTestFolders();
Core::prepareEnvironment(true);
}
$options = getopt('cf:', ['clean', 'filter:']);
(new TestRunner)->run($options);
$cleanDatabase = (isset($options['c']) or isset($options['clean']));
if (isset($options['f']))
$filter = $options['f'];
elseif (isset($options['filter']))
$filter = $options['filter'];
else
$filter = null;
resetEnvironment();
if ($cleanDatabase)
cleanDatabase();
resetEnvironment();
Core::upgradeDatabase();
$testRunner = new TestRunner;
$testRunner->setFilter($filter);
$testRunner->setEnvironmentPrepareAction(function() { resetEnvironment(); });
$testRunner->setEnvironmentCleanAction(function() { removeTestFolders(); });
$testRunner->setTestWrapperAction(function($callback)
{
\Chibi\Database::rollback(function() use ($callback)
{
$callback();
});
});
$testRunner->run($filter);

View file

@ -1,4 +1,3 @@
<?php
require_once 'src/core.php';
require_once 'src/upgrade.php';
upgradeDatabase();
Core::upgradeDatabase();