We will work on Apr 26th (Saturday) and will be off from Apr 30th (Wednesday) until May 2nd (Friday) for public holiday in our country

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
const REGISTRY_PATH_LAYOUT_SINGLETON = '_singleton/core/layout';
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_DESIGN_PACKAGE_MODEL_FOR_TEST = 'phpunit/suite/design/package/model';
......@@ -221,6 +223,8 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
$designPackageModel);
$this->loadAreaPart(self::AREA_TEST, self::AREA_PART_EVENTS);
$this->replaceRegistry(self::REGISTRY_PATH_SHARED_STORAGE, new Varien_Object());
return $this;
}
......
......@@ -28,12 +28,18 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
const CHANGE_ME = '[change me]';
/**
* Scope snapshot without applied configurations,
* It is used for proper store/website/default loading on per store basis
* Scope snapshot with different levels of saving configuration
*
* @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
......@@ -51,7 +57,7 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
{
if ($this->_isLocalConfigLoaded
&& Mage::isInstalled()
&& $this->_scopeSnapshot === null) {
&& empty($this->_scopeSnapshot)) {
$this->saveScopeSnapshot();
}
parent::loadDb();
......@@ -141,43 +147,39 @@ class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
*/
public function loadScopeSnapshot()
{
if ($this->_scopeSnapshot === null) {
if (empty($this->_scopeSnapshot)) {
throw new RuntimeException('Cannot load scope snapshot, because it was not saved before');
}
$scopeNode = $this->_scopeSnapshot->getNode();
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);
}
$scope = clone end($this->_scopeSnapshot);
$this->_xml = $scope;
return $this;
}
/**
* Saves current configuration snapshot,
* for pussible restoring in feature
* Flushes current scope snapshot level if it is not the last one
*
* @param array $nodesToSave list of nodes for saving data, by default it is 'default', 'webistes', 'stores'
* @return EcomDev_PHPUnit_Model_Config
*/
public function saveScopeSnapshot($nodesToSave = array('default', 'websites', 'stores'))
public function flushScopeSnapshot()
{
$this->_scopeSnapshot = clone $this->_prototype;
$this->_scopeSnapshot->loadString('<config />');
$scopeNode = $this->_scopeSnapshot->getNode();
foreach ($nodesToSave as $node) {
$scopeNode->addChild($node);
$scopeNode->{$node}->extend(
$this->getNode($node),
true
);
if (count($this->_scopeSnapshot) > 1) {
array_pop($this->_scopeSnapshot);
memory_get_usage(); // Memory GC
}
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;
}
......
......@@ -25,6 +25,9 @@
*/
interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_Test_Loadable_Interface
{
const SCOPE_LOCAL = 'local';
const SCOPE_SHARED = 'shared';
/**
* Sets fixture options
*
......@@ -32,4 +35,74 @@ interface EcomDev_PHPUnit_Model_Fixture_Interface extends EcomDev_PHPUnit_Model_
* @return EcomDev_PHPUnit_Model_Fixture_Interface
*/
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
$records[] = $this->_getTableRecord($row, $tableColumns);
}
$this->_getWriteAdapter()->insertMultiple(
$this->_getWriteAdapter()->insertOnDuplicate(
$this->getTable($tableEntity),
$records
);
......
......@@ -36,6 +36,13 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
*/
protected $_options = array();
/**
* Fixture model
*
* @var EcomDev_PHPUnit_Model_Fixture_Interface
*/
protected $_fixture = null;
/**
* Retrieve required indexers for re-building
*
......@@ -46,6 +53,17 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
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
*
......@@ -95,8 +113,19 @@ abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev
*/
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);
$entityTableColumns = $this->_getWriteAdapter()->describeTable(
$this->getTable($entityTypeModel->getEntityTable())
);
......@@ -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;
}
......
......@@ -25,7 +25,6 @@ class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_P
{
protected $_requiredIndexers = array(
'cataloginventory_stock',
'catalog_product_flat',
'catalog_product_attribute',
'catalog_product_price'
);
......
......@@ -271,12 +271,28 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
* @return array
*/
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)) {
$sources = array($sources);
}
$allAnnotations = $this->getAnnotations();
$allAnnotations = PHPUnit_Util_Test::parseTestMethodAnnotations(
$className, $testName
);
$annotation = array();
// Walkthrough sources for annotation retrieval
......@@ -627,12 +643,30 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function getFixture()
protected static function getFixture()
{
return Mage::getSingleton($this->getLoadableClassAlias(
'fixture',
self::XML_PATH_DEFAULT_FIXTURE_MODEL
));;
$fixture = Mage::getSingleton(
self::getLoadableClassAlias(
'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
*/
protected function getExpectation()
{
return Mage::getSingleton($this->getLoadableClassAlias(
'expectation',
self::XML_PATH_DEFAULT_EXPECTATION_MODEL
));
return Mage::getSingleton(
self::getLoadableClassAlias(
'expectation',
self::XML_PATH_DEFAULT_EXPECTATION_MODEL
)
);
}
......@@ -656,14 +692,17 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
* @param string $type
* @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)) {
$classAlias = current($annotationValue);
} else {
$classAlias = $this->app()->getConfig()->getNode($configPath);
$classAlias = self::app()->getConfig()->getNode($configPath);
}
return $classAlias;
......@@ -699,12 +738,25 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$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) {
$name .= '.yaml';
}
$classFileObject = new SplFileInfo(
EcomDev_Utils_Reflection::getRelflection($this)->getFileName()
EcomDev_Utils_Reflection::getRelflection($className)->getFileName()
);
$filePath = $classFileObject->getPath() . DS
......@@ -726,15 +778,38 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/
protected function setUp()
{
$this->getFixture()->loadByTestCase($this);
self::getFixture()
->setScope(EcomDev_PHPUnit_Model_Fixture_Interface::SCOPE_LOCAL)
->loadByTestCase($this);
$annotations = $this->getAnnotations();
$this->getFixture()->setOptions($annotations['method']);
$this->getFixture()->apply();
self::getFixture()
->setOptions($annotations['method'])
->apply();
$this->app()->resetDispatchedEvents();
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,
* 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
$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();
}
/**
* 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
$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
*
......
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