From 4c9e03f0ac8dd2f17c668e0473ff9ddfd664523b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Sat, 13 Oct 2012 17:48:06 +0200 Subject: [PATCH] MDL-36031 implement message redirection for unit testing --- lib/messagelib.php | 26 ++++ lib/phpunit/classes/advanced_testcase.php | 13 ++ lib/phpunit/classes/message_sink.php | 85 ++++++++++++ lib/phpunit/classes/util.php | 55 ++++++++ lib/phpunit/lib.php | 1 + lib/phpunit/tests/advanced_test.php | 150 ++++++++++++++++++++++ 6 files changed, 330 insertions(+) create mode 100644 lib/phpunit/classes/message_sink.php diff --git a/lib/messagelib.php b/lib/messagelib.php index 140ae0e4f12..d007432ab68 100644 --- a/lib/messagelib.php +++ b/lib/messagelib.php @@ -114,6 +114,32 @@ function message_send($eventdata) { $savemessage->timecreated = time(); + if (PHPUNIT_TEST and class_exists('phpunit_util')) { + // Add some more tests to make sure the normal code can actually work. + $componentdir = get_component_directory($eventdata->component); + if (!$componentdir or !is_dir($componentdir)) { + throw new coding_exception('Invalid component specified in message-send(): '.$eventdata->component); + } + if (!file_exists("$componentdir/db/messages.php")) { + throw new coding_exception("$eventdata->component does not contain db/messages.php necessary for message_send()"); + } + $messageproviders = null; + include("$componentdir/db/messages.php"); + if (!isset($messageproviders[$eventdata->name])) { + throw new coding_exception("Missing messaging defaults for event '$eventdata->name' in '$eventdata->component' messages.php file"); + } + unset($componentdir); + unset($messageproviders); + // Now ask phpunit if it wants to catch this message. + if (phpunit_util::is_redirecting_messages()) { + $savemessage->timeread = time(); + $messageid = $DB->insert_record('message_read', $savemessage); + $message = $DB->get_record('message_read', array('id'=>$messageid)); + phpunit_util::message_sent($message); + return $messageid; + } + } + // Fetch enabled processors $processors = get_message_processors(true); // Fetch default (site) preferences diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php index e4bace9dce6..c63eed9ea63 100644 --- a/lib/phpunit/classes/advanced_testcase.php +++ b/lib/phpunit/classes/advanced_testcase.php @@ -306,6 +306,19 @@ abstract class advanced_testcase extends PHPUnit_Framework_TestCase { $this->assertEquals(0, $count, $message); } + /** + * Starts message redirection. + * + * You can verify if messages were sent or not by inspecting the messages + * array in the returned messaging sink instance. The redirection + * can be stopped by calling $sink->close(); + * + * @return phpunit_message_sink + */ + public function redirectMessages() { + return phpunit_util::start_message_redirection(); + } + /** * Cleanup after all tests are executed. * diff --git a/lib/phpunit/classes/message_sink.php b/lib/phpunit/classes/message_sink.php new file mode 100644 index 00000000000..ffbf83e75d6 --- /dev/null +++ b/lib/phpunit/classes/message_sink.php @@ -0,0 +1,85 @@ +. + +/** + * Message sink. + * + * @package core + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +/** + * Message sink. + * + * @package core + * @category phpunit + * @copyright 2012 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class phpunit_message_sink { + /** @var array of records from message_read table */ + protected $messages = array(); + + /** + * Stop message redirection. + * + * Use if you do not want message redirected any more. + */ + public function close() { + phpunit_util::stop_message_redirection(); + } + + /** + * To be called from phpunit_util only! + * + * @param stdClass $message record from message_read table + */ + public function add_message($message) { + /* Number messages from 0. */ + $this->messages[] = $message; + } + + /** + * Returns all redirected messages. + * + * The instances are records form the message_read table. + * The array indexes are numbered from 0 and the order is matching + * the creation of events. + * + * @return array + */ + public function get_messages() { + return $this->messages; + } + + /** + * Return number of messages redirected to this sink. + * @return int + */ + public function count() { + return count($this->messages); + } + + /** + * Removes all previously stored messages. + */ + public function clear() { + $this->messages = array(); + } +} diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 67da8ef47e6..97df2fe838a 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -60,6 +60,9 @@ class phpunit_util { /** @var array list of debugging messages triggered during the last test execution */ protected static $debuggings = array(); + /** @var phpunit_message_sink alternative target for moodle messaging */ + protected static $messagesink = null; + /** * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot. * @@ -547,6 +550,9 @@ class phpunit_util { public static function reset_all_data($logchanges = false) { global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION, $GROUPLIB_CACHE; + // Stop any message redirection. + phpunit_util::stop_message_redirection(); + // Release memory and indirectly call destroy() methods to release resource handles, etc. gc_collect_cycles(); @@ -1244,4 +1250,53 @@ class phpunit_util { return true; } + + /** + * Start message redirection. + * + * Note: Do not call directly from tests, + * use $sink = $this->redirectMessages() instead. + * + * @return phpunit_message_sink + */ + public static function start_message_redirection() { + if (self::$messagesink) { + self::stop_message_redirection(); + } + self::$messagesink = new phpunit_message_sink(); + return self::$messagesink; + } + + /** + * End message redirection. + * + * Note: Do not call directly from tests, + * use $sink->close() instead. + */ + public static function stop_message_redirection() { + self::$messagesink = null; + } + + /** + * Are messages redirected to some sink? + * + * Note: to be called from messagelib.php only! + * + * @return bool + */ + public static function is_redirecting_messages() { + return !empty(self::$messagesink); + } + + /** + * To be called from messagelib.php only! + * + * @param stdClass $message record from message_read table + * @return bool true means send message, false means message "sent" to sink. + */ + public static function message_sent($message) { + if (self::$messagesink) { + self::$messagesink->add_message($message); + } + } } diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php index a7001576128..95b7f73466d 100644 --- a/lib/phpunit/lib.php +++ b/lib/phpunit/lib.php @@ -29,6 +29,7 @@ require_once('PHPUnit/Autoload.php'); require_once('PHPUnit/Extensions/Database/Autoload.php'); require_once(__DIR__.'/classes/util.php'); +require_once(__DIR__.'/classes/message_sink.php'); require_once(__DIR__.'/classes/basic_testcase.php'); require_once(__DIR__.'/classes/database_driver_testcase.php'); require_once(__DIR__.'/classes/arraydataset.php'); diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php index 4b6e75fc045..d1d1db8dc16 100644 --- a/lib/phpunit/tests/advanced_test.php +++ b/lib/phpunit/tests/advanced_test.php @@ -317,4 +317,154 @@ class core_phpunit_advanced_testcase extends advanced_testcase { $this->assertTrue($DB->record_exists('user', array('username'=>'noidea'))); $this->assertTrue($DB->record_exists('user', array('username'=>'onemore'))); } + + public function test_message_redirection() { + global $DB; + + $this->preventResetByRollback(); // Messaging is not compatible with transactions... + $this->resetAfterTest(false); + + $user1 = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); + + // Any core message will do here. + $message1 = new stdClass(); + $message1->component = 'moodle'; + $message1->name = 'instantmessage'; + $message1->userfrom = $user1; + $message1->userto = $user2; + $message1->subject = 'message subject 1'; + $message1->fullmessage = 'message body'; + $message1->fullmessageformat = FORMAT_MARKDOWN; + $message1->fullmessagehtml = '

message body

'; + $message1->smallmessage = 'small message'; + + $message2 = new stdClass(); + $message2->component = 'moodle'; + $message2->name = 'instantmessage'; + $message2->userfrom = $user2; + $message2->userto = $user1; + $message2->subject = 'message subject 2'; + $message2->fullmessage = 'message body'; + $message2->fullmessageformat = FORMAT_MARKDOWN; + $message2->fullmessagehtml = '

message body

'; + $message2->smallmessage = 'small message'; + + // There should be debugging message without redirection. + message_send($message1); + $this->assertDebuggingCalled(null, null, 'message_send() must print debug message that messaging is disabled in phpunit tests.'); + + // Sink should catch messages; + $sink = $this->redirectMessages(); + $mid1 = message_send($message1); + $mid2 = message_send($message2); + + $this->assertDebuggingNotCalled('message redirection must prevent debug messages from the message_send()'); + $this->assertEquals(2, $sink->count()); + $this->assertGreaterThanOrEqual(1, $mid1); + $this->assertGreaterThanOrEqual($mid1, $mid2); + + $messages = $sink->get_messages(); + $this->assertTrue(is_array($messages)); + $this->assertEquals(2, count($messages)); + $this->assertEquals($mid1, $messages[0]->id); + $this->assertEquals($message1->userto->id, $messages[0]->useridto); + $this->assertEquals($message1->userfrom->id, $messages[0]->useridfrom); + $this->assertEquals($message1->smallmessage, $messages[0]->smallmessage); + $this->assertEquals($mid2, $messages[1]->id); + $this->assertEquals($message2->userto->id, $messages[1]->useridto); + $this->assertEquals($message2->userfrom->id, $messages[1]->useridfrom); + $this->assertEquals($message2->smallmessage, $messages[1]->smallmessage); + + // Test resetting. + $sink->clear(); + $messages = $sink->get_messages(); + $this->assertTrue(is_array($messages)); + $this->assertEquals(0, count($messages)); + + message_send($message1); + $messages = $sink->get_messages(); + $this->assertTrue(is_array($messages)); + $this->assertEquals(1, count($messages)); + + // Test closing. + $sink->close(); + $messages = $sink->get_messages(); + $this->assertTrue(is_array($messages)); + $this->assertEquals(1, count($messages), 'Messages in sink are supposed to stay there after close'); + + // Test debugging is enabled again. + message_send($message1); + $this->assertDebuggingCalled(null, null, 'message_send() must print debug message that messaging is disabled in phpunit tests.'); + + // Test invalid names and components. + + $sink = $this->redirectMessages(); + + $message3 = new stdClass(); + $message3->component = 'xxxx_yyyyy'; + $message3->name = 'instantmessage'; + $message3->userfrom = $user2; + $message3->userto = $user1; + $message3->subject = 'message subject 2'; + $message3->fullmessage = 'message body'; + $message3->fullmessageformat = FORMAT_MARKDOWN; + $message3->fullmessagehtml = '

message body

'; + $message3->smallmessage = 'small message'; + + try { + message_send($message3); + $this->fail('coding expcetion expected if invalid component specified'); + } catch (coding_exception $e) { + $this->assertTrue(true); + } + + $message3->component = 'moodle'; + $message3->name = 'yyyyyy'; + try { + message_send($message3); + $this->fail('coding expcetion expected if invalid name specified'); + } catch (coding_exception $e) { + $this->assertTrue(true); + } + + message_send($message1); + $this->assertEquals(1, $sink->count()); + + // Test if sink can be carried over to next test. + $this->assertTrue(phpunit_util::is_redirecting_messages()); + return $sink; + } + + /** + * @depends test_message_redirection + */ + public function test_message_redirection_noreset($sink) { + $this->preventResetByRollback(); // Messaging is not compatible with transactions... + $this->resetAfterTest(true); + + $this->assertTrue(phpunit_util::is_redirecting_messages()); + $this->assertEquals(1, $sink->count()); + + $message = new stdClass(); + $message->component = 'moodle'; + $message->name = 'instantmessage'; + $message->userfrom = get_admin(); + $message->userto = get_admin(); + $message->subject = 'message subject 1'; + $message->fullmessage = 'message body'; + $message->fullmessageformat = FORMAT_MARKDOWN; + $message->fullmessagehtml = '

message body

'; + $message->smallmessage = 'small message'; + + message_send($message); + $this->assertEquals(2, $sink->count()); + } + + /** + * @depends test_message_redirection_noreset + */ + public function test_message_redirection_reset() { + $this->assertFalse(phpunit_util::is_redirecting_messages(), 'Test reset must stop message redirection.'); + } }