Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - https://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <https://www.gnu.org/licenses/>.namespace core\hook;use core\di;/*** Hooks tests.** @package core* @author Petr Skoda* @copyright 2022 Open LMS* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later* @covers \core\hook\manager*/final class manager_test extends \advanced_testcase {/*** Test public factory method to get hook manager.*/public function test_get_instance(): void {$manager = manager::get_instance();$this->assertInstanceOf(manager::class, $manager);$this->assertSame($manager, manager::get_instance());}/*** Test getting of manager test instance.*/public function test_phpunit_get_instance(): void {$testmanager = manager::phpunit_get_instance([]);$this->assertSame([], $testmanager->get_hooks_with_callbacks());// We get a new instance every time.$this->assertNotSame($testmanager, manager::phpunit_get_instance([]));$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());}/*** Test loading and parsing of callbacks from files.*/public function test_callbacks(): void {$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());$callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');$this->assertCount(2, $callbacks);$this->assertSame(['callback' => 'test_plugin\\callbacks::test2','component' => 'test_plugin2','disabled' => false,'priority' => 200,], $callbacks[0]);$this->assertSame(['callback' => 'test_plugin\\callbacks::test1','component' => 'test_plugin1','disabled' => false,'priority' => 100,], $callbacks[1]);$this->assertDebuggingNotCalled();$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_broken.php',];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame([], $testmanager->get_hooks_with_callbacks());$debuggings = $this->getDebuggingMessages();$this->resetDebugging();$this->assertSame('Hook callback definition requires \'hook\' name in \'test_plugin1\'',$debuggings[0]->message);$this->assertSame('Hook callback definition requires \'callback\' callable in \'test_plugin1\'',$debuggings[1]->message);$this->assertSame('Hook callback definition contains invalid \'callback\' static class method string in \'test_plugin1\'',$debuggings[2]->message);$this->assertCount(3, $debuggings);}/*** Test hook dispatching, that is callback execution.*/public function test_dispatch(): void {require_once(__DIR__ . '/../fixtures/hook/hook.php');require_once(__DIR__ . '/../fixtures/hook/callbacks.php');$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);\test_plugin\callbacks::$calls = [];$hook = new \test_plugin\hook\hook();$result = $testmanager->dispatch($hook);$this->assertSame($hook, $result);$this->assertSame(['test2', 'test1'], \test_plugin\callbacks::$calls);\test_plugin\callbacks::$calls = [];$this->assertDebuggingNotCalled();}/*** Test hook dispatching, that is callback execution.*/public function test_dispatch_with_exception(): void {require_once(__DIR__ . '/../fixtures/hook/hook.php');require_once(__DIR__ . '/../fixtures/hook/callbacks.php');$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_exception.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);$hook = new \test_plugin\hook\hook();$this->expectException(\Exception::class);$this->expectExceptionMessage('grrr');$testmanager->dispatch($hook);}/*** Test hook dispatching, that is callback execution.*/public function test_dispatch_with_invalid(): void {require_once(__DIR__ . '/../fixtures/hook/hook.php');require_once(__DIR__ . '/../fixtures/hook/callbacks.php');// Missing callbacks is ignored.$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_missing.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);\test_plugin\callbacks::$calls = [];$hook = new \test_plugin\hook\hook();$testmanager->dispatch($hook);$this->assertDebuggingCalled("Hook callback definition contains invalid 'callback' method name in 'test_plugin1'. Callback method not found.",);$this->assertSame(['test2'], \test_plugin\callbacks::$calls);}/*** Test stoppping of hook dispatching.*/public function test_dispatch_stoppable(): void {require_once(__DIR__ . '/../fixtures/hook/stoppablehook.php');require_once(__DIR__ . '/../fixtures/hook/callbacks.php');$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_stoppable.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_stoppable.php',];$testmanager = manager::phpunit_get_instance($componentfiles);\test_plugin\callbacks::$calls = [];$hook = new \test_plugin\hook\stoppablehook();$result = $testmanager->dispatch($hook);$this->assertSame($hook, $result);$this->assertSame(['stop1'], \test_plugin\callbacks::$calls);\test_plugin\callbacks::$calls = [];$this->assertDebuggingNotCalled();}/*** Tests callbacks can be overridden via CFG settings.*/public function test_callback_overriding(): void {global $CFG;$this->resetAfterTest();$componentfiles = ['test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php','test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php',];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());$callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');$this->assertCount(2, $callbacks);$this->assertSame(['callback' => 'test_plugin\\callbacks::test2','component' => 'test_plugin2','disabled' => false,'priority' => 200,], $callbacks[0]);$this->assertSame(['callback' => 'test_plugin\\callbacks::test1','component' => 'test_plugin1','disabled' => false,'priority' => 100,], $callbacks[1]);$CFG->hooks_callback_overrides = ['test_plugin\\hook\\hook' => ['test_plugin\\callbacks::test2' => ['priority' => 33],],];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());$callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');$this->assertCount(2, $callbacks);$this->normalise_callbacks($callbacks);$this->assertSame(['callback' => 'test_plugin\\callbacks::test1','component' => 'test_plugin1','disabled' => false,'priority' => 100,], $callbacks[0]);$this->assertSame(['callback' => 'test_plugin\\callbacks::test2','component' => 'test_plugin2','defaultpriority' => 200,'disabled' => false,'priority' => 33,], $callbacks[1]);$CFG->hooks_callback_overrides = ['test_plugin\\hook\\hook' => ['test_plugin\\callbacks::test2' => ['priority' => 33, 'disabled' => true],],];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());$callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');$this->assertCount(2, $callbacks);$this->normalise_callbacks($callbacks);$this->assertSame(['callback' => 'test_plugin\\callbacks::test1','component' => 'test_plugin1','disabled' => false,'priority' => 100,], $callbacks[0]);$this->assertSame(['callback' => 'test_plugin\\callbacks::test2','component' => 'test_plugin2','defaultpriority' => 200,'disabled' => true,'priority' => 33,], $callbacks[1]);$CFG->hooks_callback_overrides = ['test_plugin\\hook\\hook' => ['test_plugin\\callbacks::test2' => ['disabled' => true],],];$testmanager = manager::phpunit_get_instance($componentfiles);$this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks());$callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook');$this->assertCount(2, $callbacks);$this->assertSame(['callback' => 'test_plugin\\callbacks::test2','component' => 'test_plugin2','disabled' => true,'priority' => 200,], $callbacks[0]);$this->assertSame(['callback' => 'test_plugin\\callbacks::test1','component' => 'test_plugin1','disabled' => false,'priority' => 100,], $callbacks[1]);require_once(__DIR__ . '/../fixtures/hook/hook.php');require_once(__DIR__ . '/../fixtures/hook/callbacks.php');\test_plugin\callbacks::$calls = [];$hook = new \test_plugin\hook\hook();$result = $testmanager->dispatch($hook);$this->assertSame($hook, $result);$this->assertSame(['test1'], \test_plugin\callbacks::$calls);\test_plugin\callbacks::$calls = [];$this->assertDebuggingNotCalled();$CFG->hooks_callback_overrides = [];}/*** Register a fake plugin called hooktest in the component manager.** Tests consuming this helpers must run in a separate process.*/protected function setup_hooktest_plugin(): void {global $CFG;$this->add_mocked_plugintype('fake', "{$CFG->dirroot}/lib/tests/fixtures/hook/fakeplugins");$this->add_mocked_plugin('fake', 'hooktest', "{$CFG->dirroot}/lib/tests/fixtures/hook/fakeplugins/hooktest");}/*** Call a plugin callback that has been replaced by a hook, but has no hook callback.** The original callback should be called, but a debugging message should be output.** @runInSeparateProcess*/public function test_migrated_callback(): void {$this->resetAfterTest(true);// Include plugin hook discovery agent, and the hook that replaces the callback.require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_callback.php');// Register the fake plugin with the component manager.$this->setup_hooktest_plugin();// Register the fake plugin with the hook manager, but don't define any hook callbacks.di::set(manager::class,manager::phpunit_get_instance(['fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks_nocallbacks.php',],),);// Confirm a non-deprecated callback is called as expected.$this->assertEquals('Called current callback', component_callback('fake_hooktest', 'current_callback'));// Confirm the deprecated callback is called as expected.$this->assertEquals('Called deprecated callback',component_callback('fake_hooktest', 'old_callback', [], null, true));$this->assertDebuggingCalled('Callback old_callback in fake_hooktest component should be migrated to new hook ' .'callback for fake_hooktest\hook\hook_replacing_callback');}/*** Call a plugin callback that has been replaced by a hook, and has a hook callback.** The original callback should not be called, and no debugging should be output.** @runInSeparateProcess*/public function test_migrated_callback_with_replacement(): void {$this->resetAfterTest(true);// Include plugin hook discovery agent, and the hook that replaces the callback, and a hook callback for the hook.require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_callback.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook_callbacks.php');// Register the fake plugin with the component manager.$this->setup_hooktest_plugin();// Register the fake plugin with the hook manager, including the hook callback.di::set(manager::class,manager::phpunit_get_instance(['fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks.php',]),);// Confirm a non-deprecated callback is called as expected.$this->assertEquals('Called current callback', component_callback('fake_hooktest', 'current_callback'));// Confirm the deprecated callback is not called, as expected.$this->assertNull(component_callback('fake_hooktest', 'old_callback', [], null, true));$this->assertDebuggingNotCalled();}/*** Call a plugin class callback that has been replaced by a hook, but has no hook callback.** The original class callback should be called, but a debugging message should be output.** @runInSeparateProcess*/public function test_migrated_class_callback(): void {$this->resetAfterTest(true);// Include plugin hook discovery agent, the class containing callbacks, and the hook that replaces the class callback.require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/callbacks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_class_callback.php');// Register the fake plugin with the component manager.$this->setup_hooktest_plugin();// Register the fake plugin with the hook manager, but don't define any hook callbacks.di::set(manager::class,manager::phpunit_get_instance(['fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks_nocallbacks.php',]),);// Confirm a non-deprecated class callback is called as expected.$this->assertEquals('Called current class callback',component_class_callback('fake_hooktest\callbacks', 'current_class_callback', []));// Confirm the deprecated class callback is called as expected.$this->assertEquals('Called deprecated class callback',component_class_callback('fake_hooktest\callbacks', 'old_class_callback', [], null, true));$this->assertDebuggingCalled('Callback callbacks::old_class_callback in fake_hooktest component should be migrated to new hook ' .'callback for fake_hooktest\hook\hook_replacing_class_callback');}/*** Call a plugin class callback that has been replaced by a hook, and has a hook callback.** The original callback should not be called, and no debugging should be output.** @runInSeparateProcess*/public function test_migrated_class_callback_with_replacement(): void {$this->resetAfterTest(true);// Include plugin hook discovery agent, the class containing callbacks, the hook that replaces the class callback,// and a hook callback for the new hook.require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/callbacks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hooks.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook/hook_replacing_class_callback.php');require_once(__DIR__ . '/../fixtures/hook/fakeplugins/hooktest/classes/hook_callbacks.php');// Register the fake plugin with the component manager.$this->setup_hooktest_plugin();// Register the fake plugin with the hook manager, including the hook callback.di::set(manager::class,manager::phpunit_get_instance(['fake_hooktest' => __DIR__ . '/../fixtures/hook/fakeplugins/hooktest/db/hooks.php',]),);// Confirm a non-deprecated class callback is called as expected.$this->assertEquals('Called current class callback',component_class_callback('fake_hooktest\callbacks', 'current_class_callback', []));// Confirm the deprecated class callback is not called, as expected.$this->assertNull(component_class_callback('fake_hooktest\callbacks', 'old_class_callback', [], null, true));$this->assertDebuggingNotCalled();}/*** Normalise the sort order of callbacks to help with asserts.** @param array $callbacks*/private function normalise_callbacks(array &$callbacks): void {foreach ($callbacks as &$callback) {ksort($callback);}}}