diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php index 2afb8c30e31e0..a7a33bb7b59a2 100644 --- a/lib/phpunit/classes/advanced_testcase.php +++ b/lib/phpunit/classes/advanced_testcase.php @@ -76,6 +76,11 @@ final public function runBare() { parent::runBare(); // set DB reference in case somebody mocked it in test $DB = phpunit_util::get_global_backup('DB'); + + // Deal with any debugging messages. + phpunit_util::display_debugging_messages(); + phpunit_util::reset_debugging(); + } catch (Exception $e) { // cleanup after failed expectation phpunit_util::reset_all_data(); @@ -234,6 +239,70 @@ public function resetAfterTest($reset = true) { $this->resetAfterTest = $reset; } + /** + * Return debugging messages from the current test. + * @return array + */ + public function getDebuggingMessages() { + return phpunit_util::get_debugging_messages(); + } + + /** + * Clear all previous debugging messages in current test. + * @return array + */ + public function resetDebugging() { + return phpunit_util::reset_debugging(); + } + + /** + * Assert that exactly debugging was just called once. + * + * Discards the debugging message if successful. + * + * @param null|string $debugmessage null means any + * @param null|string $debuglevel null means any + * @param string $message + */ + public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') { + $debugging = phpunit_util::get_debugging_messages(); + $count = count($debugging); + + if ($count == 0) { + if ($message === '') { + $message = 'Expectation failed, debugging() not triggered.'; + } + $this->fail($message); + } + if ($count > 1) { + if ($message === '') { + $message = 'Expectation failed, debugging() triggered '.$count.' times.'; + } + $this->fail($message); + } + $this->assertEquals(1, $count); + + $debug = reset($debugging); + if ($debugmessage !== null) { + $this->assertSame($debugmessage, $debug->message, $message); + } + if ($debuglevel !== null) { + $this->assertSame($debuglevel, $debug->level, $message); + } + + phpunit_util::reset_debugging(); + } + + public function assertDebuggingNotCalled($message = '') { + $debugging = phpunit_util::get_debugging_messages(); + $count = count($debugging); + + if ($message === '') { + $message = 'Expectation failed, debugging() was triggered.'; + } + $this->assertEquals(0, $count, $message); + } + /** * Cleanup after all tests are executed. * diff --git a/lib/phpunit/classes/database_driver_testcase.php b/lib/phpunit/classes/database_driver_testcase.php index dc07596467696..09b7dd2544b11 100644 --- a/lib/phpunit/classes/database_driver_testcase.php +++ b/lib/phpunit/classes/database_driver_testcase.php @@ -133,4 +133,68 @@ public static function tearDownAfterClass() { phpunit_util::reset_all_data(); parent::tearDownAfterClass(); } + + /** + * Return debugging messages from the current test. + * @return array + */ + public function getDebuggingMessages() { + return phpunit_util::get_debugging_messages(); + } + + /** + * Clear all previous debugging messages in current test. + * @return array + */ + public function resetDebugging() { + return phpunit_util::reset_debugging(); + } + + /** + * Assert that exactly debugging was just called once. + * + * Discards the debugging message if successful. + * + * @param null|string $debugmessage null means any + * @param null|string $debuglevel null means any + * @param string $message + */ + public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') { + $debugging = phpunit_util::get_debugging_messages(); + $count = count($debugging); + + if ($count == 0) { + if ($message === '') { + $message = 'Expectation failed, debugging() not triggered.'; + } + $this->fail($message); + } + if ($count > 1) { + if ($message === '') { + $message = 'Expectation failed, debugging() triggered '.$count.' times.'; + } + $this->fail($message); + } + $this->assertEquals(1, $count); + + $debug = reset($debugging); + if ($debugmessage !== null) { + $this->assertSame($debugmessage, $debug->message, $message); + } + if ($debuglevel !== null) { + $this->assertSame($debuglevel, $debug->level, $message); + } + + phpunit_util::reset_debugging(); + } + + public function assertDebuggingNotCalled($message = '') { + $debugging = phpunit_util::get_debugging_messages(); + $count = count($debugging); + + if ($message === '') { + $message = 'Expectation failed, debugging() was triggered.'; + } + $this->assertEquals(0, $count, $message); + } } diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index de96f3dbeb94b..d9b0d571aa09e 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -57,6 +57,9 @@ class phpunit_util { /** @var resource used for prevention of parallel test execution */ protected static $lockhandle = null; + /** @var array list of debugging messages triggered during the last test execution */ + protected static $debuggings = array(); + /** * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot. * @@ -544,6 +547,10 @@ public static function reset_dataroot() { public static function reset_all_data($logchanges = false) { global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION, $GROUPLIB_CACHE; + // 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'); @@ -1160,4 +1167,70 @@ private static function directory_has_tests($dir) { } return false; } + + /** + * To be called from debugging() only. + * @param string $message + * @param int $level + * @param string $from + */ + public static function debugging_triggered($message, $level, $from) { + // Store only if debugging triggered from actual test, + // we need normal debugging outside of tests to find problems in our phpunit integration. + $backtrace = debug_backtrace(); + + foreach ($backtrace as $bt) { + $intest = false; + if (isset($bt['object']) and is_object($bt['object'])) { + if ($bt['object'] instanceof PHPUnit_Framework_TestCase) { + if (strpos($bt['function'], 'test') === 0) { + $intest = true; + break; + } + } + } + } + if (!$intest) { + return false; + } + + $debug = new stdClass(); + $debug->message = $message; + $debug->level = $level; + $debug->from = $from; + + self::$debuggings[] = $debug; + + return true; + } + + /** + * Resets the list of debugging messages. + */ + public static function reset_debugging() { + self::$debuggings = array(); + } + + /** + * Returns all debugging messages triggered during test. + * @return array with instances having message, level and stacktrace property. + */ + public static function get_debugging_messages() { + return self::$debuggings; + } + + /** + * Prints out any debug messages accumulated during test execution. + * @return bool false if no debug messages, true if debug triggered + */ + public static function display_debugging_messages() { + if (empty(self::$debuggings)) { + return false; + } + foreach(self::$debuggings as $debug) { + echo 'Debugging: ' . $debug->message . "\n" . trim($debug->from) . "\n"; + } + + return true; + } } diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php index 302ab1f7bb559..4b6e75fc04509 100644 --- a/lib/phpunit/tests/advanced_test.php +++ b/lib/phpunit/tests/advanced_test.php @@ -36,6 +36,40 @@ */ class core_phpunit_advanced_testcase extends advanced_testcase { + public function test_debugging() { + global $CFG; + $this->resetAfterTest(); + + debugging('hokus'); + $this->assertDebuggingCalled(); + debugging('pokus'); + $this->assertDebuggingCalled('pokus'); + debugging('pokus', DEBUG_MINIMAL); + $this->assertDebuggingCalled('pokus', DEBUG_MINIMAL); + $this->assertDebuggingNotCalled(); + + debugging('a'); + debugging('b', DEBUG_MINIMAL); + debugging('c', DEBUG_DEVELOPER); + $debuggings = $this->getDebuggingMessages(); + $this->assertEquals(3, count($debuggings)); + $this->assertSame('a', $debuggings[0]->message); + $this->assertSame(DEBUG_NORMAL, $debuggings[0]->level); + $this->assertSame('b', $debuggings[1]->message); + $this->assertSame(DEBUG_MINIMAL, $debuggings[1]->level); + $this->assertSame('c', $debuggings[2]->message); + $this->assertSame(DEBUG_DEVELOPER, $debuggings[2]->level); + + $this->resetDebugging(); + $this->assertDebuggingNotCalled(); + $debuggings = $this->getDebuggingMessages(); + $this->assertEquals(0, count($debuggings)); + + $CFG->debug = DEBUG_NONE; + debugging('hokus'); + $this->assertDebuggingNotCalled(); + } + public function test_set_user() { global $USER, $DB; diff --git a/lib/weblib.php b/lib/weblib.php index d18546206a1db..3c0dc19e982b2 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2853,9 +2853,13 @@ function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) { } $from = format_backtrace($backtrace, CLI_SCRIPT); if (PHPUNIT_TEST) { - echo 'Debugging: ' . $message . "\n" . $from; + if (phpunit_util::debugging_triggered($message, $level, $from)) { + // We are inside test, the debug message was logged. + return true; + } + } - } else if (NO_DEBUG_DISPLAY) { + if (NO_DEBUG_DISPLAY) { // script does not want any errors or debugging in output, // we send the info to error log instead error_log('Debugging: ' . $message . $from);