MDL-31857basic phpunit support
authorPetr Skoda <commits@skodak.org>
Wed, 21 Mar 2012 09:31:37 +0000 (10:31 +0100)
committerPetr Skoda <commits@skodak.org>
Wed, 21 Mar 2012 09:31:37 +0000 (10:31 +0100)
Thanks Eloy Lafuente, Tim Hunt and Sam Hemelryk for valuable feedback and ideas.

36 files changed:
.gitignore
admin/tool/phpunit/cli/util.php [new file with mode: 0644]
admin/tool/phpunit/index.php [new file with mode: 0644]
admin/tool/phpunit/lang/en/tool_phpunit.php [new file with mode: 0644]
admin/tool/phpunit/settings.php [new file with mode: 0644]
admin/tool/phpunit/version.php [new file with mode: 0644]
config-dist.php
lib/accesslib.php
lib/ddl/database_manager.php
lib/ddl/simpletest/testddl.php
lib/moodlelib.php
lib/phpunit/bootstrap.php [new file with mode: 0644]
lib/phpunit/lib.php [new file with mode: 0644]
lib/phpunit/readme.md [new file with mode: 0644]
lib/setup.php
lib/simpletest/testcomponentlib.php
lib/simpletest/testcsslib.php
lib/simpletest/testexternallib.php
lib/simpletest/testfilelib.php
lib/simpletest/testformslib.php
lib/simpletest/testhtmlwriter.php
lib/simpletest/testnavigationlib.php
lib/simpletest/testoutputcomponents.php
lib/simpletest/testquestionlib.php
lib/simpletest/testrss.php
lib/tests/htmlpurifier_test.php [new file with mode: 0644]
lib/tests/phpunit_test.php [new file with mode: 0644]
lib/tests/textlib_test.php [new file with mode: 0644]
lib/weblib.php
mod/lti/simpletest/testlocallib.php
mod/scorm/simpletest/test_formatduration.php
mod/url/locallib.php
mod/url/simpletest/testlib.php
mod/url/tests/lib_test.php [new file with mode: 0644]
mod/wiki/simpletest/testwikiparser.php
phpunit.xml.dist [new file with mode: 0644]

index 5a12349..a57c23f 100644 (file)
@@ -25,3 +25,4 @@ CVS
 /.project
 /.buildpath
 /.cache
+/phpunit.xml
\ No newline at end of file
diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php
new file mode 100644 (file)
index 0000000..c270e17
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPUnit related utilities.
+ *
+ * Exit codes:
+ *  0   - success
+ *  1   - general error
+ *  130 - coding error
+ *  131 - configuration problem
+ *  133 - drop existing data before installing
+ *
+ * @package    tool_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('PHPUNIT_CLI_UTIL', true);
+
+require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php');
+require_once($CFG->libdir.'/phpunit/lib.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/upgradelib.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once($CFG->libdir.'/pluginlib.php');
+require_once($CFG->libdir.'/installlib.php');
+
+// now get cli options
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'drop'        => false,
+        'install'     => false,
+        'buildconfig' => false,
+        'help'        => false,
+    ),
+    array(
+        'h' => 'help'
+    )
+);
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+$drop = $options['drop'];
+$install = $options['install'];
+$buildconfig = $options['buildconfig'];
+
+if ($options['help'] or (!$drop and !$install and !$buildconfig)) {
+    $help = "Various PHPUnit utility functions
+
+Options:
+--drop                Drop database and dataroot
+--install             Install database
+--buildconfig         Build /phpunit.xml from /phpunit.xml.dist that includes suites for all plugins and core
+
+-h, --help            Print out this help
+
+Example:
+\$/usr/bin/php lib/phpunit/tool.php
+";
+    echo $help;
+    die;
+}
+
+if ($buildconfig) {
+    phpunit_util::build_config_file();
+    exit(0);
+
+} else if ($drop) {
+    phpunit_util::drop_site();
+    // note: we must stop here because $CFG is messed up and we can not reinstall, sorry
+    exit(0);
+
+} else if ($install) {
+    phpunit_util::install_site();
+    exit(0);
+}
diff --git a/admin/tool/phpunit/index.php b/admin/tool/phpunit/index.php
new file mode 100644 (file)
index 0000000..5c55e70
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPUnit info
+ *
+ * @package    tool_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ */
+
+define('NO_OUTPUT_BUFFERING', true);
+
+require(dirname(__FILE__) . '/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+admin_externalpage_setup('toolphpunit');
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('pluginname', 'tool_phpunit'));
+echo $OUTPUT->box_start();
+
+$info = file_get_contents("$CFG->libdir/phpunit/readme.md");
+echo markdown_to_html($info);
+
+echo $OUTPUT->box_end();
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/admin/tool/phpunit/lang/en/tool_phpunit.php b/admin/tool/phpunit/lang/en/tool_phpunit.php
new file mode 100644 (file)
index 0000000..08dbbbd
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'tool_phpunit'
+ *
+ * @package    tool_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['pluginname'] = 'PHPUnit tests';
diff --git a/admin/tool/phpunit/settings.php b/admin/tool/phpunit/settings.php
new file mode 100644 (file)
index 0000000..8e06b2f
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * PHPunit integration
+ *
+ * @package    tool_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die;
+
+$ADMIN->add('development', new admin_externalpage('toolphpunit', get_string('pluginname', 'tool_phpunit'), "$CFG->wwwroot/$CFG->admin/tool/phpunit/index.php"));
diff --git a/admin/tool/phpunit/version.php b/admin/tool/phpunit/version.php
new file mode 100644 (file)
index 0000000..e745026
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Plugin version info
+ *
+ * @package    tool_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2012030800; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012030100; // Requires this Moodle version
+$plugin->component = 'tool_phpunit'; // Full name of the plugin (used for diagnostics)
+
index abc8da1..e4fe58f 100644 (file)
@@ -479,7 +479,13 @@ $CFG->admin = 'admin';
 // Example:
 //   $CFG->forced_plugin_settings = array('pluginname'  => array('settingname' => 'value', 'secondsetting' => 'othervalue'),
 //                                        'otherplugin' => array('mysetting' => 'myvalue', 'thesetting' => 'thevalue'));
-
+//
+//=========================================================================
+// 9. PHPUNIT SUPPORT
+//=========================================================================
+// $CFG->phpunit_prefix = 'phpu_';
+// $CFG->phpunit_dataroot = '/home/example/phpu_moodledata';
+// $CFG->phpunit_directorypermissions = 02777; // optional
 
 //=========================================================================
 // ALL DONE!  To continue installation, visit your main page with a browser
