DataFixture.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * Implementation of the @magentoDataFixture DocBlock annotation
  8. */
  9. namespace Magento\TestFramework\Annotation;
  10. use PHPUnit\Framework\Exception;
  11. class DataFixture
  12. {
  13. /**
  14. * @var string
  15. */
  16. protected $_fixtureBaseDir;
  17. /**
  18. * Fixtures that have been applied
  19. *
  20. * @var array
  21. */
  22. private $_appliedFixtures = [];
  23. /**
  24. * Constructor
  25. *
  26. * @param string $fixtureBaseDir
  27. * @throws \Magento\Framework\Exception\LocalizedException
  28. */
  29. public function __construct($fixtureBaseDir)
  30. {
  31. if (!is_dir($fixtureBaseDir)) {
  32. throw new \Magento\Framework\Exception\LocalizedException(
  33. new \Magento\Framework\Phrase("Fixture base directory '%1' does not exist.", [$fixtureBaseDir])
  34. );
  35. }
  36. $this->_fixtureBaseDir = realpath($fixtureBaseDir);
  37. }
  38. /**
  39. * Handler for 'startTestTransactionRequest' event
  40. *
  41. * @param \PHPUnit\Framework\TestCase $test
  42. * @param \Magento\TestFramework\Event\Param\Transaction $param
  43. */
  44. public function startTestTransactionRequest(
  45. \PHPUnit\Framework\TestCase $test,
  46. \Magento\TestFramework\Event\Param\Transaction $param
  47. ) {
  48. /* Start transaction before applying first fixture to be able to revert them all further */
  49. if ($this->_getFixtures($test)) {
  50. if ($this->getDbIsolationState($test) !== ['disabled']) {
  51. $param->requestTransactionStart();
  52. } else {
  53. $this->_applyFixtures($this->_getFixtures($test));
  54. }
  55. }
  56. }
  57. /**
  58. * Handler for 'endTestNeedTransactionRollback' event
  59. *
  60. * @param \PHPUnit\Framework\TestCase $test
  61. * @param \Magento\TestFramework\Event\Param\Transaction $param
  62. */
  63. public function endTestTransactionRequest(
  64. \PHPUnit\Framework\TestCase $test,
  65. \Magento\TestFramework\Event\Param\Transaction $param
  66. ) {
  67. /* Isolate other tests from test-specific fixtures */
  68. if ($this->_appliedFixtures && $this->_getFixtures($test)) {
  69. if ($this->getDbIsolationState($test) !== ['disabled']) {
  70. $param->requestTransactionRollback();
  71. } else {
  72. $this->_revertFixtures();
  73. }
  74. }
  75. }
  76. /**
  77. * Handler for 'startTransaction' event
  78. *
  79. * @param \PHPUnit\Framework\TestCase $test
  80. */
  81. public function startTransaction(\PHPUnit\Framework\TestCase $test)
  82. {
  83. $this->_applyFixtures($this->_getFixtures($test));
  84. }
  85. /**
  86. * Handler for 'rollbackTransaction' event
  87. */
  88. public function rollbackTransaction()
  89. {
  90. $this->_revertFixtures();
  91. }
  92. /**
  93. * Retrieve fixtures from annotation
  94. *
  95. * @param \PHPUnit\Framework\TestCase $test
  96. * @param string $scope
  97. * @return array
  98. * @throws \Magento\Framework\Exception\LocalizedException
  99. */
  100. protected function _getFixtures(\PHPUnit\Framework\TestCase $test, $scope = null)
  101. {
  102. if ($scope === null) {
  103. $annotations = $this->getAnnotations($test);
  104. } else {
  105. $annotations = $test->getAnnotations()[$scope];
  106. }
  107. $result = [];
  108. if (!empty($annotations['magentoDataFixture'])) {
  109. foreach ($annotations['magentoDataFixture'] as $fixture) {
  110. if (strpos($fixture, '\\') !== false) {
  111. // usage of a single directory separator symbol streamlines search across the source code
  112. throw new \Magento\Framework\Exception\LocalizedException(
  113. new \Magento\Framework\Phrase('Directory separator "\\" is prohibited in fixture declaration.')
  114. );
  115. }
  116. $fixtureMethod = [get_class($test), $fixture];
  117. if (is_callable($fixtureMethod)) {
  118. $result[] = $fixtureMethod;
  119. } else {
  120. $result[] = $this->_fixtureBaseDir . '/' . $fixture;
  121. }
  122. }
  123. }
  124. return $result;
  125. }
  126. /**
  127. * @param \PHPUnit\Framework\TestCase $test
  128. * @return array
  129. */
  130. private function getAnnotations(\PHPUnit\Framework\TestCase $test)
  131. {
  132. $annotations = $test->getAnnotations();
  133. return array_replace($annotations['class'], $annotations['method']);
  134. }
  135. /**
  136. * Return is explicit set isolation state
  137. *
  138. * @param \PHPUnit\Framework\TestCase $test
  139. * @return bool|null
  140. */
  141. protected function getDbIsolationState(\PHPUnit\Framework\TestCase $test)
  142. {
  143. $annotations = $this->getAnnotations($test);
  144. return isset($annotations[DbIsolation::MAGENTO_DB_ISOLATION])
  145. ? $annotations[DbIsolation::MAGENTO_DB_ISOLATION]
  146. : null;
  147. }
  148. /**
  149. * Execute single fixture script
  150. *
  151. * @param string|array $fixture
  152. * @throws \Exception
  153. */
  154. protected function _applyOneFixture($fixture)
  155. {
  156. try {
  157. if (is_callable($fixture)) {
  158. call_user_func($fixture);
  159. } else {
  160. require $fixture;
  161. }
  162. } catch (\Exception $e) {
  163. throw new Exception(
  164. sprintf(
  165. "Error in fixture: %s.\n %s\n %s",
  166. json_encode($fixture),
  167. $e->getMessage(),
  168. $e->getTraceAsString()
  169. ),
  170. 500,
  171. $e
  172. );
  173. }
  174. }
  175. /**
  176. * Execute fixture scripts if any
  177. *
  178. * @param array $fixtures
  179. * @throws \Magento\Framework\Exception\LocalizedException
  180. */
  181. protected function _applyFixtures(array $fixtures)
  182. {
  183. /* Execute fixture scripts */
  184. foreach ($fixtures as $oneFixture) {
  185. /* Skip already applied fixtures */
  186. if (in_array($oneFixture, $this->_appliedFixtures, true)) {
  187. continue;
  188. }
  189. $this->_applyOneFixture($oneFixture);
  190. $this->_appliedFixtures[] = $oneFixture;
  191. }
  192. }
  193. /**
  194. * Revert changes done by fixtures
  195. */
  196. protected function _revertFixtures()
  197. {
  198. $appliedFixtures = array_reverse($this->_appliedFixtures);
  199. foreach ($appliedFixtures as $fixture) {
  200. if (is_callable($fixture)) {
  201. $fixture[1] .= 'Rollback';
  202. if (is_callable($fixture)) {
  203. $this->_applyOneFixture($fixture);
  204. }
  205. } else {
  206. $fileInfo = pathinfo($fixture);
  207. $extension = '';
  208. if (isset($fileInfo['extension'])) {
  209. $extension = '.' . $fileInfo['extension'];
  210. }
  211. $rollbackScript = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension;
  212. if (file_exists($rollbackScript)) {
  213. $this->_applyOneFixture($rollbackScript);
  214. }
  215. }
  216. }
  217. $this->_appliedFixtures = [];
  218. }
  219. }