API Move extension management into test state

This commit is contained in:
Damian Mooyman 2017-06-13 15:00:00 +12:00
parent c66d433977
commit ca03395251
19 changed files with 188 additions and 133 deletions

View File

@ -5,5 +5,6 @@ SilverStripe\Core\Injector\Injector:
SilverStripe\Dev\SapphireTestState: SilverStripe\Dev\SapphireTestState:
properties: properties:
States: States:
extensions: %$SilverStripe\Dev\ExtensionTestState
flushable: %$SilverStripe\Dev\FlushableTestState flushable: %$SilverStripe\Dev\FlushableTestState
requirements: %$SilverStripe\View\Dev\RequirementsTestState requirements: %$SilverStripe\View\Dev\RequirementsTestState

View File

@ -35,14 +35,6 @@ class ClassManifest
*/ */
protected $cacheFactory; protected $cacheFactory;
/**
* Set if including test classes
*
* @see TestOnly
* @var bool
*/
protected $tests;
/** /**
* Cache to use, if caching. * Cache to use, if caching.
* Set to null if uncached. * Set to null if uncached.
@ -161,7 +153,7 @@ class ClassManifest
$this->implementors = $data['implementors']; $this->implementors = $data['implementors'];
$this->traits = $data['traits']; $this->traits = $data['traits'];
} else { } else {
$this->regenerate(); $this->regenerate($includeTests);
} }
} }
@ -356,8 +348,10 @@ class ClassManifest
/** /**
* Completely regenerates the manifest file. * Completely regenerates the manifest file.
*
* @param bool $includeTests
*/ */
public function regenerate() public function regenerate($includeTests)
{ {
$resets = array( $resets = array(
'classes', 'roots', 'children', 'descendants', 'interfaces', 'classes', 'roots', 'children', 'descendants', 'interfaces',
@ -373,7 +367,7 @@ class ClassManifest
$finder->setOptions(array( $finder->setOptions(array(
'name_regex' => '/^[^_].*\\.php$/', 'name_regex' => '/^[^_].*\\.php$/',
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'), 'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
'ignore_tests' => !$this->tests, 'ignore_tests' => !$includeTests,
'file_callback' => array($this, 'handleFile'), 'file_callback' => array($this, 'handleFile'),
)); ));
$finder->find($this->base); $finder->find($this->base);

View File

@ -23,6 +23,14 @@ class TestKernel extends AppKernel
$this->bootPHP(); $this->bootPHP();
} }
protected function bootPHP()
{
parent::bootPHP();
// Set default timezone consistently to avoid NZ-specific dependencies
date_default_timezone_set('UTC');
}
protected function getIncludeTests() protected function getIncludeTests()
{ {
return true; return true;

View File

@ -0,0 +1,122 @@
<?php
namespace SilverStripe\Dev;
use LogicException;
use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\DataObject;
/**
* Manages illegal and required extensions for sapphiretest
*/
class ExtensionTestState implements TestState
{
/**
* @var array
*/
protected $extensionsToReapply = [];
/**
* @var array
*/
protected $extensionsToRemove = [];
/**
* Called on setup
*
* @param SapphireTest $test
*/
public function setUp(SapphireTest $test)
{
}
public function tearDown(SapphireTest $test)
{
}
public function setUpOnce($class)
{
$isAltered = false;
/** @var string|SapphireTest $class */
/** @var string|DataObject $dataClass */
// Remove any illegal extensions that are present
foreach ($class::getIllegalExtensions() as $dataClass => $extensions) {
if (!class_exists($dataClass)) {
continue;
}
if ($extensions === '*') {
$extensions = $dataClass::get_extensions();
}
foreach ($extensions as $extension) {
if (!class_exists($extension) || !$dataClass::has_extension($extension)) {
continue;
}
if (!isset($this->extensionsToReapply[$dataClass])) {
$this->extensionsToReapply[$dataClass] = array();
}
$this->extensionsToReapply[$dataClass][] = $extension;
$dataClass::remove_extension($extension);
$isAltered = true;
}
}
// Add any required extensions that aren't present
foreach ($class::getRequiredExtensions() as $dataClass => $extensions) {
if (!class_exists($dataClass)) {
throw new LogicException("Test {$class} requires dataClass {$dataClass} which doesn't exist");
}
$this->extensionsToRemove[$dataClass] = array();
foreach ($extensions as $extension) {
$dataClass = Extension::get_classname_without_arguments($extension);
if (!class_exists($dataClass)) {
$self = static::class;
throw new LogicException("Test {$self} requires extension {$extension} which doesn't exist");
}
if (!$dataClass::has_extension($extension)) {
if (!isset($this->extensionsToRemove[$dataClass])) {
$this->extensionsToReapply[$dataClass] = array();
}
$this->extensionsToRemove[$dataClass][] = $extension;
$dataClass::add_extension($extension);
$isAltered = true;
}
}
}
// If we have made changes to the extensions present, then migrate the database schema.
if ($isAltered || $this->extensionsToReapply || $this->extensionsToRemove || $class::getExtraDataObjects()) {
DataObject::reset();
if (!SapphireTest::using_temp_db()) {
SapphireTest::create_temp_db();
}
SapphireTest::resetDBSchema(true);
}
// clear singletons, they're caching old extension info
// which is used in DatabaseAdmin->doBuild()
Injector::inst()->unregisterObjects(DataObject::class);
}
public function tearDownOnce($class)
{
// @todo: This isn't strictly necessary to restore extensions, but only to ensure that
// Object::$extra_methods is properly flushed. This should be replaced with a simple
// flush mechanism for each $class.
/** @var string|DataObject $dataClass */
// Remove extensions added for testing
foreach ($this->extensionsToRemove as $dataClass => $extensions) {
foreach ($extensions as $extension) {
$dataClass::remove_extension($extension);
}
}
// Reapply ones removed
foreach ($this->extensionsToReapply as $dataClass => $extensions) {
foreach ($extensions as $extension) {
$dataClass::add_extension($extension);
}
}
}
}

View File

@ -15,7 +15,6 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Session; use SilverStripe\Control\Session;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Extension;
use SilverStripe\Core\HTTPApplication; use SilverStripe\Core\HTTPApplication;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\TestKernel; use SilverStripe\Core\TestKernel;
@ -128,20 +127,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/ */
protected $backupGlobals = false; protected $backupGlobals = false;
/**
* Helper arrays for illegal_extensions/required_extensions code
*/
private static $extensions_to_reapply = [];
private static $extensions_to_remove = [];
/**
* Check flushables on setupBeforeClass()
*
* @var bool
*/
protected static $flushedFlushables = false;
/** /**
* Test application kernel. * Test application kernel.
* Note: This is always the root kernel. Use Injector to get the current kernel * Note: This is always the root kernel. Use Injector to get the current kernel
@ -151,6 +136,33 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/ */
protected static $kernel = null; protected static $kernel = null;
/**
* State management container for SapphireTest
*
* @var TestState
*/
protected static $state = null;
/**
* Gets illegal extensions for this class
*
* @return array
*/
public static function getIllegalExtensions()
{
return static::$illegal_extensions;
}
/**
* Gets required extensions for this class
*
* @return array
*/
public static function getRequiredExtensions()
{
return static::$required_extensions;
}
/** /**
* Check if test bootstrapping has been performed. Must not be relied on * Check if test bootstrapping has been performed. Must not be relied on
* outside of unit tests. * outside of unit tests.
@ -180,11 +192,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
return static::$fixture_file; return static::$fixture_file;
} }
/**
* @var TestState
*/
protected static $state = null;
/** /**
* Setup the test. * Setup the test.
* Always sets up in order: * Always sets up in order:
@ -286,74 +293,15 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/ */
public static function setUpBeforeClass() public static function setUpBeforeClass()
{ {
// Start tests
static::start(); static::start();
// Reset kernel
static::$kernel->reset(); static::$kernel->reset();
//nest config and injector for each suite so they are effectively sandboxed //nest config and injector for each suite so they are effectively sandboxed
Config::nest(); Config::nest();
Injector::nest(); Injector::nest();
$isAltered = false;
// Remove any illegal extensions that are present
foreach (static::$illegal_extensions as $class => $extensions) {
if (!class_exists($class)) {
continue;
}
if ($extensions === '*') {
$extensions = $class::get_extensions();
}
foreach ($extensions as $extension) {
if (!class_exists($extension) || !$class::has_extension($extension)) {
continue;
}
if (!isset(self::$extensions_to_reapply[$class])) {
self::$extensions_to_reapply[$class] = array();
}
self::$extensions_to_reapply[$class][] = $extension;
$class::remove_extension($extension);
$isAltered = true;
}
}
// Add any required extensions that aren't present
foreach (static::$required_extensions as $class => $extensions) {
if (!class_exists($class)) {
$self = static::class;
throw new LogicException("Test {$self} requires class {$class} which doesn't exist");
}
self::$extensions_to_remove[$class] = array();
foreach ($extensions as $extension) {
$extensionClass = Extension::get_classname_without_arguments($extension);
if (!class_exists($extensionClass)) {
$self = static::class;
throw new LogicException("Test {$self} requires extension {$extension} which doesn't exist");
}
if (!$class::has_extension($extension)) {
if (!isset(self::$extensions_to_remove[$class])) {
self::$extensions_to_reapply[$class] = array();
}
self::$extensions_to_remove[$class][] = $extension;
$class::add_extension($extension);
$isAltered = true;
}
}
}
// If we have made changes to the extensions present, then migrate the database schema.
if ($isAltered || self::$extensions_to_reapply || self::$extensions_to_remove || static::getExtraDataObjects()) {
DataObject::reset();
if (!self::using_temp_db()) {
self::create_temp_db();
}
static::resetDBSchema(true);
}
// clear singletons, they're caching old extension info
// which is used in DatabaseAdmin->doBuild()
Injector::inst()->unregisterObjects(DataObject::class);
// Set default timezone consistently to avoid NZ-specific dependencies
date_default_timezone_set('UTC');
// Call state helpers // Call state helpers
static::$state->setUpOnce(static::class); static::$state->setUpOnce(static::class);
@ -374,36 +322,12 @@ class SapphireTest extends PHPUnit_Framework_TestCase
// Call state helpers // Call state helpers
static::$state->tearDownOnce(static::class); static::$state->tearDownOnce(static::class);
// If we have made changes to the extensions present, then migrate the database schema.
if (self::$extensions_to_reapply || self::$extensions_to_remove) {
// @todo: This isn't strictly necessary to restore extensions, but only to ensure that
// Object::$extra_methods is properly flushed. This should be replaced with a simple
// flush mechanism for each $class.
//
// Remove extensions added for testing
foreach (self::$extensions_to_remove as $class => $extensions) {
foreach ($extensions as $extension) {
$class::remove_extension($extension);
}
}
// Reapply ones removed
foreach (self::$extensions_to_reapply as $class => $extensions) {
foreach ($extensions as $extension) {
$class::add_extension($extension);
}
}
}
//unnest injector / config now that the test suite is over //unnest injector / config now that the test suite is over
// this will reset all the extensions on the object too (see setUpBeforeClass) // this will reset all the extensions on the object too (see setUpBeforeClass)
Injector::unnest(); Injector::unnest();
Config::unnest(); Config::unnest();
$extraDataObjects = static::getExtraDataObjects(); static::resetDBSchema();
if (!empty(self::$extensions_to_reapply) || !empty(self::$extensions_to_remove) || !empty($extraDataObjects)) {
static::resetDBSchema();
}
static::$kernel->reset(); static::$kernel->reset();
} }
@ -1013,6 +937,8 @@ class SapphireTest extends PHPUnit_Framework_TestCase
/** /**
* Returns true if we are currently using a temporary database * Returns true if we are currently using a temporary database
*
* @return bool
*/ */
public static function using_temp_db() public static function using_temp_db()
{ {
@ -1021,6 +947,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase
return 1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbConn->getSelectedDatabase()); return 1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbConn->getSelectedDatabase());
} }
/**
* Destroy all temp databases
*/
public static function kill_temp_db() public static function kill_temp_db()
{ {
// Delete our temporary database // Delete our temporary database
@ -1234,7 +1163,6 @@ class SapphireTest extends PHPUnit_Framework_TestCase
*/ */
protected $cache_generatedMembers = array(); protected $cache_generatedMembers = array();
/** /**
* Test against a theme. * Test against a theme.
* *
@ -1291,7 +1219,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
* Return all extra objects to scaffold for this test * Return all extra objects to scaffold for this test
* @return array * @return array
*/ */
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return static::$extra_dataobjects; return static::$extra_dataobjects;
} }
@ -1301,7 +1229,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase
* *
* @return array * @return array
*/ */
protected static function getExtraControllers() public static function getExtraControllers()
{ {
return static::$extra_controllers; return static::$extra_controllers;
} }

View File

@ -4,8 +4,10 @@ namespace SilverStripe\Core\Tests;
use SilverStripe\Control\Controller; use SilverStripe\Control\Controller;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Extension; use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Core\Tests\ObjectTest\BaseObject; use SilverStripe\Core\Tests\ObjectTest\BaseObject;
use SilverStripe\Core\Tests\ObjectTest\ExtendTest1; use SilverStripe\Core\Tests\ObjectTest\ExtendTest1;
use SilverStripe\Core\Tests\ObjectTest\ExtendTest2; use SilverStripe\Core\Tests\ObjectTest\ExtendTest2;

View File

@ -19,7 +19,7 @@ class FormFactoryTest extends SapphireTest
protected static $fixture_file = 'FormFactoryTest.yml'; protected static $fixture_file = 'FormFactoryTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
// Prevent setup breaking if versioned module absent // Prevent setup breaking if versioned module absent
if (class_exists(Versioned::class)) { if (class_exists(Versioned::class)) {

View File

@ -27,7 +27,7 @@ class DataListTest extends SapphireTest
// Borrow the model from DataObjectTest // Borrow the model from DataObjectTest
protected static $fixture_file = 'DataObjectTest.yml'; protected static $fixture_file = 'DataObjectTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -15,7 +15,7 @@ class DataObjectLazyLoadingTest extends SapphireTest
'DataObjectTest.yml', 'DataObjectTest.yml',
); );
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -57,7 +57,7 @@ class DataObjectTest extends SapphireTest
DataObjectTest\RelationChildSecond::class, DataObjectTest\RelationChildSecond::class,
); );
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -11,7 +11,7 @@ class HasManyListTest extends SapphireTest
// Borrow the model from DataObjectTest // Borrow the model from DataObjectTest
protected static $fixture_file = 'DataObjectTest.yml'; protected static $fixture_file = 'DataObjectTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -16,7 +16,7 @@ class HierarchyTest extends SapphireTest
HierarchyTest\HideTestSubObject::class, HierarchyTest\HideTestSubObject::class,
); );
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
// Prevent setup breaking if versioned module absent // Prevent setup breaking if versioned module absent
if (class_exists(Versioned::class)) { if (class_exists(Versioned::class)) {

View File

@ -20,7 +20,7 @@ class ManyManyListTest extends SapphireTest
ManyManyListTest\Product::class, ManyManyListTest\Product::class,
]; ];
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -14,7 +14,7 @@ class MapTest extends SapphireTest
// Borrow the model from DataObjectTest // Borrow the model from DataObjectTest
protected static $fixture_file = 'DataObjectTest.yml'; protected static $fixture_file = 'DataObjectTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -22,7 +22,7 @@ class MarkedSetTest extends SapphireTest
HierarchyTest\HideTestSubObject::class, HierarchyTest\HideTestSubObject::class,
); );
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
// Prevent setup breaking if versioned module absent // Prevent setup breaking if versioned module absent
if (class_exists(Versioned::class)) { if (class_exists(Versioned::class)) {

View File

@ -18,7 +18,7 @@ class PaginatedListTest extends SapphireTest
protected static $fixture_file = 'DataObjectTest.yml'; protected static $fixture_file = 'DataObjectTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -19,7 +19,7 @@ class PolymorphicHasManyListTest extends SapphireTest
// Borrow the model from DataObjectTest // Borrow the model from DataObjectTest
protected static $fixture_file = 'DataObjectTest.yml'; protected static $fixture_file = 'DataObjectTest.yml';
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return array_merge( return array_merge(
DataObjectTest::$extra_data_objects, DataObjectTest::$extra_data_objects,

View File

@ -19,7 +19,7 @@ class SSViewerCacheBlockTest extends SapphireTest
SSViewerCacheBlockTest\TestModel::class SSViewerCacheBlockTest\TestModel::class
); );
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
$classes = parent::getExtraDataObjects(); $classes = parent::getExtraDataObjects();

View File

@ -50,7 +50,7 @@ trait i18nTestManifest
*/ */
protected $moduleManifests = 0; protected $moduleManifests = 0;
protected static function getExtraDataObjects() public static function getExtraDataObjects()
{ {
return [ return [
TestDataObject::class, TestDataObject::class,