index bd45588..62e7337 100644 (file)
@@ -217,7 +217,7 @@ $ACCESSLIB_PRIVATE->capabilities     = null;    // detailed information about th
  */
 function accesslib_clear_all_caches_for_unit_testing() {
     global $UNITTEST, $USER;
-    if (empty($UNITTEST->running)) {
+    if (empty($UNITTEST->running) and !PHPUNITTEST) {
         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
     }
 
index 22d24e4..c4409d0 100644 (file)
@@ -112,18 +112,15 @@ class database_manager {
 
     /**
      * Reset a sequence to the id field of a table.
-     * @param string $table Name of table.
-     * @throws ddl_exception|ddl_table_missing_exception Exception thrown upon reset errors.
+     * @param string|xmldb_table $table Name of table.
+     * @throws ddl_exception thrown upon reset errors.
      */
     public function reset_sequence($table) {
         if (!is_string($table) and !($table instanceof xmldb_table)) {
             throw new ddl_exception('ddlunknownerror', NULL, 'incorrect table parameter!');
         }
 
-    /// Check the table exists
-        if (!$this->table_exists($table)) {
-            throw new ddl_table_missing_exception($table);
-        }
+        // Do not test if table exists because it is slow
 
         if (!$sqlarr = $this->generator->getResetSequenceSQL($table)) {
             throw new ddl_exception('ddlunknownerror', null, 'table reset sequence sql not generated');
index 4a27f80..5d452a0 100644 (file)
@@ -8,6 +8,8 @@ if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
 
+global $CFG;
+
 require_once($CFG->libdir . '/adminlib.php');
 
 class ddl_test extends UnitTestCase {
index 0099f60..fd2765d 100644 (file)
@@ -7763,7 +7763,7 @@ function get_plugin_types($fullpaths=true) {
 function get_plugin_list($plugintype) {
     global $CFG;
 
-    $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'phpunit');
+    $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
     if ($plugintype == 'auth') {
         // Historically we have had an auth plugin called 'db', so allow a special case.
         $key = array_search('db', $ignored);
diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php
new file mode 100644 (file)
index 0000000..0dd6106
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Prepares PHPUnit environment, it is called automatically.
+ *
+ * Exit codes:
+ *  0   - success
+ *  1   - general error
+ *  130 - coding error
+ *  131 - configuration problem
+ *  132 - drop data, then install new test database
+ *
+ * @package    core_core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// we want to know about all problems
+error_reporting(E_ALL | E_STRICT);
+ini_set('display_errors', '1');
+ini_set('log_errors', '1');
+
+if (isset($_SERVER['REMOTE_ADDR'])) {
+    phpunit_bootstrap_error('Unit tests can be executed only from commandline!', 1);
+}
+
+if (defined('PHPUNITTEST')) {
+    phpunit_bootstrap_error("PHPUNITTEST constant must not be manually defined anywhere!", 130);
+}
+define('PHPUNITTEST', true);
+
+if (defined('CLI_SCRIPT')) {
+    phpunit_bootstrap_error('CLI_SCRIPT must not be manually defined in any PHPUnit test scripts', 130);
+}
+define('CLI_SCRIPT', true);
+
+define('NO_OUTPUT_BUFFERING', true);
+
+// only load CFG from config.php
+define('ABORT_AFTER_CONFIG', true);
+require(__DIR__ . '/../../config.php');
+
+// remove error handling overrides done in config.php
+error_reporting(E_ALL);
+ini_set('display_errors', '1');
+ini_set('log_errors', '1');
+
+// prepare dataroot
+umask(0);
+if (isset($CFG->phpunit_directorypermissions)) {
+    $CFG->directorypermissions = $CFG->phpunit_directorypermissions;
+} else {
+    $CFG->directorypermissions = 02777;
+}
+$CFG->filepermissions = ($CFG->directorypermissions & 0666);
+if (!isset($CFG->phpunit_dataroot)) {
+    phpunit_bootstrap_error('Missing $CFG->phpunit_dataroot in config.php, can not run tests!', 131);
+}
+if (isset($CFG->dataroot) and $CFG->phpunit_dataroot === $CFG->dataroot) {
+    phpunit_bootstrap_error('$CFG->dataroot and $CFG->phpunit_dataroot must not be identical, can not run tests!', 131);
+}
+if (!file_exists($CFG->phpunit_dataroot)) {
+    mkdir($CFG->phpunit_dataroot, $CFG->directorypermissions);
+}
+if (!is_dir($CFG->phpunit_dataroot)) {
+    phpunit_bootstrap_error('$CFG->phpunit_dataroot directory can not be created, can not run tests!', 131);
+}
+if (!is_writable($CFG->phpunit_dataroot)) {
+    // try to fix premissions if possible
+    if (function_exists('posix_getuid')) {
+        $chmod = fileperms($CFG->phpunit_dataroot);
+        if (fileowner($dir) == posix_getuid()) {
+            $chmod = $chmod | 0700;
+            chmod($CFG->phpunit_dataroot, $chmod);
+        }
+    }
+    if (!is_writable($CFG->phpunit_dataroot)) {
+        phpunit_bootstrap_error('$CFG->phpunit_dataroot directory is not writable, can not run tests!', 131);
+    }
+}
+if (!file_exists("$CFG->phpunit_dataroot/phpunittestdir.txt")) {
+    if ($dh = opendir($CFG->phpunit_dataroot)) {
+        while (($file = readdir($dh)) !== false) {
+            if ($file === 'phpunit' or $file === '.' or $file === '..' or $file === '.DS_store') {
+                continue;
+            }
+            phpunit_bootstrap_error('$CFG->phpunit_dataroot directory is not empty, can not run tests! Is it used for anything else?', 131);
+        }
+        closedir($dh);
+        unset($dh);
+        unset($file);
+    }
+
+    // now we are 100% sure this dir is used only for phpunit tests
+    phpunit_bootstrap_initdataroot($CFG->phpunit_dataroot);
+}
+
+
+// verify db prefix
+if (!isset($CFG->phpunit_prefix)) {
+    phpunit_bootstrap_error('Missing $CFG->phpunit_dataroot in config.php, can not run tests!', 131);
+}
+if (isset($CFG->prefix) and $CFG->prefix === $CFG->phpunit_prefix) {
+    phpunit_bootstrap_error('$CFG->prefix and $CFG->phpunit_prefix must not be identical, can not run tests!', 131);
+}
+
+// throw away standard CFG settings
+
+$CFG->dataroot = $CFG->phpunit_dataroot;
+$CFG->prefix = $CFG->phpunit_prefix;
+
+$allowed = array('wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
+                 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions');
+$productioncfg = (array)$CFG;
+$CFG = new stdClass();
+foreach ($productioncfg as $key=>$value) {
+    if (!in_array($key, $allowed) and strpos($key, 'phpunit_') !== 0) {
+        // ignore
+        continue;
+    }
+    $CFG->{$key} = $value;
+}
+unset($key);
+unset($value);
+unset($allowed);
+unset($productioncfg);
+
+// force the same CFG settings in all sites
+$CFG->debug = (E_ALL | E_STRICT | 38911); // can not use DEBUG_DEVELOPER here
+$CFG->debugdisplay = 1;
+error_reporting($CFG->debug);
+ini_set('display_errors', '1');
+ini_set('log_errors', '0');
+
+$CFG->noemailever = true; // better not mail anybody from tests, override temporarily if necessary
+$CFG->cachetext = 0; // disable this very nasty setting
+
+// some ugly hacks
+$CFG->themerev = 1;
+$CFG->jsrev = 1;
+
+// load test case stub classes and other stuff
+require_once("$CFG->dirroot/lib/phpunit/lib.php");
+
+// finish moodle init
+define('ABORT_AFTER_CONFIG_CANCEL', true);
+require("$CFG->dirroot/lib/setup.php");
+
+raise_memory_limit(MEMORY_EXTRA);
+
+if (defined('PHPUNIT_CLI_UTIL')) {
+    // all other tests are done in the CLI scripts...
+    return;
+}
+
+if (!phpunit_util::is_testing_ready()) {
+    phpunit_bootstrap_error('Database is not initialised to run unit tests, please use "php admin/tool/phpunit/cli/util.php --install"', 132);
+}
+
+// refresh data in all tables, clear caches, etc.
+phpunit_util::reset_all_data();
+
+// store fresh globals
+phpunit_util::init_globals();
+
+
+//=========================================================
+
+/**
+ * Print error and stop execution
+ * @param $text
+ * @param int $errorcode
+ * @return void - stops code execution with error code
+ */
+function phpunit_bootstrap_error($text, $errorcode = 1) {
+    fwrite(STDERR, $text."\n");
+    exit($errorcode);
+}
+
+/**
+ * Mark empty dataroot to be used for testing.
+ * @param $dataroot
+ * @return void
+ */
+function phpunit_bootstrap_initdataroot($dataroot) {
+    global $CFG;
+
+    file_put_contents("$dataroot/phpunittestdir.txt", 'Contents of this directory are used during tests only, do not delete this file!');
+    chmod("$dataroot/phpunittestdir.txt", $CFG->filepermissions);
+    if (!file_exists("$CFG->phpunit_dataroot/phpunit")) {
+        mkdir("$CFG->phpunit_dataroot/phpunit", $CFG->directorypermissions);
+    }
+}
diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php
new file mode 100644 (file)
index 0000000..41f7974
--- /dev/null
@@ -0,0 +1,602 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Various PHPUnit classes and functions
+ *
+ * @package    core_core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once 'PHPUnit/Autoload.php'; // necessary when loaded from cli/util.php script
+
+
+/**
+ * Collection of utility methods.
+ *
+ * @package    core_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class phpunit_util {
+    /**
+     * @var array original content of all database tables
+     */
+    protected static $tabledata = null;
+
+    protected static $globals = array();
+
+    /**
+     * Returns contents of all tables right after installation.
+     * @static
+     * @return array $table=>$records
+     */
+    protected static function get_tabledata() {
+        global $CFG;
+
+        if (!isset(self::$tabledata)) {
+            $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser");
+            self::$tabledata = unserialize($data);
+        }
+
+        if (!is_array(self::$tabledata)) {
+            phpunit_bootstrap_error('Can not read dataroot/phpunit/tabledata.ser or invalid format!');
+        }
+
+        return self::$tabledata;
+    }
+
+    /**
+     * Initialise CFG using data from fresh new install.
+     * @static
+     */
+    public static function initialise_cfg() {
+        global $CFG, $DB;
+
+        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
+            // most probably PHPUnit CLI installer
+            return;
+        }
+
+        if (!$DB->get_manager()->table_exists('config') or !$DB->count_records('config')) {
+            @unlink("$CFG->dataroot/phpunit/tabledata.ser");
+            @unlink("$CFG->dataroot/phpunit/versionshash.txt");
+            self::$tabledata = null;
+            return;
+        }
+
+        $data = self::get_tabledata();
+
+        foreach($data['config'] as $record) {
+            $name = $record->name;
+            $value = $record->value;
+            if (property_exists($CFG, $name)) {
+                // config.php settings always take precedence
+                continue;
+            }
+            $CFG->{$name} = $value;
+        }
+    }
+
+    /**
+     * 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
+     */
+    public static function reset_all_data() {
+        global $DB, $CFG;
+
+        $data = self::get_tabledata();
+
+        $trans = $DB->start_delegated_transaction(); // faster and safer
+        foreach ($data as $table=>$records) {
+            $DB->delete_records($table, array());
+            $resetseq = null;
+            foreach ($records as $record) {
+                if (is_null($resetseq)) {
+                    $resetseq = property_exists($record, 'id');
+                }
+                $DB->import_record($table, $record, false, true);
+            }
+            if ($resetseq === true) {
+                $DB->get_manager()->reset_sequence($table, true);
+            }
+        }
+        $trans->allow_commit();
+
+        purge_all_caches();
+
+        $user = new stdClass();
+        $user->id = 0;
+        $user->mnet = 0;
+        $user->mnethostid = $CFG->mnet_localhost_id;
+        session_set_user($user);
+        accesslib_clear_all_caches_for_unit_testing();
+    }
+
+    /**
+     * Called during bootstrap only!
+     * @static
+     * @return void
+     */
+    public static function init_globals() {
+        global $CFG;
+
+        self::$globals['CFG'] = clone($CFG);
+    }
+
+    /**
+     * Returns original state of global variable.
+     * @static
+     * @param string $name
+     * @return mixed
+     */
+    public static function get_global_backup($name) {
+        if (isset(self::$globals[$name])) {
+            if (is_object(self::$globals[$name])) {
+                $return = clone(self::$globals[$name]);
+                return $return;
+            } else {
+                return self::$globals[$name];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Does this site (db and dataroot) appear to be used for production?
+     * We try very hard to prevent accidental damage done to production servers!!
+     *
+     * @static
+     * @return bool
+     */
+    public static function is_test_site() {
+        global $DB, $CFG;
+
+        if (!file_exists("$CFG->dataroot/phpunittestdir.txt")) {
+            // this is already tested in bootstrap script,
+            // but anway presence of this file means the dataroot is for testing
+            return false;
+        }
+
+        $tables = $DB->get_tables(false);
+        if ($tables) {
+            if (!$DB->get_manager()->table_exists('config')) {
+                return false;
+            }
+            if (!get_config('core', 'phpunittest')) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Is this site initialised to run unit tests?
+     *
+     * @static
+     * @return bool
+     */
+    public static function is_testing_ready() {
+        global $DB, $CFG;
+
+        if (!self::is_test_site()) {
+            return false;
+        }
+
+        $tables = $DB->get_tables(true);
+
+        if (!$tables) {
+            return false;
+        }
+
+        if (!get_config('core', 'phpunittest')) {
+             return false;
+        }
+
+        if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) {
+            return false;
+        }
+
+        if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) {
+            return false;
+        }
+
+        $hash = phpunit_util::get_version_hash();
+        $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt");
+
+        if ($hash !== $oldhash) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Drop all test site data.
+     *
+     * Note: To be used from CLI scripts only.
+     *
+     * @static
+     * @return void, may terminate execution with exit code
+     */
+    public static function drop_site() {
+        global $DB, $CFG;
+
+        if (!self::is_test_site()) {
+            cli_error('Can not drop non-test sites!!', 131);
+        }
+
+        // drop dataroot
+        remove_dir($CFG->dataroot, true);
+        phpunit_bootstrap_initdataroot($CFG->dataroot);
+
+        // drop all tables
+        $trans = $DB->start_delegated_transaction();
+        $tables = $DB->get_tables(false);
+        foreach ($tables as $tablename) {
+            $DB->delete_records($tablename, array());
+        }
+        $trans->allow_commit();
+
+        // now drop them
+        foreach ($tables as $tablename) {
+            $table = new xmldb_table($tablename);
+            $DB->get_manager()->drop_table($table);
+        }
+    }
+
+    /**
+     * Perform a fresh test site installation
+     *
+     * Note: To be used from CLI scripts only.
+     *
+     * @static
+     * @return void, may terminate execution with exit code
+     */
+    public static function install_site() {
+        global $DB, $CFG;
+
+        if (!self::is_test_site()) {
+            cli_error('Can not install non-test sites!!', 131);
+        }
+
+        if ($DB->get_tables()) {
+            cli_error('Database tables already installed, drop the site first.', 133);
+        }
+
+        $options = array();
+        $options['adminpass'] = 'admin'; // removed later
+        $options['shortname'] = 'phpunit';
+        $options['fullname'] = 'PHPUnit test site';
+
+        install_cli_database($options, false);
+
+        // just in case remove admin password so that normal login is not possible
+        $DB->set_field('user', 'password', 'not cached', array('username' => 'admin'));
+
+        // add test db flag
+        set_config('phpunittest', 'phpunittest');
+
+        // store data for all tables
+        $data = array();
+        $tables = $DB->get_tables();
+        foreach ($tables as $table) {
+            $data[$table] = $DB->get_records($table, array());
+        }
+        $data = serialize($data);
+        @unlink("$CFG->dataroot/phpunit/tabledata.ser");
+        file_put_contents("$CFG->dataroot/phpunit/tabledata.ser", $data);
+
+        // hash all plugin versions - helps with very fast detection of db structure changes
+        $hash = phpunit_util::get_version_hash();
+        @unlink("$CFG->dataroot/phpunit/versionshash.txt");
+        file_put_contents("$CFG->dataroot/phpunit/versionshash.txt", $hash);
+    }
+
+    /**
+     * Culculate unique version hash for all available plugins and core.
+     * @static
+     * @return string sha1 hash
+     */
+    public static function get_version_hash() {
+        global $CFG;
+
+        $versions = array();
+
+        // main version first
+        $version = null;
+        include($CFG->dirroot.'/version.php');
+        $versions['core'] = $version;
+
+        // modules
+        $mods = get_plugin_list('mod');
+        ksort($mods);
+        foreach ($mods as $mod => $fullmod) {
+            $module = new stdClass();
+            $module->version = null;
+            include($fullmod.'/version.php');
+            $versions[$mod] = $module->version;
+        }
+
+        // now the rest of plugins
+        $plugintypes = get_plugin_types();
+        unset($plugintypes['mod']);
+        ksort($plugintypes);
+        foreach ($plugintypes as $type=>$unused) {
+            $plugs = get_plugin_list($type);
+            ksort($plugs);
+            foreach ($plugs as $plug=>$fullplug) {
+                $plugin = new stdClass();
+                $plugin->version = null;
+                @include($fullplug.'/version.php');
+                $versions[$plug] = $plugin->version;
+            }
+        }
+
+        $hash = sha1(serialize($versions));
+
+        return $hash;
+    }
+
+    /**
+     * Builds /phpunit.xml file using defaults from /phpunit.xml.dist
+     * @static
+     * @return void
+     */
+    public static function build_config_file() {
+        global $CFG;
+
+        $template = '
+    <testsuites>
+        <testsuite name="@component@">
+            <directory suffix="_test.php">@dir@</directory>
+        </testsuite>
+    </testsuites>';
+        $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
+
+        $suites = '';
+
+        $plugintypes = get_plugin_types();
+        ksort($plugintypes);
+        foreach ($plugintypes as $type=>$unused) {
+            $plugs = get_plugin_list($type);
+            ksort($plugs);
+            foreach ($plugs as $plug=>$fullplug) {
+                if (!file_exists("$fullplug/tests/")) {
+                    continue;
+                }
+                $dir = preg_replace("|$CFG->dirroot/|", '', $fullplug, 1);
+                $dir .= '/tests';
+                $component = $type.'_'.$plug;
+
+                $suite = str_replace('@component@', $component, $template);
+                $suite = str_replace('@dir@', $dir, $suite);
+
+                $suites .= $suite;
+            }
+        }
+
+        $data = preg_replace('|<!--@plugin_suits_start@-->.*<!--@plugin_suits_end@-->|s', $suites, $data, 1);
+
+        @unlink("$CFG->dirroot/phpunit.xml");
+        file_put_contents("$CFG->dirroot/phpunit.xml", $data);
+    }
+}
+
+
+/**
+ * Simplified emulation test case for legacy SimpleTest.
+ *
+ * Note: this is supposed to work for very simple tests only.
+ *
+ * @deprecated
+ * @package    core_phpunit
+ * @author     Petr Skoda
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class UnitTestCase extends PHPUnit_Framework_TestCase {
+
+    /**
+     * @deprecated
+     * @param bool $expected
+     * @param string $message
+     * @return void
+     */
+    public function expectException($expected, $message = '') {
+        // use phpdocs: @expectedException ExceptionClassName
+        if (!$expected) {
+            return;
+        }
+        $this->setExpectedException('moodle_exception', $message);
+    }
+
+    /**
+     * @deprecated
+     * @param bool $expected
+     * @param string $message
+     * @return void
+     */
+    public static function expectError($expected = false, $message = '') {
+        // not available in PHPUnit
+        if (!$expected) {
+            return;
+        }
+        self::skipIf(true);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $actual
+     * @param string $messages
+     * @return void
+     */
+    public static function assertTrue($actual, $messages = '') {
+        parent::assertTrue((bool)$actual, $messages);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $actual
+     * @param string $messages
+     * @return void
+     */
+    public static function assertFalse($actual, $messages = '') {
+        parent::assertFalse((bool)$actual, $messages);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertEqual($expected, $actual, $message = '') {
+        parent::assertEquals($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertNotEqual($expected, $actual, $message = '') {
+        parent::assertNotEquals($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertIdentical($expected, $actual, $message = '') {
+        parent::assertSame($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $expected
+     * @param mixed $actual
+     * @param string $message
+     * @return void
+     */
+    public static function assertNotIdentical($expected, $actual, $message = '') {
+        parent::assertNotSame($expected, $actual, $message);
+    }
+
+    /**
+     * @deprecated
+     * @static
+     * @param mixed $actual
+     * @param mixed $expected
+     * @param string $message
+     * @return void
+     */
+    public static function assertIsA($actual, $expected, $message = '') {
+        parent::assertInstanceOf($expected, $actual, $message);
+    }
+}
+
+
+/**
+ * The simplest PHPUnit test case customised for Moodle,
+ * do not modify database or any globals.
+ *
+ * @package    core_phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class basic_testcase extends PHPUnit_Framework_TestCase {
+
+    /**
+     * Constructs a test case with the given name.
+     *
+     * @param  string $name
+     * @param  array  $data
+     * @param  string $dataName
+     */
+    public function __construct($name = NULL, array $data = array(), $dataName = '') {
+        parent::__construct($name, $data, $dataName);
+
+        $this->setBackupGlobals(false);
+        $this->setBackupStaticAttributes(false);
+        $this->setRunTestInSeparateProcess(false);
+        $this->setInIsolation(false);
+    }
+
+    /**
+     * Runs the bare test sequence.
+     * @return void
+     */
+    public function runBare() {
+        global $CFG, $USER, $DB;
+
+        $dbwrites = $DB->perf_get_writes();
+
+        parent::runBare();
+
+        $oldcfg = phpunit_util::get_global_backup('CFG');
+        foreach($CFG as $k=>$v) {
+            if (!property_exists($oldcfg, $k)) {
+                unset($CFG->$k);
+                error_log('warning: unexpected new $CFG->'.$k.' value in testcase: '.get_class($this).'->'.$this->getName(true));
+            } else if ($oldcfg->$k !== $CFG->$k) {
+                $CFG->$k = $oldcfg->$k;
+                error_log('warning: unexpected change of $CFG->'.$k.' value in testcase: '.get_class($this).'->'.$this->getName(true));
+            }
+            unset($oldcfg->$k);
+
+        }
+        if ($oldcfg) {
+            foreach($oldcfg as $k=>$v) {
+                $CFG->$k = $v;
+                error_log('warning: unexpected removal of $CFG->'.$k.' in testcase: '.get_class($this).'->'.$this->getName(true));
+            }
+        }
+
+        if ($USER->id != 0) {
+            error_log('warning: unexpected change of $USER in testcase: '.get_class($this).'->'.$this->getName(true));
+            $USER = new stdClass();
+            $USER->id = 0;
+        }
+
+        if ($dbwrites != $DB->perf_get_writes()) {
+            //TODO: find out what was changed exactly
+            error_log('warning: unexpected database modification, resetting DB state in testcase: '.get_class($this).'->'.$this->getName(true));
+            phpunit_util::reset_all_data();
+        }
+
+        //TODO: somehow find out if there are changes in dataroot
+    }
+}
+
diff --git a/lib/phpunit/readme.md b/lib/phpunit/readme.md
new file mode 100644 (file)
index 0000000..5a67f11
--- /dev/null
@@ -0,0 +1,49 @@
+PHPUnit testing support in Moodle
+==================================
+
+
+Installation
+------------
+1. install PHPUnit PEAR extension - see [PHPUnit docs](http://www.phpunit.de/manual/current/en/installation.html) for more details
+2. edit main config.php - add $CFG->phpunit_prefix and $CFG->phpunit_dataroot - see config-dist.php for more details
+3. execute `php admin/tool/phpunit/cli/util.php --install` to initialise test database
+4. it is necessary to reinitialise the test database manually after every upgrade or installation of new plugins
+
+
+Test execution
+--------------
+* optionally generate phpunit.xml by executing `php admin/tool/phpunit/cli/util.php --buildconfig` - it collects test cases from all plugins
+* execute `phpunit` shell command from dirroot directory
+* you can also execute a single test `phpunit core_phpunit_basic_testcase lib/tests/phpunit_test.php`
+* or all tests in one directory `phpunit --configuration phpunit.xml lib/tests/*_test.php`
+* it is possible to create custom configuration files in xml format and use `phpunit -c myconfig.xml`
+
+
+How to add more tests
+---------------------
+1. create `tests` directory in any plugin
+2. add `*_test.php` files with custom class that extends `basic_testcase`
+3. manually add all core unit test locations to `phpunit.xml.dist`
+
+
+How to convert existing tests
+-----------------------------
+1. create new test file in `xxx/tests/yyy_test.php`
+2. copy contents of the old test file
+3. replace `extends UnitTestCase` with `extends basic_testcase`
+4. fix setUp, tearDown, asserts, etc.
+5. some old SimpleTest tests can be executed directly - mocking, database operations, assert(), etc. does not work, you may need to add `global $CFG;` before includes
+
+
+FAQs
+----
+* Why is it necessary to execute the tests from commandline? PHPUnit is designed to be executed from shell, existing Moodle globals and constants would interfere with it.
+* Why `tests` subdirectory? It should not collide with any plugin name because plugin names use singular form.
+* Why is it necessary to include core and plugin suites in configuration files? PHPUnit does not seem to allow dynamic loading of tests from our dir structure.
+
+
+TODO
+----
+* stage 2 - implement advaced_testcase - support for database modifications, object generators, automatic rollback of db, blobals and dataroot
+* stage 3 - mocking and other advanced features, add support for execution of functional DB tests for different engines together (new options in phpunit.xml)
+* other - support for execution of tests and cli/util.php from web UI (to be implemented via shell execution), shell script that prepares everything for the first execution
index 2f0f1e4..d5febeb 100644 (file)
@@ -45,7 +45,7 @@
 global $CFG; // this should be done much earlier in config.php before creating new $CFG instance
 
 if (!isset($CFG)) {
-    if (defined('PHPUNIT_SCRIPT') and PHPUNIT_SCRIPT) {
+    if (defined('PHPUNITTEST') and PHPUNITTEST) {
         echo('There is a missing "global $CFG;" at the beginning of the config.php file.'."\n");
         exit(1);
     } else {
@@ -112,7 +112,10 @@ if (!isset($CFG->cachedir)) {
 // directory of the script when run from the command line. The require_once()
 // would fail, so we'll have to chdir()
 if (!isset($_SERVER['REMOTE_ADDR']) && isset($_SERVER['argv'][0])) {
-    chdir(dirname($_SERVER['argv'][0]));
+    // do it only once - skip the second time when continuing after prevous abort
+    if (!defined('ABORT_AFTER_CONFIG') and !defined('ABORT_AFTER_CONFIG_CANCEL')) {
+        chdir(dirname($_SERVER['argv'][0]));
+    }
 }
 
 // sometimes default PHP settings are borked on shared hosting servers, I wonder why they have to do that??
@@ -130,6 +133,11 @@ if (!defined('NO_OUTPUT_BUFFERING')) {
     define('NO_OUTPUT_BUFFERING', false);
 }
 
+// PHPUnit tests need custom init
+if (!defined('PHPUNITTEST')) {
+    define('PHPUNITTEST', false);
+}
+
 // Servers should define a default timezone in php.ini, but if they don't then make sure something is defined.
 // This is a quick hack.  Ideally we should ask the admin for a value.  See MDL-22625 for more on this.
 if (function_exists('date_default_timezone_set') and function_exists('date_default_timezone_get')) {
@@ -139,15 +147,6 @@ if (function_exists('date_default_timezone_set') and function_exists('date_defau
     unset($olddebug);
 }
 
-// PHPUnit scripts are a special case, for now we treat them as normal CLI scripts,
-// please note you must install PHPUnit library separately via PEAR
-if (!defined('PHPUNIT_SCRIPT')) {
-    define('PHPUNIT_SCRIPT', false);
-}
-if (PHPUNIT_SCRIPT) {
-    define('CLI_SCRIPT', true);
-}
-
 // Detect CLI scripts - CLI scripts are executed from command line, do not have session and we do not want HTML in output
 // In your new CLI scripts just add "define('CLI_SCRIPT', true);" before requiring config.php.
 // Please note that one script can not be accessed from both CLI and web interface.
@@ -471,7 +470,11 @@ if (isset($CFG->debug)) {
 }
 
 // Load up any configuration from the config table
-initialise_cfg();
+if (PHPUNITTEST) {
+    phpunit_util::initialise_cfg();
+} else {
+    initialise_cfg();
+}
 
 // Verify upgrade is not running unless we are in a script that needs to execute in any case
 if (!defined('NO_UPGRADE_CHECK') and isset($CFG->upgraderunning)) {
@@ -820,7 +823,7 @@ if (!empty($CFG->customscripts)) {
     }
 }
 
-if (CLI_SCRIPT and !defined('WEB_CRON_EMULATED_CLI') and !PHPUNIT_SCRIPT) {
+if ((CLI_SCRIPT and !defined('WEB_CRON_EMULATED_CLI')) or PHPUNITTEST) {
     // no ip blocking
 } else if (!empty($CFG->allowbeforeblock)) { // allowed list processed before blocked list?
     // in this case, ip in allowed list will be performed first
index 13d1543..44a0fe2 100644 (file)
@@ -26,6 +26,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir.'/componentlib.class.php');
 
 class componentlib_test extends UnitTestCase {
index b69a8a5..abe3e1b 100644 (file)
@@ -26,6 +26,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir . '/csslib.php');
 
 
index 709d226..7e1f14a 100644 (file)
@@ -27,6 +27,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir . '/externallib.php');
 
 class externallib_test extends UnitTestCase {
index ffea131..0e63fc8 100644 (file)
@@ -27,6 +27,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir . '/filelib.php');
 
 class filelib_test extends UnitTestCase {
index 2d8d788..7b41c30 100644 (file)
@@ -27,6 +27,7 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
 require_once($CFG->libdir . '/formslib.php');
 require_once($CFG->libdir . '/form/radio.php');
 require_once($CFG->libdir . '/form/select.php');
index ed3a1e0..b22c92c 100644 (file)
@@ -27,6 +27,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir . '/outputcomponents.php');
 
 
index fdb5f4f..be54934 100644 (file)
@@ -26,6 +26,9 @@
 if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
+
+global $CFG;
+
 require_once($CFG->libdir . '/navigationlib.php');
 
 class navigation_node_test extends UnitTestCase {
@@ -52,7 +55,7 @@ class navigation_node_test extends UnitTestCase {
 
         $this->activeurl = $PAGE->url;
         navigation_node::override_active_url($this->activeurl);
-        
+
         $this->inactiveurl = new moodle_url('http://www.moodle.com/');
         $this->fakeproperties['action'] = $this->inactiveurl;
 
@@ -165,11 +168,11 @@ class navigation_node_test extends UnitTestCase {
     public function test_find_active_node() {
         $activenode1 = $this->node->find_active_node();
         $activenode2 = $this->node->get('demo1')->find_active_node();
-        
+
         if ($this->assertIsA($activenode1, 'navigation_node')) {
             $this->assertReference($activenode1, $this->node->get('demo3')->get('demo5')->get('activity1'));
         }
-        
+
         $this->assertNotA($activenode2, 'navigation_node');
     }
 
index f8aa788..6ad6ccc 100644 (file)
@@ -24,6 +24,9 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
 require_once($CFG->libdir . '/outputcomponents.php');
 
 /**
@@ -142,7 +145,7 @@ EOF;
         $this->assertTrue($menu instanceof custom_menu);
         $this->assertTrue($menu->has_children());
         $firstlevel = $menu->get_children();
-        $this->assertIsA($firstlevel, 'array');
+        $this->assertTrue(is_array($firstlevel));
         $this->assertEqual(2, count($firstlevel));
 
         $item = array_shift($firstlevel);
index 2e1f205..6ebacdb 100644 (file)
@@ -26,6 +26,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+global $CFG;
+
 require_once($CFG->libdir . '/questionlib.php');
 
 
index add6b09..d154c8d 100644 (file)
@@ -14,6 +14,8 @@ if (!defined('MOODLE_INTERNAL')) {
  * If networking/proxy configuration is wrong these tests will fail..
  */
 
+global $CFG;
+
 require_once($CFG->libdir.'/simplepie/moodle_simplepie.php');
 
 class moodlesimplepie_test extends UnitTestCase {
diff --git a/lib/tests/htmlpurifier_test.php b/lib/tests/htmlpurifier_test.php
new file mode 100644 (file)
index 0000000..a43b7d0
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the HTMLPurifier integration
+ *
+ * @package    core_core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+class core_htmlpurifier_testcase extends basic_testcase {
+
+    /**
+     * Verify _blank target is allowed
+     * @return void
+     */
+    public function test_allow_blank_target() {
+        $text = '<a href="http://moodle.org" target="_blank">Some link</a>';
+        $result = format_text($text, FORMAT_HTML);
+        $this->assertSame($text, $result);
+
+        $result = format_text('<a href="http://moodle.org" target="some">Some link</a>', FORMAT_HTML);
+        $this->assertSame('<a href="http://moodle.org">Some link</a>', $result);
+    }
+
+    /**
+     * Verify our nolink tag accepted
+     * @return void
+     */
+    public function test_nolink() {
+        // we can not use format text because nolink changes result
+        $text = '<nolink><div>no filters</div></nolink>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+
+        $text = '<nolink>xxx<em>xx</em><div>xxx</div></nolink>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+    }
+
+    /**
+     * Verify our tex tag accepted
+     * @return void
+     */
+    public function test_tex() {
+        $text = '<tex>a+b=c</tex>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+    }
+
+    /**
+     * Verify our algebra tag accepted
+     * @return void
+     */
+    public function test_algebra() {
+        $text = '<algebra>a+b=c</algebra>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+    }
+
+    /**
+     * Verify our hacky multilang works
+     * @return void
+     */
+    public function test_multilang() {
+        $text = '<lang lang="en">hmmm</lang><lang lang="anything">hm</lang>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+
+        $text = '<span lang="en" class="multilang">hmmm</span><span lang="anything" class="multilang">hm</span>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+
+        $text = '<span lang="en">hmmm</span>';
+        $result = purify_html($text, array());
+        $this->assertNotSame($text, $result);
+
+        // keep standard lang tags
+
+        $text = '<span lang="de_DU" class="multilang">asas</span>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+
+        $text = '<lang lang="de_DU">xxxxxx</lang>';
+        $result = purify_html($text, array());
+        $this->assertSame($text, $result);
+    }
+
+    /**
+     * Tests the 'allowid' option for format_text.
+     * @return void
+     */
+    public function test_format_text_allowid() {
+        // Start off by not allowing ids (default)
+        $options = array(
+            'nocache' => true
+        );
+        $result = format_text('<div id="example">Frog</div>', FORMAT_HTML, $options);
+        $this->assertSame('<div>Frog</div>', $result);
+
+        // Now allow ids
+        $options['allowid'] = true;
+        $result = format_text('<div id="example">Frog</div>', FORMAT_HTML, $options);
+        $this->assertSame('<div id="example">Frog</div>', $result);
+    }
+
+    /**
+     * Test if linebreaks kept unchanged.
+     * @return void
+     */
+    function test_line_breaking() {
+        $text = "\n\raa\rsss\nsss\r";
+        $this->assertSame($text, purify_html($text));
+    }
+
+    /**
+     * Test fixing of strict problems.
+     * @return void
+     */
+    function test_tidy() {
+        $text = "<p>xx";
+        $this->assertSame('<p>xx</p>', purify_html($text));
+
+        $text = "<P>xx</P>";
+        $this->assertSame('<p>xx</p>', purify_html($text));
+
+        $text = "xx<br>";
+        $this->assertSame('xx<br />', purify_html($text));
+    }
+
+    /**
+     * Test nesting - this used to cause problems in earlier versions
+     * @return void
+     */
+    function test_nested_lists() {
+        $text = "<ul><li>One<ul><li>Two</li></ul></li><li>Three</li></ul>";
+        $this->assertSame($text, purify_html($text));
+    }
+
+    /**
+     * Test that XSS protection works, complete smoke tests are in htmlpurifier itself.
+     * @return void
+     */
+    function test_cleaning_nastiness() {
+        $text = "x<SCRIPT>alert('XSS')</SCRIPT>x";
+        $this->assertSame('xx', purify_html($text));
+
+        $text = '<DIV STYLE="background-image:url(javascript:alert(\'XSS\'))">xx</DIV>';
+        $this->assertSame('<div>xx</div>', purify_html($text));
+
+        $text = '<DIV STYLE="width:expression(alert(\'XSS\'));">xx</DIV>';
+        $this->assertSame('<div>xx</div>', purify_html($text));
+
+        $text = 'x<IFRAME SRC="javascript:alert(\'XSS\');"></IFRAME>x';
+        $this->assertSame('xx', purify_html($text));
+
+        $text = 'x<OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT>x';
+        $this->assertSame('xx', purify_html($text));
+
+        $text = 'x<EMBED SRC="http://ha.ckers.org/xss.swf" AllowScriptAccess="always"></EMBED>x';
+        $this->assertSame('xx', purify_html($text));
+
+        $text = 'x<form></form>x';
+        $this->assertSame('xx', purify_html($text));
+    }
+}
+
diff --git a/lib/tests/phpunit_test.php b/lib/tests/phpunit_test.php
new file mode 100644 (file)
index 0000000..90ec3da
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Test integration of PHPUnit and custom Moodle hacks.
+ *
+ * @package    core_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 core_phpunit_basic_testcase extends basic_testcase {
+
+    public function test_bootstrap() {
+        global $CFG;
+        $this->assertTrue(isset($CFG->httpswwwroot));
+        $this->assertEquals($CFG->httpswwwroot, $CFG->wwwroot);
+        $this->assertEquals($CFG->prefix, $CFG->phpunit_prefix);
+    }
+
+/*
+    public function test_borked_tests() {
+        global $DB;
+
+        $DB->set_field('user', 'confirmed', 1, array('id'=>-1));
+        $CFG->xx = 'yy';
+    }
+*/
+
+}
\ No newline at end of file
diff --git a/lib/tests/textlib_test.php b/lib/tests/textlib_test.php
new file mode 100644 (file)
index 0000000..9a73691
--- /dev/null
@@ -0,0 +1,420 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * textlib unit teststest
+ *
+ * @package    core_core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Unit tests for our utf-8 aware text processing
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright  2010 Petr Skoda (http://skodak.org)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_textlib_testcase extends basic_testcase {
+
+    public function test_parse_charset() {
+        $this->assertSame(textlib::parse_charset('Cp1250'), 'windows-1250');
+        // does typo3 work? some encoding moodle does not use
+        $this->assertSame(textlib::parse_charset('ms-ansi'), 'windows-1252');
+    }
+
+    public function test_convert() {
+        $utf8 = "Žluťoučký koníček";
+        $iso2 = pack("H*", "ae6c75bb6f75e86bfd206b6f6eede8656b");
+        $win  = pack("H*", "8e6c759d6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'iso-8859-2'), $iso2);
+        $this->assertSame(textlib::convert($iso2, 'iso-8859-2', 'utf-8'), $utf8);
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'win-1250'), $win);
+        $this->assertSame(textlib::convert($win, 'win-1250', 'utf-8'), $utf8);
+        $this->assertSame(textlib::convert($win, 'win-1250', 'iso-8859-2'), $iso2);
+        $this->assertSame(textlib::convert($iso2, 'iso-8859-2', 'win-1250'), $win);
+        $this->assertSame(textlib::convert($iso2, 'iso-8859-2', 'iso-8859-2'), $iso2);
+        $this->assertSame(textlib::convert($win, 'win-1250', 'cp1250'), $win);
+
+
+        $utf8 = '言語設定';
+        $str = pack("H*", "b8c0b8ecc0dfc4ea"); //EUC-JP
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'EUC-JP'), $str);
+        $this->assertSame(textlib::convert($str, 'EUC-JP', 'utf-8'), $utf8);
+
+        $str = pack("H*", "1b24423840386c405f446a1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'ISO-2022-JP'), $str);
+        $this->assertSame(textlib::convert($str, 'ISO-2022-JP', 'utf-8'), $utf8);
+
+        $str = pack("H*", "8cbe8cea90dd92e8"); //SHIFT-JIS
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'SHIFT-JIS'), $str);
+        $this->assertSame(textlib::convert($str, 'SHIFT-JIS', 'utf-8'), $utf8);
+
+        $utf8 = '简体中文';
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB2312
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'GB2312'), $str);
+        $this->assertSame(textlib::convert($str, 'GB2312', 'utf-8'), $utf8);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB18030
+        $this->assertSame(textlib::convert($utf8, 'utf-8', 'GB18030'), $str);
+        $this->assertSame(textlib::convert($str, 'GB18030', 'utf-8'), $utf8);
+    }
+
+    public function test_substr() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::substr($str, 0), $str);
+        $this->assertSame(textlib::substr($str, 1), 'luťoučký koníček');
+        $this->assertSame(textlib::substr($str, 1, 3), 'luť');
+        $this->assertSame(textlib::substr($str, 0, 100), $str);
+        $this->assertSame(textlib::substr($str, -3, 2), 'če');
+
+        $iso2 = pack("H*", "ae6c75bb6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::substr($iso2, 1, 3, 'iso-8859-2'), textlib::convert('luť', 'utf-8', 'iso-8859-2'));
+        $this->assertSame(textlib::substr($iso2, 0, 100, 'iso-8859-2'), textlib::convert($str, 'utf-8', 'iso-8859-2'));
+        $this->assertSame(textlib::substr($iso2, -3, 2, 'iso-8859-2'), textlib::convert('če', 'utf-8', 'iso-8859-2'));
+
+        $win  = pack("H*", "8e6c759d6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::substr($win, 1, 3, 'cp1250'), textlib::convert('luť', 'utf-8', 'cp1250'));
+        $this->assertSame(textlib::substr($win, 0, 100, 'cp1250'), textlib::convert($str, 'utf-8', 'cp1250'));
+        $this->assertSame(textlib::substr($win, -3, 2, 'cp1250'), textlib::convert('če', 'utf-8', 'cp1250'));
+
+
+        $str = pack("H*", "b8c0b8ecc0dfc4ea"); //EUC-JP
+        $s = pack("H*", "b8ec"); //EUC-JP
+        $this->assertSame(textlib::substr($str, 1, 1, 'EUC-JP'), $s);
+
+        $str = pack("H*", "1b24423840386c405f446a1b2842"); //ISO-2022-JP
+        $s = pack("H*", "1b2442386c1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::substr($str, 1, 1, 'ISO-2022-JP'), $s);
+
+        $str = pack("H*", "8cbe8cea90dd92e8"); //SHIFT-JIS
+        $s = pack("H*", "8cea"); //SHIFT-JIS
+        $this->assertSame(textlib::substr($str, 1, 1, 'SHIFT-JIS'), $s);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB2312
+        $s = pack("H*", "cce5"); //GB2312
+        $this->assertSame(textlib::substr($str, 1, 1, 'GB2312'), $s);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB18030
+        $s = pack("H*", "cce5"); //GB18030
+        $this->assertSame(textlib::substr($str, 1, 1, 'GB18030'), $s);
+    }
+
+    public function test_strlen() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::strlen($str), 17);
+
+        $iso2 = pack("H*", "ae6c75bb6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strlen($iso2, 'iso-8859-2'), 17);
+
+        $win  = pack("H*", "8e6c759d6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strlen($win, 'cp1250'), 17);
+
+
+        $str = pack("H*", "b8ec"); //EUC-JP
+        $this->assertSame(textlib::strlen($str, 'EUC-JP'), 1);
+        $str = pack("H*", "b8c0b8ecc0dfc4ea"); //EUC-JP
+        $this->assertSame(textlib::strlen($str, 'EUC-JP'), 4);
+
+        $str = pack("H*", "1b2442386c1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::strlen($str, 'ISO-2022-JP'), 1);
+        $str = pack("H*", "1b24423840386c405f446a1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::strlen($str, 'ISO-2022-JP'), 4);
+
+        $str = pack("H*", "8cea"); //SHIFT-JIS
+        $this->assertSame(textlib::strlen($str, 'SHIFT-JIS'), 1);
+        $str = pack("H*", "8cbe8cea90dd92e8"); //SHIFT-JIS
+        $this->assertSame(textlib::strlen($str, 'SHIFT-JIS'), 4);
+
+        $str = pack("H*", "cce5"); //GB2312
+        $this->assertSame(textlib::strlen($str, 'GB2312'), 1);
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB2312
+        $this->assertSame(textlib::strlen($str, 'GB2312'), 4);
+
+        $str = pack("H*", "cce5"); //GB18030
+        $this->assertSame(textlib::strlen($str, 'GB18030'), 1);
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB18030
+        $this->assertSame(textlib::strlen($str, 'GB18030'), 4);
+    }
+
+    public function test_strtolower() {
+        $str = "Žluťoučký koníček";
+        $low = 'žluťoučký koníček';
+        $this->assertSame(textlib::strtolower($str), $low);
+
+        $iso2 = pack("H*", "ae6c75bb6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strtolower($iso2, 'iso-8859-2'), textlib::convert($low, 'utf-8', 'iso-8859-2'));
+
+        $win  = pack("H*", "8e6c759d6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strtolower($win, 'cp1250'), textlib::convert($low, 'utf-8', 'cp1250'));
+
+
+        $str = '言語設定';
+        $this->assertSame(textlib::strtolower($str), $str);
+
+        $str = '简体中文';
+        $this->assertSame(textlib::strtolower($str), $str);
+
+        $str = pack("H*", "1b24423840386c405f446a1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::strtolower($str, 'ISO-2022-JP'), $str);
+
+        $str = pack("H*", "8cbe8cea90dd92e8"); //SHIFT-JIS
+        $this->assertSame(textlib::strtolower($str, 'SHIFT-JIS'), $str);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB2312
+        $this->assertSame(textlib::strtolower($str, 'GB2312'), $str);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB18030
+        $this->assertSame(textlib::strtolower($str, 'GB18030'), $str);
+    }
+
+    public function test_strtoupper() {
+        $str = "Žluťoučký koníček";
+        $up  = 'ŽLUŤOUČKÝ KONÍČEK';
+        $this->assertSame(textlib::strtoupper($str), $up);
+
+        $iso2 = pack("H*", "ae6c75bb6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strtoupper($iso2, 'iso-8859-2'), textlib::convert($up, 'utf-8', 'iso-8859-2'));
+
+        $win  = pack("H*", "8e6c759d6f75e86bfd206b6f6eede8656b");
+        $this->assertSame(textlib::strtoupper($win, 'cp1250'), textlib::convert($up, 'utf-8', 'cp1250'));
+
+
+        $str = '言語設定';
+        $this->assertSame(textlib::strtoupper($str), $str);
+
+        $str = '简体中文';
+        $this->assertSame(textlib::strtoupper($str), $str);
+
+        $str = pack("H*", "1b24423840386c405f446a1b2842"); //ISO-2022-JP
+        $this->assertSame(textlib::strtoupper($str, 'ISO-2022-JP'), $str);
+
+        $str = pack("H*", "8cbe8cea90dd92e8"); //SHIFT-JIS
+        $this->assertSame(textlib::strtoupper($str, 'SHIFT-JIS'), $str);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB2312
+        $this->assertSame(textlib::strtoupper($str, 'GB2312'), $str);
+
+        $str = pack("H*", "bcf2cce5d6d0cec4"); //GB18030
+        $this->assertSame(textlib::strtoupper($str, 'GB18030'), $str);
+    }
+
+    public function test_strpos() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::strpos($str, 'koníč'), 10);
+    }
+
+    public function test_strrpos() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::strrpos($str, 'o'), 11);
+    }
+
+    public function test_specialtoascii() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::specialtoascii($str), 'Zlutoucky konicek');
+    }
+
+    public function test_encode_mimeheader() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::encode_mimeheader($str), '=?utf-8?B?xb1sdcWlb3XEjWvDvSBrb27DrcSNZWs=?=');
+    }
+
+    public function test_entities_to_utf8() {
+        $str = "&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&#237;&#269;ek";
+        $this->assertSame(textlib::entities_to_utf8($str), "Žluťoučký koníček");
+    }
+
+    public function test_utf8_to_entities() {
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::utf8_to_entities($str), "&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&#xed;&#x10d;ek");
+        $this->assertSame(textlib::utf8_to_entities($str, true), "&#381;lu&#357;ou&#269;k&#253; kon&#237;&#269;ek");
+
+    }
+
+    public function test_trim_utf8_bom() {
+        $bom = "\xef\xbb\xbf";
+        $str = "Žluťoučký koníček";
+        $this->assertSame(textlib::trim_utf8_bom($bom.$str.$bom), $str.$bom);
+    }
+
+    public function test_get_encodings() {
+        $encodings = textlib::get_encodings();
+        $this->assertTrue(is_array($encodings));
+        $this->assertTrue(count($encodings) > 1);
+        $this->assertTrue(isset($encodings['UTF-8']));
+    }
+
+    public function test_code2utf8() {
+        $this->assertSame(textlib::code2utf8(381), 'Ž');
+    }
+
+    public function test_strtotitle() {
+        $str = "žluťoučký koníček";
+        $this->assertSame(textlib::strtotitle($str), "Žluťoučký Koníček");
+    }
+
+    public function test_deprecated_textlib_get_instance() {
+        ob_start();
+        $textlib = textlib_get_instance();
+        $output = ob_get_contents();
+        $this->assertNotEmpty($output);
+        ob_end_clean();
+        $this->assertSame($textlib->substr('abc', 1, 1), 'b');
+        $this->assertSame($textlib->strlen('abc'), 3);
+        $this->assertSame($textlib->strtoupper('Abc'), 'ABC');
+        $this->assertSame($textlib->strtolower('Abc'), 'abc');
+        $this->assertSame($textlib->strpos('abc', 'a'), 0);
+        $this->assertSame($textlib->strpos('abc', 'd'), false);
+        $this->assertSame($textlib->strrpos('abcabc', 'a'), 3);
+        $this->assertSame($textlib->specialtoascii('ábc'), 'abc');
+        $this->assertSame($textlib->strtotitle('abc ABC'), 'Abc Abc');
+    }
+}
+
+
+/**
+ * Unit tests for our utf-8 aware collator.
+ *
+ * Used for sorting.
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright  2011 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class collatorlib_testcase extends basic_testcase {
+
+    protected $initiallang = null;
+    protected $error = null;
+
+    public function setUp() {
+        global $SESSION;
+        if (isset($SESSION->lang)) {
+            $this->initiallang = $SESSION->lang;
+        }
+        $SESSION->lang = 'en'; // make sure we test en language to get consistent results, hopefully all systems have this locale
+        if (extension_loaded('intl')) {
+            $this->error = 'Collation aware sorting not supported';
+        } else {
+            $this->error = 'Collation aware sorting not supported, PHP extension "intl" is not available.';
+        }
+        parent::setUp();
+    }
+    public function tearDown() {
+        global $SESSION;
+        parent::tearDown();
+        if ($this->initiallang !== null) {
+            $SESSION->lang = $this->initiallang;
+            $this->initiallang = null;
+        } else {
+            unset($SESSION->lang);
+        }
+    }
+    function test_asort() {
+        $arr = array('b' => 'ab', 1 => 'aa', 0 => 'cc');
+        collatorlib::asort($arr);
+        $this->assertSame(array_keys($arr), array(1, 'b', 0));
+        $this->assertSame(array_values($arr), array('aa', 'ab', 'cc'));
+
+        $arr = array('a' => 'áb', 'b' => 'ab', 1 => 'aa', 0=>'cc');
+        collatorlib::asort($arr);
+        $this->assertSame(array_keys($arr), array(1, 'b', 'a', 0), $this->error);
+        $this->assertSame(array_values($arr), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    function test_asort_objects_by_method() {
+        $objects = array(
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_method($objects, 'get_protected_name');
+        $this->assertSame(array_keys($objects), array(1, 'b', 0));
+        $this->assertSame($this->get_ordered_names($objects, 'get_protected_name'), array('aa', 'ab', 'cc'));
+
+        $objects = array(
+            'a' => new string_test_class('áb'),
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_method($objects, 'get_private_name');
+        $this->assertSame(array_keys($objects), array(1, 'b', 'a', 0), $this->error);
+        $this->assertSame($this->get_ordered_names($objects, 'get_private_name'), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    function test_asort_objects_by_property() {
+        $objects = array(
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_property($objects, 'publicname');
+        $this->assertSame(array_keys($objects), array(1, 'b', 0));
+        $this->assertSame($this->get_ordered_names($objects, 'publicname'), array('aa', 'ab', 'cc'));
+
+        $objects = array(
+            'a' => new string_test_class('áb'),
+            'b' => new string_test_class('ab'),
+            1 => new string_test_class('aa'),
+            0 => new string_test_class('cc')
+        );
+        collatorlib::asort_objects_by_property($objects, 'publicname');
+        $this->assertSame(array_keys($objects), array(1, 'b', 'a', 0), $this->error);
+        $this->assertSame($this->get_ordered_names($objects, 'publicname'), array('aa', 'ab', 'áb', 'cc'), $this->error);
+    }
+    protected function get_ordered_names($objects, $methodproperty = 'get_protected_name') {
+        $return = array();
+        foreach ($objects as $object) {
+            if ($methodproperty == 'publicname') {
+                $return[] = $object->publicname;
+            } else {
+                $return[] = $object->$methodproperty();
+            }
+        }
+        return $return;
+    }
+}
+
+
+/**
+ * Simple class used to work with the unit test.
+ *
+ * @package    core
+ * @subpackage lib
+ * @copyright  2011 Sam Hemelryk
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class string_test_class extends stdClass {
+    public $publicname;
+    protected $protectedname;
+    private $privatename;
+    public function __construct($name) {
+        $this->publicname = $name;
+        $this->protectedname = $name;
+        $this->privatename = $name;
+    }
+    public function get_protected_name() {
+        return $this->protectedname;
+    }
+    public function get_private_name() {
+        return $this->publicname;
+    }
+}
index 7de357d..22141d2 100644 (file)
@@ -2742,7 +2742,10 @@ function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) {
             $backtrace = debug_backtrace();
         }
         $from = format_backtrace($backtrace, CLI_SCRIPT);
-        if (!empty($UNITTEST->running)) {
+        if (PHPUNITTEST) {
+            echo 'Debugging: ' . $message . "\n" . $from;
+
+        } else if (!empty($UNITTEST->running)) {
             // When the unit tests are running, any call to trigger_error
             // is intercepted by the test framework and reported as an exception.
             // Therefore, we cannot use trigger_error during unit tests.
index aaeeb42..62acc3f 100644 (file)
@@ -50,6 +50,8 @@
 
 defined('MOODLE_INTERNAL') || die;
 
+global $CFG;
+
 require_once($CFG->dirroot . '/mod/lti/locallib.php');
 require_once($CFG->dirroot . '/mod/lti/servicelib.php');
 
index 2d2a808..cd8430c 100644 (file)
@@ -20,6 +20,8 @@ if (!defined('MOODLE_INTERNAL')) {
 
 // Unit tests for scorm_formatduration function from locallib.php
 
+global $CFG;
+
 // Make sure the code being tested is accessible.
 require_once($CFG->dirroot . '/mod/scorm/locallib.php'); // Include the code to test
 
index 70e7efc..070f0e5 100644 (file)
@@ -40,9 +40,9 @@ require_once("$CFG->dirroot/mod/url/lib.php");
 function url_appears_valid_url($url) {
     if (preg_match('/^(\/|https?:|ftp:)/i', $url)) {
         // note: this is not exact validation, we look for severely malformed URLs only
-        return preg_match('/^[a-z]+:\/\/([^:@\s]+:[^@\s]+@)?[a-z0-9_\.\-]+(:[0-9]+)?(\/[^#]*)?(#.*)?$/i', $url);
+        return (bool)preg_match('/^[a-z]+:\/\/([^:@\s]+:[^@\s]+@)?[a-z0-9_\.\-]+(:[0-9]+)?(\/[^#]*)?(#.*)?$/i', $url);
     } else {
-        return preg_match('/^[a-z]+:\/\/...*$/i', $url);
+        return (bool)preg_match('/^[a-z]+:\/\/...*$/i', $url);
     }
 }
 
index 2dbf1ba..18a026e 100644 (file)
@@ -26,6 +26,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+global $CFG;
+
 require_once($CFG->dirroot . '/mod/url/locallib.php');
 
 
diff --git a/mod/url/tests/lib_test.php b/mod/url/tests/lib_test.php
new file mode 100644 (file)
index 0000000..752bc67
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+// This file is part of Moodle - http://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 <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for some mod URL lib stuff.
+ *
+ * @package    mod_url
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * mod_url tests
+ *
+ * @package    mod_url
+ * @category   phpunit
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_url_lib_testcase extends basic_testcase {
+
+    public static function setUpBeforeClass() {
+        global $CFG;
+        require_once($CFG->dirroot . '/mod/url/locallib.php');
+    }
+
+    public function test_url_appears_valid_url() {
+        $this->assertTrue(url_appears_valid_url('http://example'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com'));
+        $this->assertTrue(url_appears_valid_url('http://www.exa-mple2.com'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com/~nobody/index.html'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com#hmm'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com/#hmm'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com/žlutý koníček/lala.txt'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com/žlutý koníček/lala.txt#hmmmm'));
+        $this->assertTrue(url_appears_valid_url('http://www.example.com/index.php?xx=yy&zz=aa'));
+        $this->assertTrue(url_appears_valid_url('https://user:password@www.example.com/žlutý koníček/lala.txt'));
+        $this->assertTrue(url_appears_valid_url('ftp://user:password@www.example.com/žlutý koníček/lala.txt'));
+
+        $this->assertFalse(url_appears_valid_url('http:example.com'));
+        $this->assertFalse(url_appears_valid_url('http:/example.com'));
+        $this->assertFalse(url_appears_valid_url('http://'));
+        $this->assertFalse(url_appears_valid_url('http://www.exa mple.com'));
+        $this->assertFalse(url_appears_valid_url('http://www.examplé.com'));
+        $this->assertFalse(url_appears_valid_url('http://@www.example.com'));
+        $this->assertFalse(url_appears_valid_url('http://user:@www.example.com'));
+
+        $this->assertTrue(url_appears_valid_url('lalala://@:@/'));
+    }
+}
\ No newline at end of file
index 9d197ab..024642e 100644 (file)
@@ -35,6 +35,8 @@ if (!defined('MOODLE_INTERNAL')) {
     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
 }
 
+global $CFG;
+
 require_once($CFG->dirroot . '/mod/wiki/parser/parser.php');
 
 class wikiparser_test extends UnitTestCase {
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644 (file)
index 0000000..09456e1
--- /dev/null
@@ -0,0 +1,30 @@
+<phpunit
+        bootstrap="lib/phpunit/bootstrap.php"
+        colors="true"
+        convertErrorsToExceptions="true"
+        convertNoticesToExceptions="true"
+        convertWarningsToExceptions="true"
+        processIsolation="false"
+        backupGlobals="false"
+        backupStaticAttributes="false"
+        stopOnError="false"
+        stopOnFailure="false"
+        stopOnIncomplete="false"
+        stopOnSkipped="false"
+        strict="false"
+        verbose="false"
+        >
+
+<!--All core suites need to be added manually here-->
+
+    <testsuites>
+        <testsuite name="core_lib">
+            <directory suffix="_test.php">lib/tests</directory>
+        </testsuite>
+    </testsuites>
+
+<!--Plugin suites: use admin/tool/phpunit/cli/util.php to build phpunit.xml from phpunit.xml.dist with up-to-date list of plugins in current install-->
+<!--@plugin_suits_start@-->
+<!--@plugin_suits_end@-->
+
+</phpunit>