/** * Reset contents of all database tables to initial values, reset caches, etc. * * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care! * * @static * @param bool $detectchanges * true - changes in global state and database are reported as errors * false - no errors reported * null - only critical problems are reported as errors * @return void */ public static function reset_all_data($detectchanges = false) { global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION; // Stop any message redirection. phpunit_util::stop_message_redirection(); // Stop any message redirection. phpunit_util::stop_phpmailer_redirection(); // Stop any message redirection. phpunit_util::stop_event_redirection(); // We used to call gc_collect_cycles here to ensure desctructors were called between tests. // This accounted for 25% of the total time running phpunit - so we removed it. // Show any unhandled debugging messages, the runbare() could already reset it. self::display_debugging_messages(); self::reset_debugging(); // reset global $DB in case somebody mocked it $DB = self::get_global_backup('DB'); if ($DB->is_transaction_started()) { // we can not reset inside transaction $DB->force_transaction_rollback(); } $resetdb = self::reset_database(); $warnings = array(); if ($detectchanges === true) { if ($resetdb) { $warnings[] = 'Warning: unexpected database modification, resetting DB state'; } $oldcfg = self::get_global_backup('CFG'); $oldsite = self::get_global_backup('SITE'); foreach ($CFG as $k => $v) { if (!property_exists($oldcfg, $k)) { $warnings[] = 'Warning: unexpected new $CFG->' . $k . ' value'; } else { if ($oldcfg->{$k} !== $CFG->{$k}) { $warnings[] = 'Warning: unexpected change of $CFG->' . $k . ' value'; } } unset($oldcfg->{$k}); } if ($oldcfg) { foreach ($oldcfg as $k => $v) { $warnings[] = 'Warning: unexpected removal of $CFG->' . $k; } } if ($USER->id != 0) { $warnings[] = 'Warning: unexpected change of $USER'; } if ($COURSE->id != $oldsite->id) { $warnings[] = 'Warning: unexpected change of $COURSE'; } } if (ini_get('max_execution_time') != 0) { // This is special warning for all resets because we do not want any // libraries to mess with timeouts unintentionally. // Our PHPUnit integration is not supposed to change it either. if ($detectchanges !== false) { $warnings[] = 'Warning: max_execution_time was changed to ' . ini_get('max_execution_time'); } set_time_limit(0); } // restore original globals $_SERVER = self::get_global_backup('_SERVER'); $CFG = self::get_global_backup('CFG'); $SITE = self::get_global_backup('SITE'); $_GET = array(); $_POST = array(); $_FILES = array(); $_REQUEST = array(); $COURSE = $SITE; // reinitialise following globals $OUTPUT = new bootstrap_renderer(); $PAGE = new moodle_page(); $FULLME = null; $ME = null; $SCRIPT = null; // Empty sessison and set fresh new not-logged-in user. \core\session\manager::init_empty_session(); // reset all static caches \core\event\manager::phpunit_reset(); accesslib_clear_all_caches(true); get_string_manager()->reset_caches(true); reset_text_filters_cache(true); events_get_handlers('reset'); core_text::reset_caches(); get_message_processors(false, true); filter_manager::reset_caches(); // Reset internal users. core_user::reset_internal_users(); //TODO MDL-25290: add more resets here and probably refactor them to new core function // Reset course and module caches. if (class_exists('format_base')) { // If file containing class is not loaded, there is no cache there anyway. format_base::reset_course_cache(0); } get_fast_modinfo(0, 0, true); // Reset other singletons. if (class_exists('core_plugin_manager')) { core_plugin_manager::reset_caches(true); } if (class_exists('\\core\\update\\checker')) { \core\update\checker::reset_caches(true); } if (class_exists('\\core\\update\\deployer')) { \core\update\deployer::reset_caches(true); } // purge dataroot directory self::reset_dataroot(); // restore original config once more in case resetting of caches changed CFG $CFG = self::get_global_backup('CFG'); // inform data generator self::get_data_generator()->reset(); // fix PHP settings error_reporting($CFG->debug); // verify db writes just in case something goes wrong in reset if (self::$lastdbwrites != $DB->perf_get_writes()) { error_log('Unexpected DB writes in phpunit_util::reset_all_data()'); self::$lastdbwrites = $DB->perf_get_writes(); } if ($warnings) { $warnings = implode("\n", $warnings); trigger_error($warnings, E_USER_WARNING); } }
/** * Force rollback of all delegated transaction. * Does not throw any exceptions and does not log anything. * * This method should be used only from default exception handlers and other * core code. * * @return void */ public function force_transaction_rollback() { if ($this->transactions) { try { $this->rollback_transaction(); } catch (dml_exception $e) { // ignore any sql errors here, the connection might be broken } } // now enable transactions again $this->transactions = array(); $this->force_rollback = false; \core\event\manager::database_transaction_rolledback(); \core\message\manager::database_transaction_rolledback(); }
/** * Trigger event. */ public final function trigger() { global $CFG; if ($this->restored) { throw new \coding_exception('Can not trigger restored event'); } if ($this->triggered or $this->dispatched) { throw new \coding_exception('Can not trigger event twice'); } $this->validate_before_trigger(); $this->triggered = true; if (isset($CFG->loglifetime) and $CFG->loglifetime != -1) { if ($data = $this->get_legacy_logdata()) { $manager = get_log_manager(); if (method_exists($manager, 'legacy_add_to_log')) { if (is_array($data[0])) { // Some events require several entries in 'log' table. foreach ($data as $d) { call_user_func_array(array($manager, 'legacy_add_to_log'), $d); } } else { call_user_func_array(array($manager, 'legacy_add_to_log'), $data); } } } } if (PHPUNIT_TEST and \phpunit_util::is_redirecting_events()) { $this->dispatched = true; \phpunit_util::event_triggered($this); return; } \core\event\manager::dispatch($this); $this->dispatched = true; if ($legacyeventname = static::get_legacy_eventname()) { events_trigger_legacy($legacyeventname, $this->get_legacy_eventdata()); } }
/** * Get the full list of observers for the system. * * @return array An array of observers in the system. */ public static function get_observer_list() { $events = \core\event\manager::get_all_observers(); foreach ($events as $key => $observers) { foreach ($observers as $observerskey => $observer) { $events[$key][$observerskey]->parentplugin = \core_plugin_manager::instance()->get_parent_of_subplugin($observer->plugintype); } } return $events; }
/** * Trigger event. */ public final function trigger() { global $CFG; if ($this->restored) { throw new \coding_exception('Can not trigger restored event'); } if ($this->triggered or $this->dispatched) { throw new \coding_exception('Can not trigger event twice'); } $this->validate_before_trigger(); $this->triggered = true; if (isset($CFG->loglifetime) and $CFG->loglifetime != -1) { if ($data = $this->get_legacy_logdata()) { call_user_func_array('add_to_log', $data); } } if (PHPUNIT_TEST and \phpunit_util::is_redirecting_events()) { $this->dispatched = true; \phpunit_util::event_triggered($this); return; } \core\event\manager::dispatch($this); $this->dispatched = true; if ($legacyeventname = static::get_legacy_eventname()) { events_trigger_legacy($legacyeventname, $this->get_legacy_eventdata()); } }
public function test_trigger_problems() { $event = \core_tests\event\unittest_executed::create(array('courseid' => 1, 'context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10))); $event->trigger(); try { $event->trigger(); $this->fail('Exception expected on double trigger'); } catch (\moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e); } $data = $event->get_data(); $restored = \core_tests\event\unittest_executed::restore($data, array()); $this->assertTrue($restored->is_triggered()); $this->assertTrue($restored->is_restored()); try { $restored->trigger(); $this->fail('Exception expected on triggering of restored event'); } catch (\moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e); } $event = \core_tests\event\unittest_executed::create(array('courseid' => 1, 'context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10))); try { \core\event\manager::dispatch($event); $this->fail('Exception expected on manual event dispatching'); } catch (\moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e); } }
/** * Call when delegated transaction failed, this rolls back * all delegated transactions up to the top most level. * * In many cases you do not need to call this method manually, * because all open delegated transactions are rolled back * automatically if exceptions not caught. * * @param moodle_transaction $transaction An instance of a moodle_transaction. * @param Exception $e The related exception to this transaction rollback. * @return void This does not return, instead the exception passed in will be rethrown. */ public function rollback_delegated_transaction(moodle_transaction $transaction, Exception $e) { if ($transaction->is_disposed()) { throw new dml_transaction_exception('Transactions already disposed', $transaction); } // mark as disposed so that it can not be used again $transaction->dispose(); // one rollback at any level rollbacks everything $this->force_rollback = true; if (empty($this->transactions) or $transaction !== $this->transactions[count($this->transactions) - 1]) { // this may or may not be a coding problem, better just rethrow the exception, // because we do not want to loose the original $e throw $e; } if (count($this->transactions) == 1) { // only rollback the top most level $this->rollback_transaction(); } array_pop($this->transactions); if (empty($this->transactions)) { // finally top most level rolled back $this->force_rollback = false; \core\event\manager::database_transaction_rolledback(); } throw $e; }
/** * This tests the internal method of \core\event\manager::get_observing_classes. * * What we are testing is if we can subscribe to a parent event class, instead of only * the base event class or the final, implemented event class. This enables us to subscribe * to things like all course module view events, all comment created events, etc. */ public function test_observe_parent_event() { $this->resetAfterTest(); // Ensure this has been reset prior to using it. \core_tests\event\unittest_observer::reset(); $course = $this->getDataGenerator()->create_course(); $feed = $this->getDataGenerator()->create_module('feedback', ['course' => $course->id]); $context = context_module::instance($feed->cmid); $data = ['context' => $context, 'courseid' => $course->id, 'objectid' => $feed->id]; // This assertion ensures that basic observe use case did not break. \core\event\manager::phpunit_replace_observers([['eventname' => '\\core_tests\\event\\course_module_viewed', 'callback' => ['\\core_tests\\event\\unittest_observer', 'observe_all_alt']]]); $pageevent = \core_tests\event\course_module_viewed::create($data); $pageevent->trigger(); $this->assertSame(['observe_all_alt'], \core_tests\event\unittest_observer::$info, 'Error observing triggered event'); \core_tests\event\unittest_observer::reset(); // This assertion tests that we can observe an abstract (parent) class instead of the implemented class. \core\event\manager::phpunit_replace_observers([['eventname' => '\\core\\event\\course_module_viewed', 'callback' => ['\\core_tests\\event\\unittest_observer', 'observe_all_alt']]]); $pageevent = \core_tests\event\course_module_viewed::create($data); $pageevent->trigger(); $this->assertSame(['observe_all_alt'], \core_tests\event\unittest_observer::$info, 'Error observing parent class event'); \core_tests\event\unittest_observer::reset(); }
/** * Test that all observer information is returned correctly. */ public function test_get_all_observers() { // Retrieve all observers. $observers = \core\event\manager::get_all_observers(); // Expected information from the workshop allocation scheduled observer. $expected = array(); $observer = new stdClass(); $observer->callable = '\\workshopallocation_scheduled\\observer::workshop_viewed'; $observer->priority = 0; $observer->internal = true; $observer->includefile = null; $observer->plugintype = 'workshopallocation'; $observer->plugin = 'scheduled'; $expected[0] = $observer; $this->assertEquals($expected, $observers['\\mod_workshop\\event\\course_module_viewed']); }