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 350ca36f authored by Ivan Chepurnyi's avatar Ivan Chepurnyi

! Merged trunk into QA

parents fd93125f c03bbe26
......@@ -56,6 +56,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/
protected static $_oldEventCollection = null;
/**
* List of singletons in original application
*
* @var array
*/
protected static $_oldRegistry = null;
/**
* Configuration model class name for unit tests
*
......@@ -77,6 +84,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/
protected static $_cacheClass = 'EcomDev_PHPUnit_Model_Cache';
/**
* Enabled events flag
*
* @var boolean
*/
protected $_eventsEnabled = true;
/**
* This method replaces application, event and config objects
* in Mage to perform unit tests in separate Magento steam
......@@ -88,15 +102,14 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
self::$_oldApplication = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_app');
self::$_oldConfig = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_config');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_events');
$resource = Mage::getSingleton('core/resource');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_registry');
// Setting environment variables for unit tests
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', new self::$_configClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', new self);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', new self::$_eventCollectionClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array());
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', array());
// All unit tests will be runned in admin scope, to get rid of frontend restrictions
Mage::app()->initTest();
......@@ -113,7 +126,22 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
$this->_config->setOptions($options);
$this->_initBaseConfig();
$this->_initCache();
// Set using cache
// for things that shouldn't be reloaded each time
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$this->_cache,
'_allowedCacheOptions',
array(
'eav' => 1,
'layout' => 1,
'translate' => 1
)
);
// Clean cache before the whole suite is running
$this->getCache()->clean();
// Init modules runs install proccess for table structures,
// It is required for setting up proper setup script
$this->_initModules();
......@@ -127,6 +155,18 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
return $this;
}
/**
* Overriden to fix issue with stores loading
* (non-PHPdoc)
* @see Mage_Core_Model_App::_initStores()
*/
protected function _initStores()
{
$this->_store = null;
parent::_initStores();
return $this;
}
/**
* Discard test scope for application, returns all the objects from live version
*
......@@ -137,18 +177,44 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', self::$_oldApplication);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', self::$_oldConfig);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', self::$_oldEventCollection);
$resource = Mage::getSingleton('core/resource');
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array());
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', self::$_oldRegistry);
}
/**
* We will not use cache for UnitTests
* Disables events fire
*
* @return boolean
* @return EcomDev_PHPUnit_Model_App
*/
public function useCache($type=null)
public function disableEvents()
{
return false;
$this->_eventsEnabled = false;
return $this;
}
/**
* Enable events fire
*
* @return EcomDev_PHPUnit_Model_App
*/
public function enableEvents()
{
$this->_eventsEnabled = true;
return $this;
}
/**
* Overriden for disabling events
* fire during fixutre loading
*
* (non-PHPdoc)
* @see Mage_Core_Model_App::dispatchEvent()
*/
public function dispatchEvent($eventName, $args)
{
if ($this->_eventsEnabled) {
parent::dispatchEvent($eventName, $args);
}
return $this;
}
}
......@@ -23,6 +23,77 @@
*/
class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
{
/**
* Scope snapshot without applied configurations,
* It is used for proper store/website/default loading on per store basis
*
* @var Mage_Core_Model_Config_Base
*/
protected $_scopeSnapshot = null;
/**
* Load config data from DB
*
* @return Mage_Core_Model_Config
*/
public function loadDb()
{
if ($this->_isLocalConfigLoaded
&& Mage::isInstalled()
&& $this->_scopeSnapshot === null) {
$this->saveScopeSnapshot();
}
parent::loadDb();
return $this;
}
/**
* Loads scope snapshot
*
* @return EcomDev_PHPUnit_Model_Config
*/
public function loadScopeSnapshot()
{
if ($this->_scopeSnapshot === null) {
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);
}
return $this;
}
/**
* Saves current configuration snapshot,
* 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
*/
public function saveScopeSnapshot($nodesToSave = array('default', 'websites', 'stores'))
{
$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
);
}
return $this;
}
/**
* Loads additional configuration for unit tests
* (non-PHPdoc)
......
......@@ -28,6 +28,15 @@ require_once 'Spyc/spyc.php';
*/
class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
{
// Configuration path for eav loaders
const XML_PATH_FIXTURE_EAV_LOADERS = 'phpunit/suite/fixture/eav';
// Default eav loader class node in loaders configuration
const DEFAULT_EAV_LOADER_NODE = 'default';
// Default eav loader class alias
const DEFAULT_EAV_LOADER_CLASS = 'ecomdev_phpunit/fixture_eav_default';
/**
* Fixtures array, contains config,
* table and eav keys.
......@@ -59,12 +68,29 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/
protected $_fixture = array();
/**
* Fixture options
*
* @var array
*/
protected $_options = array();
/**
* Associative array of configuration values that was changed by fixture,
* Associative array of configuration nodes xml that was changed by fixture,
* it is used to preserve
*
* @var array
*/
protected $_originalConfigurationXml = array();
/**
* Hash of current scope instances (store, website, group)
*
* @return array
*/
protected $_originalConfiguration = array();
protected $_currentScope = array();
/**
* Model constuctor, just defines wich resource model to use
......@@ -76,6 +102,18 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
$this->_init('ecomdev_phpunit/fixture');
}
/**
* Set fixture options
*
* @param array $options
* @return EcomDev_PHPUnit_Model_Fixture
*/
public function setOptions(array $options)
{
$this->_options = $options;
return $this;
}
/**
* Load YAML file
*
......@@ -143,9 +181,45 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
if (!is_array($configuration)) {
throw new InvalidArgumentException('Configuration part should be an associative list');
}
Mage::getConfig()->loadScopeSnapshot();
foreach ($configuration as $path => $value) {
$this->_originalConfiguration[$path] = Mage::getConfig()->getNode($path);
Mage::getConfig()->setNode($path, $value);
$this->_setConfigNodeValue($path, $value);
}
Mage::getConfig()->loadDb();
Mage::app()->reinitStores();
return $this;
}
/**
* Applies raw xml data to config node
*
* @param array $configuration
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyConfigXml($configuration)
{
if (!is_array($configuration)) {
throw new InvalidArgumentException('Configuration part should be an associative list');
}
foreach ($configuration as $path => $value) {
if (!is_string($value)) {
throw new InvalidArgumentException('Configuration value should be a valid xml string');
}
try {
$xmlElement = new Varien_Simplexml_Element($value);
} catch (Exception $e) {
throw new InvalidArgumentException('Configuration value should be a valid xml string', 0, $e);
}
$node = Mage::getConfig()->getNode($path);
if (!$node) {
throw new InvalidArgumentException('Configuration value should be a valid xml string');
}
$this->_originalConfigurationXml[$path] = $node->asNiceXml();
$node->extend($xmlElement, true);
}
return $this;
......@@ -158,11 +232,28 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/
protected function _discardConfig()
{
foreach ($this->_originalConfiguration as $path => $value) {
Mage::getConfig()->setNode($path, $value);
Mage::getConfig()->loadScopeSnapshot();
Mage::getConfig()->loadDb();
Mage::app()->reinitStores();
return $this;
}
/**
* Reverts fixture configuration xml values in Mage_Core_Model_Config
*
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardConfigXml()
{
foreach ($this->_originalConfigurationXml as $path => $value) {
$node = Mage::getConfig()->getNode($path);
$parentNode = $node->getParent();
unset($parentNode->{$node->getName()});
$oldXml = new Varien_Simplexml_Element($value);
$parentNode->appendChild($oldXml);
}
$this->_originalConfiguration = array();
$this->_originalConfigurationXml = array();
return $this;
}
......@@ -182,9 +273,11 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
foreach ($tables as $tableEntity => $data) {
$this->getResource()->cleanTable($tableEntity);
if (!empty($data)) {
$this->getResource()->loadTableData($tableEntity, $data);
}
}
}
/**
* Removes table data from test data base
......@@ -206,10 +299,165 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
}
/**
* @todo Create Implementation for EAV models
* Setting config value with applying the values to stores and websites
*
* @param string $path
* @param string $value
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _setConfigNodeValue($path, $value)
{
$pathArray = explode('/', $path);
$scope = array_shift($pathArray);
switch ($scope) {
case 'stores':
$storeCode = array_shift($pathArray);
Mage::app()->getStore($storeCode)->setConfig(
implode('/', $pathArray), $value
);
break;
case 'websites':
$websiteCode = array_shift($pathArray);
$website = Mage::app()->getWebsite($websiteCode);
$website->setConfig(implode('/', $pathArray), $value);
break;
default:
Mage::getConfig()->setNode($path, $value);
break;
}
return $this;
}
/**
* Retrieves eav loader for a particular entity type
*
* @param string $entityType
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
protected function _getEavLoader($entityType)
{
$loaders = Mage::getConfig()->getNode(self::XML_PATH_FIXTURE_EAV_LOADERS);
if (isset($loaders->$entityType)) {
$classAlias = (string)$loaders->$entityType;
} elseif (isset($loaders->{self::DEFAULT_EAV_LOADER_NODE})) {
$classAlias = (string)$loaders->{self::DEFAULT_EAV_LOADER_NODE};
} else {
$classAlias = self::DEFAULT_EAV_LOADER_CLASS;
}
return Mage::getResourceSingleton($classAlias);
}
/**
* Applies fixture EAV values
*
* @param array $configuration
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyEav($entities)
{
if (!is_array($entities)) {
throw new InvalidArgumentException('EAV part should be an associative list with rows as value and entity type as key');
}
foreach ($entities as $entityType => $values) {
$this->_getEavLoader($entityType)
->setOptions($this->_options)
->loadEntity($entityType, $values);
}
return $this;
}
/**
* Clean applied eav data
*
* @param array $entities
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardEav($entities)
{
foreach (array_keys($entities) as $entityType) {
$this->_getEavLoader($entityType)
->cleanEntity($entityType);
}
return $this;
}
/**
* Applies scope fixture,
* i.e., website, store, store group
*
* @param array $types
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyScope($types)
{
$modelByType = array(
'store' => 'core/store',
'group' => 'core/store_group',
'website' => 'core/website'
);
Mage::app()->disableEvents();
foreach ($types as $type => $rows) {
if (!isset($modelByType[$type])) {
throw new RuntimeException(sprintf('Unknown "%s" scope type specified', $type));
}
foreach ($rows as $row) {
$scopeModel = Mage::getModel($modelByType[$type]);
$this->_currentScope[$type][] = $scopeModel;
$scopeModel->setData($row);
// Change property for saving new objects with specified ids
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', true
);
$scopeModel->isObjectNew(true);
$scopeModel->save();
// Revert changed property
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', false
);
}
}
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
/**
* @todo Create Implementation for Websites/Stores/Groups
* Removes scope fixture changes,
* i.e., website, store, store group
*
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardScope()
{
Mage::app()->disableEvents();
$scope = array_reverse($this->_currentScope);
foreach ($scope as $models) {
foreach ($models as $model) {
$model->delete();
}
}
$this->_currentScope = array();
Mage::app()->getCache()->clean(
Zend_Cache::CLEANING_MODE_MATCHING_TAG,
array(Mage_Core_Model_Mysql4_Collection_Abstract::CACHE_TAG)
);
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
}
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Base implementation of EAV fixtures loader
*
*/
abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev_PHPUnit_Model_Mysql4_Fixture
{
/**
* List of indexers required to build
*
* @var array
*/
protected $_requiredIndexers = array();
/**
* Fixture options
*
* @var array
*/
protected $_options = array();
/**
* Retrieve required indexers for re-building
*
* @var array
*/
public function getRequiredIndexers()
{
return $this->_requiredIndexers;
}
/**
* Set fixture options
*
* @param array $options
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function setOptions(array $options)
{
$this->_options = $options;
return $this;
}
/**
* Add indexer by specific code to required indexers list
*
* @param string $code
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function addRequiredIndexer($code)
{
if (!in_array($code, $this->_requiredIndexers)) {
$this->_requiredIndexers[] = $code;
}
return $this;
}
/**
* Clean entity data table
*
* @param string $entityType
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function cleanEntity($entityType)
{
$entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType);
$this->cleanTable($entityTypeModel->getEntityTable());
return $this;
}
/**
* Loads EAV data into DB tables
*
* @param string $entityType
* @param array $values
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
* @throws RuntimeException
*/
public function loadEntity($entityType, $values)
{
$entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType);
$entityTableColumns = $this->_getWriteAdapter()->describeTable(
$this->getTable($entityTypeModel->getEntityTable())
);
$attributeTableColumns = $this->_getAttributeTablesColumnList($entityTypeModel);
$entities = array();
$entityValues = array();
// Custom values array is used for
// inserting custom entity data in custom tables.
// It is an associative array with table name as key,
// and rows list as value
// See getCustomTableRecords
$customValues = array();
foreach ($values as $index => &$row) {
if (!isset($row[$this->_getEntityIdField($entityTypeModel)])) {
throw new RuntimeException('Entity Id should be specified in EAV fixture');
}
// Fullfil neccessary information
$row['entity_type_id'] = $entityTypeModel->getEntityTypeId();
if (!isset($row['attribute_set_id'])) {
$row['attribute_set_id'] = $entityTypeModel->getDefaultAttributeSetId();
}
// Preparing entity table record
$entity = $this->_getTableRecord($row, $entityTableColumns);
$entities[] = $entity;
// Preparing simple attributes records
foreach ($entityTypeModel->getAttributeCollection() as $attribute) {
$attributeBackendTable = $attribute->getBackendTable();
if (!$attribute->isStatic()
&& $attributeBackendTable
&& isset($attributeTableColumns[$attributeBackendTable])) {
// Prepearing data for insert per attribute table
$attributeRecords = $this->_getAttributeRecords(
$row,
$attribute,
$attributeTableColumns[$attributeBackendTable]
);
if ($attributeRecords) {
if (!isset($entityValues[$attributeBackendTable])) {
$entityValues[$attributeBackendTable] = array();
}
$entityValues[$attributeBackendTable] = array_merge(
$entityValues[$attributeBackendTable],
$attributeRecords
);
}
}
}
// Processing custom entity values
$customValues = array_merge_recursive(
$customValues,
$this->_getCustomTableRecords($row, $entityTypeModel)
);
}
$this->_getWriteAdapter()->insertOnDuplicate(
$this->getTable($entityTypeModel->getEntityTable()),
$entities
);
foreach ($entityValues as $tableName => $records) {
$this->_getWriteAdapter()->insertOnDuplicate(
$tableName,
$records
);
}
foreach ($customValues as $tableName => $records) {
$this->_getWriteAdapter()->insertOnDuplicate(
(strpos($tableName, '/') !== false ? $this->getTable($tableName) : $tableName),
$records
);
}
foreach ($entities as $entity) {
$this->_customEntityAction($entity, $entityTypeModel);
}
if (empty($this->_options['doNotIndexAll'])) {
$indexer = Mage::getSingleton('index/indexer');
foreach ($this->getRequiredIndexers() as $indexerCode) {
if (empty($this->_options['doNotIndex'])
|| !in_array($indexerCode, $this->_options['doNotIndex'])) {
$indexer->getProcessByCode($indexerCode)
->reindexAll();
}
}
}
return $this;
}
/**
* Performs custom action on entity
*
* @param array $entity
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
protected function _customEntityAction($entity, $entityTypeModel)
{
return $this;
}
/**
* If you have some custom EAV tables,
* this method will help you to insert
* them on fixture processing step
* It should return an associative array, where an entry key
* is the table name and its value is a list of table rows
*
* @example
* return array(
* 'some/table' => array(
* array(
* 'field' => 'value'
* )
* )
* )
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
return array();
}
/**
* Retrieves associative list of attribute tables and their columns
*
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getAttributeTablesColumnList($entityTypeModel)
{
$tableNames = array_unique(
$entityTypeModel->getAttributeCollection()
->walk('getBackendTable')
);
$columnsByTable = array();
foreach ($tableNames as $table) {
if ($table) {
$columnsByTable[$table] = $this->_getWriteAdapter()
->describeTable(
$table
);
}
}
return $columnsByTable;
}
/**
* Prepares entity table record from array
*
* @param array $row
* @param array $tableColumns list of entity_table columns
* @return array
*/
protected function _getTableRecord($row, $tableColumns)
{
$record = array();
// Fullfil table records with data
foreach ($tableColumns as $columnName => $definition) {
if (isset($row[$columnName])) {
$record[$columnName] = $row[$columnName];
} elseif ($definition['DEFAULT'] !== null) {
$record[$columnName] = $definition['DEFAULT'];
} else {
$record[$columnName] = (($definition['NULLABLE']) ? null : '');
}
}
return $record;
}
/**
* Retrieves attribute records for single entity
*
* @param array $row
* @param array $attribute
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
$records = array();
$value = $this->_getAttributeValue($row, $attribute);
if ($value !== null) {
$valueInfo = $this->_getAttributeValueInfo($row, $attribute);
$valueInfo['value'] = $value;
$records[] = $this->_getTableRecord($valueInfo, $tableColumns);
}
return $records;
}
/**
* Returns attribute meta info for record,
* e.g. entity_type_id, attribute_id, etc
*
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @return array
*/
protected function _getAttributeValueInfo($row, $attribute)
{
return array(
'attribute_id' => $attribute->getId(),
'entity_type_id' => $attribute->getEntityTypeId(),
$this->_getEntityIdField($attribute) => $row[$this->_getEntityIdField($attribute)]
);
}
/**
* Retrieves attribute value
*
* @param array $row
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @return mixed|null
*/
protected function _getAttributeValue($row, $attribute)
{
if (isset($row[$attribute->getAttributeCode()]) && !is_array($row[$attribute->getAttributeCode()])) {
$value = $row[$attribute->getAttributeCode()];
} elseif ($attribute->getIsRequired()
&& $attribute->getDefaultValue() !== null
&& $attribute->getDefaultValue() !== ''
&& !is_array($attribute->getDefaultValue())) {
$value = $attribute->getDefaultValue();
} else {
$value = null;
}
return $value;
}
/**
* Retrieves entity id field, based on entity configuration
*
* @param Mage_Eav_Model_Entity_Type|Mage_Eav_Model_Entity_Attribute $entityTypeModel
* @return string
*/
protected function _getEntityIdField($entityTypeModel)
{
if ($entityTypeModel->getEntityIdField()) {
return $entityTypeModel->getEntityIdField();
}
return Mage_Eav_Model_Entity::DEFAULT_ENTITY_ID_FIELD;
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Base Catalog EAV fixture loader
*
*
*/
abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
const SCOPE_TYPE_STORE = 'stores';
const SCOPE_TYPE_WEBSITE = 'websites';
/**
* Overriden to add GWS implementation for attribute records
*
* @param array $row
* @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute
* @param array $tableColumns
* @return array
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
$records = parent::_getAttributeRecords($row, $attribute, $tableColumns);
// If the attribute is not global,
// then walk over all websites and stores scopes for attribute value
if ($attribute->isScopeStore() || $attribute->isScopeWebsite()) {
// Search for website values and fullfil data per website's store
$storeValues = array();
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_WEBSITE) as $websiteCode) {
$website = Mage::app()->getWebsite($websiteCode);
$value = $this->_getGwsValue($row, $attribute, $websiteCode, self::SCOPE_TYPE_WEBSITE);
if ($value !== null) {
foreach ($website->getStoreIds() as $storeId) {
$storeValues[$storeId] = $value;
}
}
}
// If attribute has store scope, then override website values by store ones
if ($attribute->isScopeStore()) {
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_STORE) as $storeCode) {
$store = Mage::app()->getStore($storeCode);
$value = $this->_getGwsValue($row, $attribute, $storeCode, self::SCOPE_TYPE_STORE);
if ($value !== null) {
$storeValues[$store->getId()] = $value;
}
}
}
// Apply collected values
$valueInfo = $this->_getAttributeValueInfo($row, $attribute);
foreach ($storeValues as $storeId => $value) {
$valueInfo['store_id'] = $storeId;
$valueInfo['value'] = $value;
$records[] = $this->_getTableRecord($valueInfo, $tableColumns);
}
}
return $records;
}
/**
* Check is available store/website values
*
* @param array $row
* @param string $scopeType
* @return boolean
*/
protected function _hasGwsValues($row, $scopeType = self::SCOPE_TYPE_STORE)
{
return isset($row['/' . $scopeType]);
}
/**
* Retrieves list of websites/stores codes
*
* @param array $row
* @param string $scopeType
*/
protected function _getGwsCodes($row, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!$this->_hasGwsValues($row, $scopeType)) {
return array();
}
return array_keys($row['/' . $scopeType]);
}
/**
* Retrieves scope dependent value from fixture value, i.e,
* store view or
* website attribute value
*
*
* @param array $row
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @param string $scopeCode
* @param string $scopeType
* @return mixed|null
*/
protected function _getGwsValue($row, $attribute, $scopeCode, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!isset($row['/' . $scopeType][$scopeCode]) || !is_array($row['/' . $scopeType][$scopeCode])) {
return null;
}
return $this->_getAttributeValue($row['/' . $scopeType][$scopeCode], $attribute);
}
/**
* Overriden to add default store id
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeValueInfo()
*/
protected function _getAttributeValueInfo($row, $attribute)
{
$info = parent::_getAttributeValueInfo($row, $attribute);
$info['store_id'] = 0;
return $info;
}
}
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Category EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Category extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'catalog_category_flat'
);
/**
* Overriden to add easy fixture loading for product associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
return $this->_getProductAssociationRecords($row, $entityTypeModel);
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductAssociationRecords($row, $entityTypeModel)
{
if (isset($row['products']) && is_array($row['products'])) {
$records = array();
foreach ($row['products'] as $productId => $position) {
$records[] = array(
'category_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'product_id' => $productId,
'position' => $position
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Product EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'cataloginventory_stock',
'catalog_product_flat',
'catalog_product_attribute',
'catalog_product_price'
);
/**
* Ovveriden to fix issue with flat tables existance mark
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::loadEntity()
*/
public function loadEntity($entityType, $values)
{
// Fix of Product Flat Indexer
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
Mage::getResourceSingleton('catalog/product_flat_indexer'),
'_existsFlatTables',
array()
);
return parent::loadEntity($entityType, $values);
}
/**
* Overriden to add easy fixture loading for websites and categories associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
$records = array();
$records += $this->_getWebsiteVisibilityRecords($row, $entityTypeModel);
$records += $this->_getTierPriceRecords($row, $entityTypeModel);
$records += $this->_getCategoryAssociationRecords($row, $entityTypeModel);
$records += $this->_getProductStockRecords($row, $entityTypeModel);
return $records;
}
/**
* Changed to support price attribute type multi-scope
* (non-PHPdoc)
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
if ($attribute->getFrontendInput() == 'price') {
$attribute->setIsGlobal(
Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE
);
}
return parent::_getAttributeRecords($row, $attribute, $tableColumns);
}
/**
* Generates records for catalog_product_website table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
* @throws RuntimeException
*/
protected function _getWebsiteVisibilityRecords($row, $entityTypeModel)
{
if (isset($row['website_ids']) && is_array($row['website_ids'])) {
$records = array();
foreach ($row['website_ids'] as $websiteId) {
$website = Mage::app()->getWebsite($websiteId);
$records[] = array(
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'website_id' => $website->getId()
);
}
// We shouldn't return empty table data
if ($records) {
return array('catalog/product_website' => $records);
}
}
return array();
}
/**
* Generates records for catalog_product_entity_tier_price table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getTierPriceRecords($row, $entityTypeModel)
{
if (isset($row['tier_price']) && is_array($row['tier_price'])) {
$tableName = $entityTypeModel->getValueTablePrefix() . '_tier_price';
$columns = $this->_getWriteAdapter()->describeTable(
$tableName
);
$records = array();
foreach ($row['tier_price'] as $tierPrice) {
$tierPrice[$this->_getEntityIdField($entityTypeModel)] = $row[$this->_getEntityIdField($entityTypeModel)];
$records[] = $this->_getTableRecord($tierPrice, $columns);
}
if ($records) {
return array($tableName => $records);
}
}
return array();
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getCategoryAssociationRecords($row, $entityTypeModel)
{
if (isset($row['category_ids']) && is_array($row['category_ids'])) {
$records = array();
foreach ($row['category_ids'] as $categoryId) {
$records[] = array(
'category_id' => $categoryId,
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)]
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
/**
* Generates records for cataloginventory_stock_item table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductStockRecords($row, $entityTypeModel)
{
if (isset($row['stock']) && is_array($row['stock'])) {
$columns = $this->_getWriteAdapter()->describeTable(
$this->getTable('cataloginventory/stock_item')
);
$row['stock']['product_id'] = $row[$this->_getEntityIdField($entityTypeModel)];
if (!isset($row['stock']['stock_id'])) {
$row['stock']['stock_id'] = 1;
}
return array(
'cataloginventory/stock_item' => array(
$this->_getTableRecord($row['stock'], $columns)
)
);
}
return array();
}
/**
* Adding enabled and visibility indexes
*
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_customEntityAction()
*/
protected function _customEntityAction($entity, $entityTypeModel)
{
Mage::getResourceSingleton('catalog/product_status')
->refreshEnabledIndex($entity[$this->_getEntityIdField($entityTypeModel)], 0);
parent::_customEntityAction($entity, $entityTypeModel);
return $this;
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Default EAV fixture loaded
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Default extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
}
\ No newline at end of file
......@@ -34,6 +34,14 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/
protected $_expectations = null;
/**
* Original store kept for tearDown,
* if was set in test method
*
* @var Mage_Core_Model_Store
*/
protected $_originalStore = null;
/**
* Loads expectations for current test case
*
......@@ -78,9 +86,29 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$this->_expectations = new Varien_Object($expectations);
}
$arguments = func_get_args();
if ($arguments) {
if (count($arguments) > 1) {
$dataKey = call_user_func_array('sprintf', $arguments);
} else {
$dataKey = array_shift($arguments);
}
$dataPart = $this->_expectations->getData($dataKey);
if (!is_array($dataPart)) {
throw new InvalidArgumentException(
'Argument values for specifying special scope of expectations should be presented '
. ' in expectation file and should be an associative list (dataKey: "' . $dataKey . '")'
);
}
return new Varien_Object($dataPart);
}
return $this->_expectations;
}
/**
* Retrieves fixture model singleton
*
......@@ -148,7 +176,8 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$this->_getFixture()->loadYaml($filePath);
}
}
// Pass methods for
$this->_getFixture()->setOptions($annotations['method']);
$this->_getFixture()->apply();
parent::setUp();
}
......@@ -172,6 +201,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
return Spyc::YAMLLoad($filePath);
}
/**
* Set current store scope for test
*
* @param int|string|Mage_Core_Model_Store $store
* @return EcomDev_PHPUnit_Test_Case
*/
public function setCurrentStore($store)
{
if (!$this->_originalStore) {
$this->_originalStore = Mage::app()->getStore();
}
Mage::app()->setCurrentStore(
Mage::app()->getStore($store)
);
return $this;
}
/**
* Performs a clean up after a particular test was run
* (non-PHPdoc)
......@@ -179,6 +226,11 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/
protected function tearDown()
{
if ($this->_originalStore) { // Remove store scope, that was set in test
Mage::app()->setCurrentStore($this->_originalStore);
$this->_originalStore = null;
}
$this->_expectations = null; // Clear expectation values
$this->_getFixture()->discard(); // Clear applied fixture
parent::tearDown();
......
......@@ -29,6 +29,8 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
const XML_PATH_UNIT_TEST_GROUPS = 'phpunit/suite/groups';
const XML_PATH_UNIT_TEST_MODULES = 'phpunit/suite/modules';
const XML_PATH_UNIT_TEST_APP = 'phpunit/suite/app';
const CACHE_TAG = 'ECOMDEV_PHPUNIT';
const CACHE_TYPE = 'ecomdev_phpunit';
/**
* Setting up test scope for Magento
......@@ -86,15 +88,52 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
continue;
}
$directoryIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($searchPath)
);
$currentGroups = array(
$group->getName(),
$module->getName()
);
$testCases = self::_loadTestCases($searchPath, $moduleCodeDir);
foreach ($testCases as $className) {
$classReflection = EcomDev_Utils_Reflection::getRelflection($className);
$suite->addTest(new PHPUnit_Framework_TestSuite($classReflection), $currentGroups);
}
}
}
if (!$suite->count()) {
$suite->addTest(self::warning('There were no test cases for the current run'));
}
return $suite;
}
/**
* Loads test cases from search path,
* Will return cached result
*
* @param string $searchPath path for searching files with tests
* @param string $moduleCodeDir path where the module files are placed (e.g. app/code/local),
* used for determining the class name
*/
protected static function _loadTestCases($searchPath, $moduleCodeDir)
{
if (Mage::app()->useCache(self::CACHE_TYPE)) {
$cachedTestCases = Mage::app()->loadCache(
self::CACHE_TYPE . md5($searchPath)
);
if ($cachedTestCases) {
return unserialize($cachedTestCases);
}
}
$testCases = array();
$directoryIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($searchPath)
);
foreach ($directoryIterator as $fileObject) {
/* @var $fileObject SplFileObject */
// Skip entry if it is not a php file
......@@ -102,30 +141,29 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
continue;
}
$classPath = substr($fileObject->getPath() . DS . $fileObject->getBasename('.php'), strlen($moduleCodeDir));
$className = uc_words(ltrim($classPath, DS), '_', DS);
// Add unit test case only
// if it is a valid class extended from EcomDev_PHPUnit_Test_Case
if (class_exists($className, true)) {
$reflectionClass = EcomDev_Utils_Reflection::getRelflection($className);
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case')) {
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case')
|| $reflectionClass->isAbstract()) {
continue;
}
$suite->addTest(new PHPUnit_Framework_TestSuite($reflectionClass), $currentGroups);
}
}
$testCases[] = $className;
}
}
if (!$suite->count()) {
$suite->addTest(self::warning('There were no test cases for the current run'));
if (Mage::app()->useCache(self::CACHE_TYPE)) {
Mage::app()->saveCache(
serialize($testCases),
self::CACHE_TYPE . md5($searchPath),
array(self::CACHE_TAG)
);
}
return $suite;
return $testCases;
}
}
......@@ -33,6 +33,15 @@
<class>EcomDev_PHPUnit_Model_Mysql4</class>
</ecomdev_phpunit_mysql4>
</models>
<cache>
<types>
<ecomdev_phpunit>
<label>Unit Test Cases</label>
<description>Unit test case file paths cache.</description>
<tags>ECOMDEV_PHPUNIT</tags>
</ecomdev_phpunit>
</types>
</cache>
</global>
<phpunit>
<suite>
......@@ -42,6 +51,16 @@
<helpers>Helper</helpers>
<blocks>Block</blocks>
</groups>
<fixture>
<eav>
<!-- Here goes the list of fixture loaders for EAV
If no fixture loader is specified for entity, then default will be used
-->
<default>ecomdev_phpunit/fixture_eav_default</default>
<catalog_product>ecomdev_phpunit/fixture_eav_catalog_product</catalog_product>
<catalog_category>ecomdev_phpunit/fixture_eav_catalog_category</catalog_category>
</eav>
</fixture>
<!-- Application model class name for running tests -->
<app>EcomDev_PHPUnit_Model_App</app>
<modules>
......
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