From df2b1bc531a1f27b01db9697d7922766d6fa5fa0 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 20 May 2021 19:47:29 -0700 Subject: [PATCH] CiviTestListener - Move scanning responsibility from 'CiviTestListener' to 'EventScanner' The removes duplicate code and expands the functionality to support methods of the form `_on_{$symfony_event}()`. --- CRM/Utils/System/UnitTests.php | 9 ++++++ Civi/Test/CiviTestListener.php | 44 -------------------------- Civi/Test/CiviTestListenerPHPUnit7.php | 44 -------------------------- Civi/Test/HookInterface.php | 17 ++++++++++ Civi/Test/Legacy/CiviTestListener.php | 43 ------------------------- 5 files changed, 26 insertions(+), 131 deletions(-) diff --git a/CRM/Utils/System/UnitTests.php b/CRM/Utils/System/UnitTests.php index 179906dbec..6ac827fa91 100644 --- a/CRM/Utils/System/UnitTests.php +++ b/CRM/Utils/System/UnitTests.php @@ -27,6 +27,15 @@ class CRM_Utils_System_UnitTests extends CRM_Utils_System_Base { $this->supports_form_extensions = FALSE; } + public function initialize() { + parent::initialize(); + $test = $GLOBALS['CIVICRM_TEST_CASE'] ?? NULL; + if ($test && $test instanceof \Civi\Test\HeadlessInterface) { + $listenerMap = \Civi\Core\Event\EventScanner::findListeners($test); + \Civi::dispatcher()->addListenerMap($test, $listenerMap); + } + } + /** * @param string $name * @param string $value diff --git a/Civi/Test/CiviTestListener.php b/Civi/Test/CiviTestListener.php index c8c838bc05..8f4c21d7a4 100644 --- a/Civi/Test/CiviTestListener.php +++ b/Civi/Test/CiviTestListener.php @@ -58,12 +58,6 @@ else { $this->bootHeadless($test); } - if ($test instanceof HookInterface) { - // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config - // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. - $this->registerHooks($test); - } - if ($test instanceof TransactionalInterface) { $this->tx = new \CRM_Core_Transaction(TRUE); $this->tx->rollback(); @@ -114,25 +108,6 @@ else { } } - /** - * @param \Civi\Test\HookInterface $test - * @return array - * Array(string $hookName => string $methodName)). - */ - protected function findTestHooks(HookInterface $test) { - $class = get_class($test); - if (!isset($this->cache[$class])) { - $funcs = []; - foreach (get_class_methods($class) as $func) { - if (preg_match('/^hook_/', $func)) { - $funcs[substr($func, 5)] = $func; - } - } - $this->cache[$class] = $funcs; - } - return $this->cache[$class]; - } - /** * @param \PHPUnit\Framework\Test $test * @return bool @@ -141,25 +116,6 @@ else { return $test instanceof HookInterface || $test instanceof HeadlessInterface; } - /** - * Find any hook functions in $test and register them. - * - * @param \Civi\Test\HookInterface $test - */ - protected function registerHooks(HookInterface $test) { - if (CIVICRM_UF !== 'UnitTests') { - // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. - // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. - throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); - } - \CRM_Utils_Hook::singleton()->reset(); - /** @var \CRM_Utils_Hook_UnitTests $hooks */ - $hooks = \CRM_Utils_Hook::singleton(); - foreach ($this->findTestHooks($test) as $hook => $func) { - $hooks->setHook($hook, [$test, $func]); - } - } - /** * The first time we come across HeadlessInterface or EndToEndInterface, we'll * try to autoboot. diff --git a/Civi/Test/CiviTestListenerPHPUnit7.php b/Civi/Test/CiviTestListenerPHPUnit7.php index b791e4caf1..42161cb12b 100644 --- a/Civi/Test/CiviTestListenerPHPUnit7.php +++ b/Civi/Test/CiviTestListenerPHPUnit7.php @@ -50,12 +50,6 @@ class CiviTestListenerPHPUnit7 implements \PHPUnit\Framework\TestListener { $this->bootHeadless($test); } - if ($test instanceof HookInterface) { - // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config - // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. - $this->registerHooks($test); - } - if ($test instanceof TransactionalInterface) { $this->tx = new \CRM_Core_Transaction(TRUE); $this->tx->rollback(); @@ -105,25 +99,6 @@ class CiviTestListenerPHPUnit7 implements \PHPUnit\Framework\TestListener { } } - /** - * @param \Civi\Test\HookInterface $test - * @return array - * Array(string $hookName => string $methodName)). - */ - protected function findTestHooks(HookInterface $test) { - $class = get_class($test); - if (!isset($this->cache[$class])) { - $funcs = []; - foreach (get_class_methods($class) as $func) { - if (preg_match('/^hook_/', $func)) { - $funcs[substr($func, 5)] = $func; - } - } - $this->cache[$class] = $funcs; - } - return $this->cache[$class]; - } - /** * @param \PHPUnit\Framework\Test $test * @return bool @@ -132,25 +107,6 @@ class CiviTestListenerPHPUnit7 implements \PHPUnit\Framework\TestListener { return $test instanceof HookInterface || $test instanceof HeadlessInterface; } - /** - * Find any hook functions in $test and register them. - * - * @param \Civi\Test\HookInterface $test - */ - protected function registerHooks(HookInterface $test) { - if (CIVICRM_UF !== 'UnitTests') { - // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. - // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. - throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); - } - \CRM_Utils_Hook::singleton()->reset(); - /** @var \CRM_Utils_Hook_UnitTests $hooks */ - $hooks = \CRM_Utils_Hook::singleton(); - foreach ($this->findTestHooks($test) as $hook => $func) { - $hooks->setHook($hook, [$test, $func]); - } - } - /** * The first time we come across HeadlessInterface or EndToEndInterface, we'll * try to autoboot. diff --git a/Civi/Test/HookInterface.php b/Civi/Test/HookInterface.php index 4885cd0677..bd3fee8acf 100644 --- a/Civi/Test/HookInterface.php +++ b/Civi/Test/HookInterface.php @@ -17,12 +17,29 @@ namespace Civi\Test; * } * ``` * + * Similarly, to subscribe using Symfony-style listeners, create function with the 'on_' prefix: + * + * ``` + * class MyTest extends \PHPUnit_Framework_TestCase implements \Civi\Test\HookInterface { + * public function on_civi_api_authorize(AuthorizeEvent $e) { + * echo "Running civi.api.authorize\n"; + * } + * public function on_hook_civicrm_post(GenericHookEvent $e) { + * echo "Running hook_civicrm_post\n"; + * } + * } + * ``` + * * At time of writing, there are a few limitations in how HookInterface is handled * by CiviTestListener: * * - The test must execute in-process (aka HeadlessInterface; aka CIVICRM_UF==UnitTests). * End-to-end tests (multi-process tests) are not supported. * - Early bootstrap hooks (e.g. hook_civicrm_config) are not supported. + * - This does not support priorities or registering multiple listeners. + * + * If you need more advanced registration abilities, consider using `Civi::dispatcher()` + * or `EventDispatcherInterface`. * * @see CiviTestListener */ diff --git a/Civi/Test/Legacy/CiviTestListener.php b/Civi/Test/Legacy/CiviTestListener.php index 1bd0a70507..4a71a74504 100644 --- a/Civi/Test/Legacy/CiviTestListener.php +++ b/Civi/Test/Legacy/CiviTestListener.php @@ -48,11 +48,6 @@ class CiviTestListener extends \PHPUnit_Framework_BaseTestListener { $this->bootHeadless($test); } - if ($test instanceof \Civi\Test\HookInterface) { - // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config - // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. - $this->registerHooks($test); - } if ($test instanceof \Civi\Test\TransactionalInterface) { $this->tx = new \CRM_Core_Transaction(TRUE); $this->tx->rollback(); @@ -103,25 +98,6 @@ class CiviTestListener extends \PHPUnit_Framework_BaseTestListener { } } - /** - * @param \Civi\Test\HookInterface $test - * @return array - * Array(string $hookName => string $methodName)). - */ - protected function findTestHooks(\Civi\Test\HookInterface $test) { - $class = get_class($test); - if (!isset($this->cache[$class])) { - $funcs = []; - foreach (get_class_methods($class) as $func) { - if (preg_match('/^hook_/', $func)) { - $funcs[substr($func, 5)] = $func; - } - } - $this->cache[$class] = $funcs; - } - return $this->cache[$class]; - } - /** * @param \PHPUnit_Framework_Test $test * @return bool @@ -130,25 +106,6 @@ class CiviTestListener extends \PHPUnit_Framework_BaseTestListener { return $test instanceof \Civi\Test\HookInterface || $test instanceof \Civi\Test\HeadlessInterface; } - /** - * Find any hook functions in $test and register them. - * - * @param \Civi\Test\HookInterface $test - */ - protected function registerHooks(\Civi\Test\HookInterface $test) { - if (CIVICRM_UF !== 'UnitTests') { - // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. - // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. - throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); - } - \CRM_Utils_Hook::singleton()->reset(); - /** @var \CRM_Utils_Hook_UnitTests $hooks */ - $hooks = \CRM_Utils_Hook::singleton(); - foreach ($this->findTestHooks($test) as $hook => $func) { - $hooks->setHook($hook, [$test, $func]); - } - } - /** * The first time we come across HeadlessInterface or EndToEndInterface, we'll * try to autoboot. -- 2.25.1