Commit e1a9e49c authored by Ivan Chepurnyi's avatar Ivan Chepurnyi

+ Shared Fixtures

! Improved performance of EAV and scope fixtures loading
parent f3bb0dac
...@@ -48,6 +48,8 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -48,6 +48,8 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
const REGISTRY_PATH_LAYOUT_SINGLETON = '_singleton/core/layout'; const REGISTRY_PATH_LAYOUT_SINGLETON = '_singleton/core/layout';
const REGISTRY_PATH_DESIGN_PACKAGE_SINGLETON = '_singleton/core/design_package'; const REGISTRY_PATH_DESIGN_PACKAGE_SINGLETON = '_singleton/core/design_package';
const REGISTRY_PATH_SHARED_STORAGE = 'test_suite_shared_storage';
const XML_PATH_LAYOUT_MODEL_FOR_TEST = 'phpunit/suite/layout/model'; const XML_PATH_LAYOUT_MODEL_FOR_TEST = 'phpunit/suite/layout/model';
const XML_PATH_DESIGN_PACKAGE_MODEL_FOR_TEST = 'phpunit/suite/design/package/model'; const XML_PATH_DESIGN_PACKAGE_MODEL_FOR_TEST = 'phpunit/suite/design/package/model';
...@@ -221,6 +223,8 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App ...@@ -221,6 +223,8 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
$designPackageModel); $designPackageModel);
$this->loadAreaPart(self::AREA_TEST, self::AREA_PART_EVENTS); $this->loadAreaPart(self::AREA_TEST, self::AREA_PART_EVENTS);
$this->replaceRegistry(self::REGISTRY_PATH_SHARED_STORAGE, new Varien_Object());
return $this; return $this;
} }
......
...@@ -28,12 +28,18 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config ...@@ -28,12 +28,18 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
const CHANGE_ME = '[change me]'; const CHANGE_ME = '[change me]';
/** /**
* Scope snapshot without applied configurations, * Scope snapshot with different levels of saving configuration
* It is used for proper store/website/default loading on per store basis
* *
* @var Mage_Core_Model_Config_Base * @var Mage_Core_Model_Config_Base
*/ */
protected $_scopeSnapshot = null; protected $_scopeSnapshot = array();
/**
* Scope snapshot for a particular test case
*
* @var Mage_Core_Model_Config_Base
*/
protected $_localScopeSnapshot = null;
/** /**
* List of replaced instance creation * List of replaced instance creation
...@@ -51,7 +57,7 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config ...@@ -51,7 +57,7 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
{ {
if ($this->_isLocalConfigLoaded if ($this->_isLocalConfigLoaded
&& Mage::isInstalled() && Mage::isInstalled()
&& $this->_scopeSnapshot === null) { && empty($this->_scopeSnapshot)) {
$this->saveScopeSnapshot(); $this->saveScopeSnapshot();
} }
parent::loadDb(); parent::loadDb();
...@@ -141,43 +147,39 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config ...@@ -141,43 +147,39 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
*/ */
public function loadScopeSnapshot() public function loadScopeSnapshot()
{ {
if ($this->_scopeSnapshot === null) { if (empty($this->_scopeSnapshot)) {
throw new RuntimeException('Cannot load scope snapshot, because it was not saved before'); throw new RuntimeException('Cannot load scope snapshot, because it was not saved before');
} }
$scopeNode = $this->_scopeSnapshot->getNode(); $scope = clone end($this->_scopeSnapshot);
foreach ($scopeNode->children() as $nodeName => $values) {
// Remove somehow modified before xml node
unset($this->getNode()->$nodeName);
// Add saved snapshot of configuration node
$this->getNode()->addChild($nodeName);
$this->getNode()->$nodeName->extend($values);
}
$this->_xml = $scope;
return $this; return $this;
} }
/** /**
* Saves current configuration snapshot, * Flushes current scope snapshot level if it is not the last one
* for pussible restoring in feature
* *
* @param array $nodesToSave list of nodes for saving data, by default it is 'default', 'webistes', 'stores'
* @return EcomDev_PHPUnit_Model_Config * @return EcomDev_PHPUnit_Model_Config
*/ */
public function saveScopeSnapshot($nodesToSave = array('default', 'websites', 'stores')) public function flushScopeSnapshot()
{ {
$this->_scopeSnapshot = clone $this->_prototype; if (count($this->_scopeSnapshot) > 1) {
$this->_scopeSnapshot->loadString('<config />'); array_pop($this->_scopeSnapshot);
$scopeNode = $this->_scopeSnapshot->getNode(); memory_get_usage(); // Memory GC
foreach ($nodesToSave as $node) {
$scopeNode->addChild($node);
$scopeNode->{$node}->extend(
$this->getNode($node),
true
);
} }
return $this;
}
/**
* Saves current configuration snapshot,
* for pussible restoring in feature
*
* @return EcomDev_PHPUnit_Model_Config
*/
public function saveScopeSnapshot()
{
$this->_scopeSnapshot[] = clone $this->_xml;
return $this; return $this;
} }
......
...@@ -25,6 +25,9 @@ ...@@ -25,6 +25,9 @@
*/ */
interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_Test_Loadable_Interface interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_Test_Loadable_Interface
{ {
const SCOPE_LOCAL = 'local';
const SCOPE_SHARED = 'shared';
/** /**
* Sets fixture options * Sets fixture options
* *
...@@ -32,4 +35,74 @@ interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_ ...@@ -32,4 +35,74 @@ interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_
* @return EcomDev_PHPUnit_Model_Fixture_Interface * @return EcomDev_PHPUnit_Model_Fixture_Interface
*/ */
public function setOptions(array $options); public function setOptions(array $options);
/**
* Sets storage for fixutures
*
* @param Varien_Object $storage
* @return EcomDev_PHPUnit_Model_Fixture_Interface
*/
public function setStorage(Varien_Object $storage);
/**
* Retrieve fixture storage
*
* @return Varien_Object
*/
public function getStorage();
/**
* Retrieves storage data for a particular fixture scope
*
* @param string $key
* @param string|null $scope
*/
public function getStorageData($key, $scope = null);
/**
* Sets storage data for a particular fixture scope
*
* @param string $key
* @param mixed $value
* @param string|null $scope
*/
public function setStorageData($key, $value, $scope = null);
/**
* Returns current fixture scope
*
* @return string
*/
public function getScope();
/**
* Sets current fixture scope
*
*
* @param string $scope EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_LOCAL|EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_SHARED
*/
public function setScope($scope);
/**
* Check that current fixture scope is equal to SCOPE_SHARED
*
* @return boolean
*/
public function isScopeShared();
/**
* Check that current fixture scope is equal to SCOPE_LOCAL
*
* @return boolean
*/
public function isScopeLocal();
/**
* Loads fixture files from test class annotations
*
* @param string $className
* @return EcomDev_PHPUnit_Model_Fixture_Interface
*/
public function loadForClass($className);
} }
\ No newline at end of file
...@@ -58,7 +58,7 @@ class EcomDev_PHPUnit_Model_Mysql4_Fixture extends Mage_Core_Model_Mysql4_Abstra ...@@ -58,7 +58,7 @@ class EcomDev_PHPUnit_Model_Mysql4_Fixture extends Mage_Core_Model_Mysql4_Abstra
$records[] = $this->_getTableRecord($row, $tableColumns); $records[] = $this->_getTableRecord($row, $tableColumns);
} }
$this->_getWriteAdapter()->insertMultiple( $this->_getWriteAdapter()->insertOnDuplicate(
$this->getTable($tableEntity), $this->getTable($tableEntity),
$records $records
); );
......
...@@ -36,6 +36,13 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev ...@@ -36,6 +36,13 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
*/ */
protected $_options = array(); protected $_options = array();
/**
* Fixture model
*
* @var EcomDev_PHPUnit_Model_Fixture_Interface
*/
protected $_fixture = null;
/** /**
* Retrieve required indexers for re-building * Retrieve required indexers for re-building
* *
...@@ -46,6 +53,17 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev ...@@ -46,6 +53,17 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
return $this->_requiredIndexers; return $this->_requiredIndexers;
} }
/**
* Sets fixture model to EAV loader
*
* @param EcomDev_PHPUnit_Model_Fixture_Interface $fixture
*/
public function setFixture($fixture)
{
$this->_fixture = $fixture;
return $this;
}
/** /**
* Set fixture options * Set fixture options
* *
...@@ -95,8 +113,19 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev ...@@ -95,8 +113,19 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
*/ */
public function loadEntity($entityType, $values) public function loadEntity($entityType, $values)
{ {
$originalRequiredIndexers = $this->_requiredIndexers;
if (!empty($this->_options['addRequiredIndex'])) {
foreach ($this->_options['addRequiredIndex'] as $data) {
if (preg_match('/^([a-z0-9_\\-])+\\s+([a-z0-9_\\-])\s*$/i', $data, $match)
&& $match[1] == $entityType) {
$this->_requiredIndexers[] = $match[2];
}
}
}
$entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType); $entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType);
$entityTableColumns = $this->_getWriteAdapter()->describeTable( $entityTableColumns = $this->_getWriteAdapter()->describeTable(
$this->getTable($entityTypeModel->getEntityTable()) $this->getTable($entityTypeModel->getEntityTable())
); );
...@@ -197,6 +226,8 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev ...@@ -197,6 +226,8 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
} }
} }
// Restoring original required indexers for making tests isolated
$this->_requiredIndexers = $originalRequiredIndexers;
return $this; return $this;
} }
......
...@@ -25,7 +25,6 @@ class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_P ...@@ -25,7 +25,6 @@ class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_P
{ {
protected $_requiredIndexers = array( protected $_requiredIndexers = array(
'cataloginventory_stock', 'cataloginventory_stock',
'catalog_product_flat',
'catalog_product_attribute', 'catalog_product_attribute',
'catalog_product_price' 'catalog_product_price'
); );
......
...@@ -271,12 +271,28 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -271,12 +271,28 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
* @return array * @return array
*/ */
public function getAnnotationByName($name, $sources = 'method') public function getAnnotationByName($name, $sources = 'method')
{
return self::getAnnotationByNameFromClass(get_class($this), $name, $sources, $this->getName(false));
}
/**
* Retrieves annotation by its name from different sources (class, method) based on meta information
*
* @param string $className
* @param string $name annotation name
* @param array|string $sources
* @param string $testName test method name
*/
public static function getAnnotationByNameFromClass($className, $name, $sources = 'class', $testName = '')
{ {
if (is_string($sources)) { if (is_string($sources)) {
$sources = array($sources); $sources = array($sources);
} }
$allAnnotations = $this->getAnnotations(); $allAnnotations = PHPUnit_Util_Test::parseTestMethodAnnotations(
$className, $testName
);
$annotation = array(); $annotation = array();
// Walkthrough sources for annotation retrieval // Walkthrough sources for annotation retrieval
...@@ -627,12 +643,30 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -627,12 +643,30 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
* *
* @return EcomDev_PHPUnit_Model_Fixture * @return EcomDev_PHPUnit_Model_Fixture
*/ */
protected function getFixture() protected static function getFixture()
{ {
return Mage::getSingleton($this->getLoadableClassAlias( $fixture = Mage::getSingleton(
'fixture', self::getLoadableClassAlias(
self::XML_PATH_DEFAULT_FIXTURE_MODEL 'fixture',
));; self::XML_PATH_DEFAULT_FIXTURE_MODEL
)
);
if (!$fixture instanceof EcomDev_PHPUnit_Model_Fixture_Interface) {
throw new RuntimeException('Fixture model should implement EcomDev_PHPUnit_Model_Fixture_Interface interface');
}
$storage = Mage::registry(EcomDev_PHPUnit_Model_App::REGISTRY_PATH_SHARED_STORAGE);
if (!$storage instanceof Varien_Object) {
throw new RuntimeException('Fixture storage object was not initialized during test application setup');
}
$fixture->setStorage(
Mage::registry(EcomDev_PHPUnit_Model_App::REGISTRY_PATH_SHARED_STORAGE)
);
return $fixture;
} }
/** /**
...@@ -642,10 +676,12 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -642,10 +676,12 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/ */
protected function getExpectation() protected function getExpectation()
{ {
return Mage::getSingleton($this->getLoadableClassAlias( return Mage::getSingleton(
'expectation', self::getLoadableClassAlias(
self::XML_PATH_DEFAULT_EXPECTATION_MODEL 'expectation',
)); self::XML_PATH_DEFAULT_EXPECTATION_MODEL
)
);
} }
...@@ -656,14 +692,17 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -656,14 +692,17 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
* @param string $type * @param string $type
* @param string $configPath * @param string $configPath
*/ */
protected function getLoadableClassAlias($type, $configPath) protected static function getLoadableClassAlias($type, $configPath)
{ {
$annotationValue = $this->getAnnotationByName($type .'Model' , 'class'); $annotationValue = self::getAnnotationByNameFromClass(
get_called_class(),
$type .'Model'
);
if (current($annotationValue)) { if (current($annotationValue)) {
$classAlias = current($annotationValue); $classAlias = current($annotationValue);
} else { } else {
$classAlias = $this->app()->getConfig()->getNode($configPath); $classAlias = self::app()->getConfig()->getNode($configPath);
} }
return $classAlias; return $classAlias;
...@@ -699,12 +738,25 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -699,12 +738,25 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$name = $this->getName(false); $name = $this->getName(false);
} }
return self::getYamlFilePathByClass(get_called_class(), $type, $name);
}
/**
* Loads YAML file from directory inside of the unit test class
*
* @param string $className class name for looking fixture files
* @param string $type type of YAML data (fixtures,expectations,dataproviders)
* @param string $name the file name for loading
* @return string|boolean
*/
public static function getYamlFilePathByClass($className, $type, $name)
{
if (strrpos($name, '.yaml') !== strlen($name) - 5) { if (strrpos($name, '.yaml') !== strlen($name) - 5) {
$name .= '.yaml'; $name .= '.yaml';
} }
$classFileObject = new SplFileInfo( $classFileObject = new SplFileInfo(
EcomDev_Utils_Reflection::getRelflection($this)->getFileName() EcomDev_Utils_Reflection::getRelflection($className)->getFileName()
); );
$filePath = $classFileObject->getPath() . DS $filePath = $classFileObject->getPath() . DS
...@@ -726,15 +778,38 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -726,15 +778,38 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/ */
protected function setUp() protected function setUp()
{ {
$this->getFixture()->loadByTestCase($this); self::getFixture()
->setScope(EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_LOCAL)
->loadByTestCase($this);
$annotations = $this->getAnnotations(); $annotations = $this->getAnnotations();
$this->getFixture()->setOptions($annotations['method']); self::getFixture()
$this->getFixture()->apply(); ->setOptions($annotations['method'])
->apply();
$this->app()->resetDispatchedEvents(); $this->app()->resetDispatchedEvents();
parent::setUp(); parent::setUp();
} }
/**
* Initializes test environment for subset of tests
*
*/
public static function setUpBeforeClass()
{
self::getFixture()
->setScope(EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_SHARED)
->loadForClass(get_called_class());
$annotations = PHPUnit_Util_Test::parseTestMethodAnnotations(
get_called_class()
);
self::getFixture()
->setOptions($annotations['class'])
->apply();
parent::setUpBeforeClass();
}
/** /**
* Implements default data provider functionality, * Implements default data provider functionality,
* returns array data loaded from Yaml file with the same name as test method * returns array data loaded from Yaml file with the same name as test method
...@@ -796,9 +871,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase ...@@ -796,9 +871,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$this->app()->replaceRegistry($registryPath, $originalValue); $this->app()->replaceRegistry($registryPath, $originalValue);
} }
$this->getFixture()->discard(); // Clear applied fixture self::getFixture()
->setScope(EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_LOCAL)
->discard(); // Clear applied fixture
parent::tearDown(); parent::tearDown();
} }
/**
* Clean up all the shared fixture data
*
* @return void
*/
public static function tearDownAfterClass()
{
self::getFixture()
->setScope(EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_SHARED)
->discard();
parent::tearDownAfterClass();
}
} }
\ No newline at end of file
...@@ -44,6 +44,23 @@ class EcomDev_Utils_Reflection ...@@ -44,6 +44,23 @@ class EcomDev_Utils_Reflection
$reflectionProperty->setValue((is_string($object) ? null : $object), $value); $reflectionProperty->setValue((is_string($object) ? null : $object), $value);
} }
/**
* Sets multiple restricted property values for an object
*
* @param string|object $object class name
* @param array $properties
*/
public static function setRestrictedPropertyValues($object, array $properties)
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
throw new RuntimeException('For setting of restricted properties via Reflection, PHP version should be 5.3.0 or later');
}
foreach ($properties as $property => $value) {
self::setRestrictedPropertyValue($object, $property, $value);
}
}
/** /**
* Gets protected or private property value * Gets protected or private property value
* *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment