From a3d5830a0a40611934927998f9b8596f033e55fe Mon Sep 17 00:00:00 2001 From: Petr Skoda Date: Sat, 31 Mar 2012 23:51:02 +0200 Subject: [PATCH 01/26] MDL-32149 PHPUnit test support - part 2 Includes: * constants refactoring * reworked db table init * support for $CFG->debug = -1 * functional DB tests * fixed $DB->get_indexes() to not throw exceptions when table does not exist * fix handling of user passwords in test db * add debug info to exception messages * removed unnecessary PHP debug errors from mathslib * fixed @error suppression in get_string * fixed PHPUnit error handler setup * added timezone info to default install --- admin/tool/phpunit/cli/util.php | 2 +- config-dist.php | 6 + lib/accesslib.php | 2 +- lib/ajax/tests/ajaxlib_test.php | 130 + lib/ddl/tests/ddl_test.php | 1831 +++++++++ lib/ddl/tests/fixtures/invalid.xml | 19 + lib/ddl/tests/fixtures/xmldb_table.xml | 19 + lib/dml/mysqli_native_moodle_database.php | 6 +- lib/dml/tests/dml_test.php | 4555 +++++++++++++++++++++ lib/dml/tests/fixtures/clob.txt | 197 + lib/dml/tests/fixtures/randombinary | Bin 0 -> 10000 bytes lib/form/tests/duration_test.php | 127 + lib/mathslib.php | 2 +- lib/moodlelib.php | 21 +- lib/phpunit/bootstrap.php | 59 +- lib/phpunit/generatorlib.php | 360 ++ lib/phpunit/lib.php | 465 ++- lib/phpunit/readme.md | 11 +- lib/pluginlib.php | 2 +- lib/setup.php | 37 +- lib/setuplib.php | 4 + lib/simpletest/testexternallib.php | 2 +- lib/tests/accesslib_test.php | 990 +++++ lib/tests/blocklib_test.php | 386 ++ lib/tests/code_test.php | 56 + lib/tests/componentlib_test.php | 182 + lib/tests/cssslib_test.php | 708 ++++ lib/tests/eventslib_test.php | 233 ++ lib/tests/externallib_test.php | 77 + lib/tests/filelib_test.php | 88 + lib/tests/filter_test.php | 767 ++++ lib/tests/fixtures/events.php | 42 + lib/tests/formslib_test.php | 220 + lib/tests/html2text_test.php | 146 + lib/tests/htmlwriter_test.php | 92 + lib/tests/mathslib_test.php | 218 + lib/tests/moodlelib_test.php | 1823 +++++++++ lib/tests/outputcomponents_test.php | 232 ++ lib/tests/outputlib_test.php | 162 + lib/tests/questionlib_test.php | 58 + lib/tests/rsslib_test.php | 145 + lib/tests/weblib_test.php | 187 + lib/weblib.php | 4 +- phpunit.xml.dist | 17 +- 44 files changed, 14552 insertions(+), 138 deletions(-) create mode 100644 lib/ajax/tests/ajaxlib_test.php create mode 100644 lib/ddl/tests/ddl_test.php create mode 100644 lib/ddl/tests/fixtures/invalid.xml create mode 100644 lib/ddl/tests/fixtures/xmldb_table.xml create mode 100644 lib/dml/tests/dml_test.php create mode 100644 lib/dml/tests/fixtures/clob.txt create mode 100644 lib/dml/tests/fixtures/randombinary create mode 100644 lib/form/tests/duration_test.php create mode 100644 lib/phpunit/generatorlib.php create mode 100644 lib/tests/accesslib_test.php create mode 100644 lib/tests/blocklib_test.php create mode 100644 lib/tests/code_test.php create mode 100644 lib/tests/componentlib_test.php create mode 100644 lib/tests/cssslib_test.php create mode 100644 lib/tests/eventslib_test.php create mode 100644 lib/tests/externallib_test.php create mode 100644 lib/tests/filelib_test.php create mode 100644 lib/tests/filter_test.php create mode 100644 lib/tests/fixtures/events.php create mode 100644 lib/tests/formslib_test.php create mode 100644 lib/tests/html2text_test.php create mode 100644 lib/tests/htmlwriter_test.php create mode 100644 lib/tests/mathslib_test.php create mode 100644 lib/tests/moodlelib_test.php create mode 100644 lib/tests/outputcomponents_test.php create mode 100644 lib/tests/outputlib_test.php create mode 100644 lib/tests/questionlib_test.php create mode 100644 lib/tests/rsslib_test.php create mode 100644 lib/tests/weblib_test.php diff --git a/admin/tool/phpunit/cli/util.php b/admin/tool/phpunit/cli/util.php index c270e172633fe..18a08dbcb9b8e 100644 --- a/admin/tool/phpunit/cli/util.php +++ b/admin/tool/phpunit/cli/util.php @@ -29,7 +29,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define('PHPUNIT_CLI_UTIL', true); +define('PHPUNIT_UTIL', true); require(__DIR__ . '/../../../../lib/phpunit/bootstrap.php'); require_once($CFG->libdir.'/phpunit/lib.php'); diff --git a/config-dist.php b/config-dist.php index 0aec77a0dd433..36eb3d9cb87bb 100644 --- a/config-dist.php +++ b/config-dist.php @@ -490,6 +490,12 @@ // $CFG->phpunit_prefix = 'phpu_'; // $CFG->phpunit_dataroot = '/home/example/phpu_moodledata'; // $CFG->phpunit_directorypermissions = 02777; // optional +// $CFG->phpunit_extra_drivers = array( +// 1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'), +// 2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'), +// 3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'), +// 4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'), +// ); // for database driver testing only, DB is selected via PHPUNIT_TEST_DRIVER=n //========================================================================= // ALL DONE! To continue installation, visit your main page with a browser diff --git a/lib/accesslib.php b/lib/accesslib.php index 8e39595aa471d..a4161012d532b 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -217,7 +217,7 @@ */ function accesslib_clear_all_caches_for_unit_testing() { global $UNITTEST, $USER; - if (empty($UNITTEST->running) and !PHPUNITTEST) { + if (empty($UNITTEST->running) and !PHPUNIT_TEST) { throw new coding_exception('You must not call clear_all_caches outside of unit tests.'); } diff --git a/lib/ajax/tests/ajaxlib_test.php b/lib/ajax/tests/ajaxlib_test.php new file mode 100644 index 0000000000000..45aa7084fd0df --- /dev/null +++ b/lib/ajax/tests/ajaxlib_test.php @@ -0,0 +1,130 @@ +. + + +/** + * Unit tests for (some of) ../ajaxlib.php. + * + * @package core + * @category phpunit + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/ajax/ajaxlib.php'); + + +/** + * Unit tests for ../ajaxlib.php functions. + * + * @copyright 2008 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class ajax_test extends UnitTestCase { + + var $user_agents = array( + 'MSIE' => array( + '5.5' => array('Windows 2000' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)'), + '6.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'), + '7.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.0.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)') + ), + 'Firefox' => array( + '1.0.6' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6'), + '1.5' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.8) Gecko/20051107 Firefox/1.5'), + '1.5.0.1' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1'), + '2.0' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1', + 'Ubuntu Linux AMD64' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1) Gecko/20060601 Firefox/2.0 (Ubuntu-edgy)') + ), + 'Safari' => array( + '312' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312'), + '2.0' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412 (KHTML, like Gecko) Safari/412') + ), + 'Opera' => array( + '8.51' => array('Windows XP' => 'Opera/8.51 (Windows NT 5.1; U; en)'), + '9.0' => array('Windows XP' => 'Opera/9.0 (Windows NT 5.1; U; en)', + 'Debian Linux' => 'Opera/9.01 (X11; Linux i686; U; en)') + ) + ); + + /** + * Uses the array of user agents to test ajax_lib::ajaxenabled + */ + function test_ajaxenabled() + { + global $CFG, $USER; + $CFG->enableajax = true; + $USER->ajax = true; + + // Should be true + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP']; + $this->assertTrue(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['1.5']['Windows XP']; + $this->assertTrue(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['2.0']['Mac OS X']; + $this->assertTrue(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['9.0']['Windows XP']; + $this->assertTrue(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['6.0']['Windows XP SP2']; + $this->assertTrue(ajaxenabled()); + + // Should be false + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['1.0.6']['Windows XP']; + $this->assertFalse(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['312']['Mac OS X']; + $this->assertFalse(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['8.51']['Windows XP']; + $this->assertFalse(ajaxenabled()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['5.5']['Windows 2000']; + $this->assertFalse(ajaxenabled()); + + // Test array of tested browsers + $tested_browsers = array('MSIE' => 6.0, 'Gecko' => 20061111); + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP']; + $this->assertTrue(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['7.0']['Windows XP SP2']; + $this->assertTrue(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['2.0']['Mac OS X']; + $this->assertFalse(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['9.0']['Windows XP']; + $this->assertFalse(ajaxenabled($tested_browsers)); + + $tested_browsers = array('Safari' => 412, 'Opera' => 9.0); + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP']; + $this->assertFalse(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['7.0']['Windows XP SP2']; + $this->assertFalse(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['2.0']['Mac OS X']; + $this->assertTrue(ajaxenabled($tested_browsers)); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['9.0']['Windows XP']; + $this->assertTrue(ajaxenabled($tested_browsers)); + } +} diff --git a/lib/ddl/tests/ddl_test.php b/lib/ddl/tests/ddl_test.php new file mode 100644 index 0000000000000..15cca4238b02d --- /dev/null +++ b/lib/ddl/tests/ddl_test.php @@ -0,0 +1,1831 @@ +. + +/** + * DDL layer tests + * + * @package core + * @subpackage ddl + * @category phpunit + * @copyright 2008 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +class ddl_testcase extends database_driver_testcase { + private $tables = array(); + private $records= array(); + + public function setUp() { + //global $CFG; + //require_once($CFG->libdir . '/adminlib.php'); + + parent::setUp(); + + $table = new xmldb_table('test_table0'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('type', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'general'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null); + $table->add_field('intro', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, null); + $table->add_field('logo', XMLDB_TYPE_BINARY, 'big', null, null, null); + $table->add_field('assessed', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('assesstimestart', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('assesstimefinish', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('scale', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('maxbytes', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('forcesubscribe', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('trackingtype', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1'); + $table->add_field('rsstype', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('rssarticles', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('grade', XMLDB_TYPE_NUMBER, '20,0', XMLDB_UNSIGNED, null, null, null); + $table->add_field('percent', XMLDB_TYPE_NUMBER, '5,2', null, null, null, 66.6); + $table->add_field('warnafter', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('blockafter', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('blockperiod', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('course', XMLDB_KEY_UNIQUE, array('course')); + $table->add_index('type-name', XMLDB_INDEX_UNIQUE, array('type', 'name')); + $table->add_index('rsstype', XMLDB_INDEX_NOTUNIQUE, array('rsstype')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + // Define 2 initial records for this table + $this->records[$table->getName()] = array( + (object)array( + 'course' => '1', + 'type' => 'general', + 'name' => 'record', + 'intro' => 'first record'), + (object)array( + 'course' => '2', + 'type' => 'social', + 'name' => 'record', + 'intro' => 'second record')); + + // Second, smaller table + $table = new xmldb_table ('test_table1'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, null, null, 'Moodle'); + $table->add_field('secondname', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null); + $table->add_field('thirdname', XMLDB_TYPE_CHAR, '30', null, null, null, ''); // nullable column with empty default + $table->add_field('intro', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null); + $table->add_field('avatar', XMLDB_TYPE_BINARY, 'medium', null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_NUMBER, '20,10', null, null, null); + $table->add_field('gradefloat', XMLDB_TYPE_FLOAT, '20,0', XMLDB_UNSIGNED, null, null, null); + $table->add_field('percentfloat', XMLDB_TYPE_FLOAT, '5,2', null, null, null, 99.9); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('course', XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('course')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + // Define 2 initial records for this table + $this->records[$table->getName()] = array( + (object)array( + 'course' => '1', + 'secondname' => 'first record', // > 10 cc, please don't modify. Some tests below depend of this + 'intro' => 'first record'), + (object)array( + 'course' => '2', + 'secondname' => 'second record', // > 10 cc, please don't modify. Some tests below depend of this + 'intro' => 'second record')); + } + + private function create_deftable($tablename) { + $dbman = $this->tdb->get_manager(); + + if (!isset($this->tables[$tablename])) { + return null; + } + + $table = $this->tables[$tablename]; + + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $dbman->create_table($table); + + return $table; + } + + /** + * Fill the given test table with some records, as far as + * DDL behaviour must be tested both with real data and + * with empty tables + */ + private function fill_deftable($tablename) { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + if (!isset($this->records[$tablename])) { + return null; + } + + if ($dbman->table_exists($tablename)) { + foreach ($this->records[$tablename] as $row) { + $DB->insert_record($tablename, $row); + } + } else { + return null; + } + + return count($this->records[$tablename]); + } + + /** + * Test behaviour of table_exists() + */ + public function test_table_exists() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // first make sure it returns false if table does not exist + $table = $this->tables['test_table0']; + + ob_start(); // hide debug warning + try { + $result = $DB->get_records('test_table0'); + } catch (dml_exception $e) { + $result = false; + } + ob_end_clean(); + + $this->assertFalse($result); + + $this->assertFalse($dbman->table_exists('test_table0')); // by name + $this->assertFalse($dbman->table_exists($table)); // by xmldb_table + + // create table and test again + $dbman->create_table($table); + + $this->assertTrue($DB->get_records('test_table0') === array()); + $this->assertTrue($dbman->table_exists('test_table0')); // by name + $this->assertTrue($dbman->table_exists($table)); // by xmldb_table + + // drop table and test again + $dbman->drop_table($table); + + ob_start(); // hide debug warning + try { + $result = $DB->get_records('test_table0'); + } catch (dml_exception $e) { + $result = false; + } + ob_end_clean(); + + $this->assertFalse($result); + + $this->assertFalse($dbman->table_exists('test_table0')); // by name + $this->assertFalse($dbman->table_exists($table)); // by xmldb_table + } + + /** + * Test behaviour of create_table() + */ + public function test_create_table() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // create table + $table = $this->tables['test_table1']; + + $dbman->create_table($table); + $this->assertTrue($dbman->table_exists($table)); + + // basic get_tables() test + $tables = $DB->get_tables(); + $this->assertTrue(array_key_exists('test_table1', $tables)); + + // basic get_columns() tests + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['id']->meta_type, 'R'); + $this->assertEquals($columns['course']->meta_type, 'I'); + $this->assertEquals($columns['name']->meta_type, 'C'); + $this->assertEquals($columns['secondname']->meta_type, 'C'); + $this->assertEquals($columns['thirdname']->meta_type, 'C'); + $this->assertEquals($columns['intro']->meta_type, 'X'); + $this->assertEquals($columns['avatar']->meta_type, 'B'); + $this->assertEquals($columns['grade']->meta_type, 'N'); + $this->assertEquals($columns['percentfloat']->meta_type, 'N'); + $this->assertEquals($columns['userid']->meta_type, 'I'); + // some defaults + $this->assertTrue($columns['course']->has_default); + $this->assertEquals($columns['course']->default_value, 0); + $this->assertTrue($columns['name']->has_default); + $this->assertEquals($columns['name']->default_value, 'Moodle'); + $this->assertTrue($columns['secondname']->has_default); + $this->assertEquals($columns['secondname']->default_value, ''); + $this->assertTrue($columns['thirdname']->has_default); + $this->assertEquals($columns['thirdname']->default_value, ''); + $this->assertTrue($columns['percentfloat']->has_default); + $this->assertEquals($columns['percentfloat']->default_value, 99.9); + $this->assertTrue($columns['userid']->has_default); + $this->assertEquals($columns['userid']->default_value, 0); + + // basic get_indexes() test + $indexes = $DB->get_indexes('test_table1'); + $courseindex = reset($indexes); + $this->assertEquals($courseindex['unique'], 1); + $this->assertEquals($courseindex['columns'][0], 'course'); + + // check sequence returns 1 for first insert + $rec = (object)array( + 'course' => 10, + 'secondname' => 'not important', + 'intro' => 'not important'); + $this->assertSame($DB->insert_record('test_table1', $rec), 1); + + // check defined defaults are working ok + $dbrec = $DB->get_record('test_table1', array('id' => 1)); + $this->assertEquals($dbrec->name, 'Moodle'); + $this->assertEquals($dbrec->thirdname, ''); + + // check exceptions if multiple R columns + $table = new xmldb_table ('test_table2'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('rid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('primaryx', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + // check exceptions missing primary key on R column + $table = new xmldb_table ('test_table2'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + // long table name names - the largest allowed + $table = new xmldb_table('test_table0123456789_____xyz'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + $dbman->create_table($table); + $this->assertTrue($dbman->table_exists($table)); + $dbman->drop_table($table); + + // table name is too long + $table = new xmldb_table('test_table0123456789_____xyz9'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // invalid table name + $table = new xmldb_table('test_tableCD'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + + // weird column names - the largest allowed + $table = new xmldb_table('test_table3'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('abcdef____0123456789_______xyz', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + $dbman->create_table($table); + $this->assertTrue($dbman->table_exists($table)); + $dbman->drop_table($table); + + // Too long field name - max 30 + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('abcdeabcdeabcdeabcdeabcdeabcdez', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid field name + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('abCD', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid integer length + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '21', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid integer default + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'x'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid decimal length + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_NUMBER, '21,10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid decimal decimals + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_NUMBER, '10,11', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid decimal default + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_NUMBER, '10,5', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'x'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid float length + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_FLOAT, '21,10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid float decimals + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_FLOAT, '10,11', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + // Invalid float default + $table = new xmldb_table('test_table4'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('num', XMLDB_TYPE_FLOAT, '10,5', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'x'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->setComment("This is a test'n drop table. You can drop it safely"); + + $this->tables[$table->getName()] = $table; + + try { + $dbman->create_table($table); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertSame(get_class($e), 'coding_exception'); + } + + } + + /** + * Test behaviour of drop_table() + */ + public function test_drop_table() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // initially table doesn't exist + $this->assertFalse($dbman->table_exists('test_table0')); + + // create table with contents + $table = $this->create_deftable('test_table0'); + $this->assertTrue($dbman->table_exists('test_table0')); + + // fill the table with some records before dropping it + $this->fill_deftable('test_table0'); + + // drop by xmldb_table object + $dbman->drop_table($table); + $this->assertFalse($dbman->table_exists('test_table0')); + + // basic get_tables() test + $tables = $DB->get_tables(); + $this->assertFalse(array_key_exists('test_table0', $tables)); + + // columns cache must be empty + $columns = $DB->get_columns('test_table0'); + $this->assertEmpty($columns); + + $indexes = $DB->get_indexes('test_table0'); + $this->assertEmpty($indexes); + } + + /** + * Test behaviour of rename_table() + */ + public function test_rename_table() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + + // fill the table with some records before renaming it + $insertedrows = $this->fill_deftable('test_table1'); + + $this->assertFalse($dbman->table_exists('test_table_cust1')); + $dbman->rename_table($table, 'test_table_cust1'); + $this->assertTrue($dbman->table_exists('test_table_cust1')); + + // check sequence returns $insertedrows + 1 for this insert (after rename) + $rec = (object)array( + 'course' => 20, + 'secondname' => 'not important', + 'intro' => 'not important'); + $this->assertSame($DB->insert_record('test_table_cust1', $rec), $insertedrows + 1); + } + + /** + * Test behaviour of field_exists() + */ + public function test_field_exists() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + + // String params + // Give a nonexistent table as first param (throw exception) + try { + $dbman->field_exists('nonexistenttable', 'id'); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof moodle_exception); + } + + // Give a nonexistent field as second param (return false) + $this->assertFalse($dbman->field_exists('test_table0', 'nonexistentfield')); + + // Correct string params + $this->assertTrue($dbman->field_exists('test_table0', 'id')); + + // Object params + $realfield = $table->getField('id'); + + // Give a nonexistent table as first param (throw exception) + $nonexistenttable = new xmldb_table('nonexistenttable'); + try { + $dbman->field_exists($nonexistenttable, $realfield); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof moodle_exception); + } + + // Give a nonexistent field as second param (return false) + $nonexistentfield = new xmldb_field('nonexistentfield'); + $this->assertFalse($dbman->field_exists($table, $nonexistentfield)); + + // Correct object params + $this->assertTrue($dbman->field_exists($table, $realfield)); + + // Mix string and object params + // Correct ones + $this->assertTrue($dbman->field_exists($table, 'id')); + $this->assertTrue($dbman->field_exists('test_table0', $realfield)); + // Non existing tables (throw exception) + try { + $this->assertFalse($dbman->field_exists($nonexistenttable, 'id')); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof moodle_exception); + } + try { + $this->assertFalse($dbman->field_exists('nonexistenttable', $realfield)); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof moodle_exception); + } + // Non existing fields (return false) + $this->assertFalse($dbman->field_exists($table, 'nonexistentfield')); + $this->assertFalse($dbman->field_exists('test_table0', $nonexistentfield)); + } + + /** + * Test behaviour of add_field() + */ + public function test_add_field() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + + // fill the table with some records before adding fields + $this->fill_deftable('test_table1'); + + /// add one not null field without specifying default value (throws ddl_exception) + $field = new xmldb_field('onefield'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null); + try { + $dbman->add_field($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + /// add one existing field (throws ddl_exception) + $field = new xmldb_field('course'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2); + try { + $dbman->add_field($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + // TODO: add one field with invalid type, must throw exception + // TODO: add one text field with default, must throw exception + // TODO: add one binary field with default, must throw exception + + /// add one integer field and check it + $field = new xmldb_field('oneinteger'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'oneinteger')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['oneinteger']->name ,'oneinteger'); + $this->assertEquals($columns['oneinteger']->not_null , true); + // max_length and scale cannot be checked under all DBs at all for integer fields + $this->assertEquals($columns['oneinteger']->primary_key , false); + $this->assertEquals($columns['oneinteger']->binary , false); + $this->assertEquals($columns['oneinteger']->has_default , true); + $this->assertEquals($columns['oneinteger']->default_value, 2); + $this->assertEquals($columns['oneinteger']->meta_type ,'I'); + $this->assertEquals($DB->get_field('test_table1', 'oneinteger', array(), IGNORE_MULTIPLE), 2); //check default has been applied + + /// add one numeric field and check it + $field = new xmldb_field('onenumber'); + $field->set_attributes(XMLDB_TYPE_NUMBER, '6,3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2.55); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'onenumber')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['onenumber']->name ,'onenumber'); + $this->assertEquals($columns['onenumber']->max_length , 6); + $this->assertEquals($columns['onenumber']->scale , 3); + $this->assertEquals($columns['onenumber']->not_null , true); + $this->assertEquals($columns['onenumber']->primary_key , false); + $this->assertEquals($columns['onenumber']->binary , false); + $this->assertEquals($columns['onenumber']->has_default , true); + $this->assertEquals($columns['onenumber']->default_value, 2.550); + $this->assertEquals($columns['onenumber']->meta_type ,'N'); + $this->assertEquals($DB->get_field('test_table1', 'onenumber', array(), IGNORE_MULTIPLE), 2.550); //check default has been applied + + /// add one float field and check it (not official type - must work as number) + $field = new xmldb_field('onefloat'); + $field->set_attributes(XMLDB_TYPE_FLOAT, '6,3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 3.550); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'onefloat')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['onefloat']->name ,'onefloat'); + $this->assertEquals($columns['onefloat']->not_null , true); + // max_length and scale cannot be checked under all DBs at all for float fields + $this->assertEquals($columns['onefloat']->primary_key , false); + $this->assertEquals($columns['onefloat']->binary , false); + $this->assertEquals($columns['onefloat']->has_default , true); + $this->assertEquals($columns['onefloat']->default_value, 3.550); + $this->assertEquals($columns['onefloat']->meta_type ,'N'); + // Just rounding DB information to 7 decimal digits. Fair enough to test 3.550 and avoids one nasty bug + // in MSSQL core returning wrong floats (http://social.msdn.microsoft.com/Forums/en-US/sqldataaccess/thread/5e08de63-16bb-4f24-b645-0cf8fc669de3) + // In any case, floats aren't officially supported by Moodle, with number/decimal type being the correct ones, so + // this isn't a real problem at all. + $this->assertEquals(round($DB->get_field('test_table1', 'onefloat', array(), IGNORE_MULTIPLE), 7), 3.550); //check default has been applied + + /// add one char field and check it + $field = new xmldb_field('onechar'); + $field->set_attributes(XMLDB_TYPE_CHAR, '25', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'Nice dflt!'); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'onechar')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['onechar']->name ,'onechar'); + $this->assertEquals($columns['onechar']->max_length , 25); + $this->assertEquals($columns['onechar']->scale , null); + $this->assertEquals($columns['onechar']->not_null , true); + $this->assertEquals($columns['onechar']->primary_key , false); + $this->assertEquals($columns['onechar']->binary , false); + $this->assertEquals($columns['onechar']->has_default , true); + $this->assertEquals($columns['onechar']->default_value,'Nice dflt!'); + $this->assertEquals($columns['onechar']->meta_type ,'C'); + $this->assertEquals($DB->get_field('test_table1', 'onechar', array(), IGNORE_MULTIPLE), 'Nice dflt!'); //check default has been applied + + /// add one big text field and check it + $field = new xmldb_field('onetext'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'big'); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'onetext')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['onetext']->name ,'onetext'); + $this->assertEquals($columns['onetext']->max_length , -1); // -1 means unknown or big + $this->assertEquals($columns['onetext']->scale , null); + $this->assertEquals($columns['onetext']->not_null , false); + $this->assertEquals($columns['onetext']->primary_key , false); + $this->assertEquals($columns['onetext']->binary , false); + $this->assertEquals($columns['onetext']->has_default , false); + $this->assertEquals($columns['onetext']->default_value, null); + $this->assertEquals($columns['onetext']->meta_type ,'X'); + + /// add one medium text field and check it + $field = new xmldb_field('mediumtext'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'medium'); + $dbman->add_field($table, $field); + $columns = $DB->get_columns('test_table1'); + $this->assertTrue(($columns['mediumtext']->max_length == -1) or ($columns['mediumtext']->max_length >= 16777215)); // -1 means unknown or big + + /// add one small text field and check it + $field = new xmldb_field('smalltext'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'small'); + $dbman->add_field($table, $field); + $columns = $DB->get_columns('test_table1'); + $this->assertTrue(($columns['smalltext']->max_length == -1) or ($columns['smalltext']->max_length >= 65535)); // -1 means unknown or big + + /// add one binary field and check it + $field = new xmldb_field('onebinary'); + $field->set_attributes(XMLDB_TYPE_BINARY); + $dbman->add_field($table, $field); + $this->assertTrue($dbman->field_exists($table, 'onebinary')); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['onebinary']->name ,'onebinary'); + $this->assertEquals($columns['onebinary']->max_length , -1); + $this->assertEquals($columns['onebinary']->scale , null); + $this->assertEquals($columns['onebinary']->not_null , false); + $this->assertEquals($columns['onebinary']->primary_key , false); + $this->assertEquals($columns['onebinary']->binary , true); + $this->assertEquals($columns['onebinary']->has_default , false); + $this->assertEquals($columns['onebinary']->default_value, null); + $this->assertEquals($columns['onebinary']->meta_type ,'B'); + + // TODO: check datetime type. Although unused should be fully supported. + } + + /** + * Test behaviour of drop_field() + */ + public function test_drop_field() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + + // fill the table with some records before dropping fields + $this->fill_deftable('test_table0'); + + // drop field with simple xmldb_field having indexes, must return exception + $field = new xmldb_field('type'); // Field has indexes and default clause + $this->assertTrue($dbman->field_exists($table, 'type')); + try { + $dbman->drop_field($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_dependency_exception); + } + $this->assertTrue($dbman->field_exists($table, 'type')); // continues existing, drop aborted + + // drop field with complete xmldb_field object and related indexes, must return exception + $field = $table->getField('course'); // Field has indexes and default clause + $this->assertTrue($dbman->field_exists($table, $field)); + try { + $dbman->drop_field($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_dependency_exception); + } + $this->assertTrue($dbman->field_exists($table, $field)); // continues existing, drop aborted + + // drop one non-existing field, must return exception + $field = new xmldb_field('nonexistingfield'); + $this->assertFalse($dbman->field_exists($table, $field)); + try { + $dbman->drop_field($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_field_missing_exception); + } + + // drop field with simple xmldb_field, not having related indexes + $field = new xmldb_field('forcesubscribe'); // Field has default clause + $this->assertTrue($dbman->field_exists($table, 'forcesubscribe')); + $dbman->drop_field($table, $field); + $this->assertFalse($dbman->field_exists($table, 'forcesubscribe')); + + + // drop field with complete xmldb_field object, not having related indexes + $field = new xmldb_field('trackingtype'); // Field has default clause + $this->assertTrue($dbman->field_exists($table, $field)); + $dbman->drop_field($table, $field); + $this->assertFalse($dbman->field_exists($table, $field)); + } + + /** + * Test behaviour of change_field_type() + */ + public function test_change_field_type() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // create table with indexed field and not indexed field to + // perform tests in both fields, both having defaults + $table = new xmldb_table('test_table_cust0'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2'); + $table->add_field('anothernumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '4'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('onenumber', XMLDB_INDEX_NOTUNIQUE, array('onenumber')); + $dbman->create_table($table); + + $record = new stdClass(); + $record->onenumber = 2; + $record->anothernumber = 4; + $recoriginal = $DB->insert_record('test_table_cust0', $record); + + // change column from integer to varchar. Must return exception because of dependent index + $field = new xmldb_field('onenumber'); + $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'test'); + try { + $dbman->change_field_type($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_dependency_exception); + } + // column continues being integer 10 not null default 2 + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['onenumber']->meta_type, 'I'); + //TODO: check the rest of attributes + + // change column from integer to varchar. Must work because column has no dependencies + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'test'); + $dbman->change_field_type($table, $field); + // column is char 30 not null default 'test' now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'C'); + //TODO: check the rest of attributes + + // change column back from char to integer + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '8', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '5'); + $dbman->change_field_type($table, $field); + // column is integer 8 not null default 5 now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'I'); + //TODO: check the rest of attributes + + // change column once more from integer to char + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, "test'n drop"); + $dbman->change_field_type($table, $field); + // column is char 30 not null default "test'n drop" now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'C'); + //TODO: check the rest of attributes + + // insert one string value and try to convert to integer. Must throw exception + $record = new stdClass(); + $record->onenumber = 7; + $record->anothernumber = 'string value'; + $rectodrop = $DB->insert_record('test_table_cust0', $record); + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '5'); + try { + $dbman->change_field_type($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_change_structure_exception); + } + // column continues being char 30 not null default "test'n drop" now + $this->assertEquals($columns['anothernumber']->meta_type, 'C'); + //TODO: check the rest of attributes + $DB->delete_records('test_table_cust0', array('id' => $rectodrop)); // Delete the string record + + // change the column from varchar to float + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_FLOAT, '20,10', XMLDB_UNSIGNED, null, null, null); + $dbman->change_field_type($table, $field); + // column is float 20,10 null default null + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'N'); // floats are seen as number + //TODO: check the rest of attributes + + // change the column back from float to varchar + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'test'); + $dbman->change_field_type($table, $field); + // column is char 20 not null default "test" now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'C'); + //TODO: check the rest of attributes + + // change the column from varchar to number + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_NUMBER, '20,10', XMLDB_UNSIGNED, null, null, null); + $dbman->change_field_type($table, $field); + // column is number 20,10 null default null now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'N'); + //TODO: check the rest of attributes + + // change the column from number to integer + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, null, null, null); + $dbman->change_field_type($table, $field); + // column is integer 2 null default null now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'I'); + //TODO: check the rest of attributes + + // change the column from integer to text + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); + $dbman->change_field_type($table, $field); + // column is char text not null default null + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'X'); + + // change the column back from text to number + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_NUMBER, '20,10', XMLDB_UNSIGNED, null, null, null); + $dbman->change_field_type($table, $field); + // column is number 20,10 null default null now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'N'); + //TODO: check the rest of attributes + + // change the column from number to text + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); + $dbman->change_field_type($table, $field); + // column is char text not null default "test" now + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'X'); + //TODO: check the rest of attributes + + // change the column back from text to integer + $field = new xmldb_field('anothernumber'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 10); + $dbman->change_field_type($table, $field); + // column is integer 10 not null default 10 + $columns = $DB->get_columns('test_table_cust0'); + $this->assertEquals($columns['anothernumber']->meta_type, 'I'); + //TODO: check the rest of attributes + + // check original value has survived to all the type changes + $this->assertnotEmpty($rec = $DB->get_record('test_table_cust0', array('id' => $recoriginal))); + $this->assertEquals($rec->anothernumber, 4); + + $dbman->drop_table($table); + $this->assertFalse($dbman->table_exists($table)); + } + + /** + * Test behaviour of test_change_field_precision() + */ + public function test_change_field_precision() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + + // fill the table with some records before dropping fields + $this->fill_deftable('test_table1'); + + // change text field from medium to big + $field = new xmldb_field('intro'); + $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); + $dbman->change_field_precision($table, $field); + $columns = $DB->get_columns('test_table1'); + // cannot check the text type, only the metatype + $this->assertEquals($columns['intro']->meta_type, 'X'); + //TODO: check the rest of attributes + + // change char field from 30 to 20 + $field = new xmldb_field('secondname'); + $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, null); + $dbman->change_field_precision($table, $field); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['secondname']->meta_type, 'C'); + //TODO: check the rest of attributes + + // change char field from 20 to 10, having contents > 10cc. Throw exception + $field = new xmldb_field('secondname'); + $field->set_attributes(XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, null); + try { + $dbman->change_field_precision($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_change_structure_exception); + } + // No changes in field specs at all + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['secondname']->meta_type, 'C'); + //TODO: check the rest of attributes + + // change number field from 20,10 to 10,2 + $field = new xmldb_field('grade'); + $field->set_attributes(XMLDB_TYPE_NUMBER, '10,2', null, null, null, null); + $dbman->change_field_precision($table, $field); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['grade']->meta_type, 'N'); + //TODO: check the rest of attributes + + // change integer field from 10 to 2 + $field = new xmldb_field('userid'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $dbman->change_field_precision($table, $field); + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['userid']->meta_type, 'I'); + //TODO: check the rest of attributes + + // change the column from integer (2) to integer (6) (forces change of type in some DBs) + $field = new xmldb_field('userid'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, null, null, null); + $dbman->change_field_precision($table, $field); + // column is integer 6 null default null now + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['userid']->meta_type, 'I'); + //TODO: check the rest of attributes + + // insert one record with 6-digit field + $record = new stdClass(); + $record->course = 10; + $record->secondname = 'third record'; + $record->intro = 'third record'; + $record->userid = 123456; + $DB->insert_record('test_table1', $record); + // change integer field from 6 to 2, contents are bigger. must throw exception + $field = new xmldb_field('userid'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + try { + $dbman->change_field_precision($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_change_structure_exception); + } + // No changes in field specs at all + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['userid']->meta_type, 'I'); + //TODO: check the rest of attributes + + // change integer field from 10 to 3, in field used by index. must throw exception. + $field = new xmldb_field('course'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + try { + $dbman->change_field_precision($table, $field); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_dependency_exception); + } + // No changes in field specs at all + $columns = $DB->get_columns('test_table1'); + $this->assertEquals($columns['course']->meta_type, 'I'); + //TODO: check the rest of attributes + } + + public function testChangeFieldNullability() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = new xmldb_table('test_table_cust0'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $record = new stdClass(); + $record->name = NULL; + + ob_start(); // hide debug warning + try { + $result = $DB->insert_record('test_table_cust0', $record, false); + } catch (dml_exception $e) { + $result = false; + } + ob_end_clean(); + $this->assertFalse($result); + + $field = new xmldb_field('name'); + $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, null, null, null); + $dbman->change_field_notnull($table, $field); + + $this->assertTrue($DB->insert_record('test_table_cust0', $record, false)); + + // TODO: add some tests with existing data in table + $DB->delete_records('test_table_cust0'); + + $field = new xmldb_field('name'); + $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null); + $dbman->change_field_notnull($table, $field); + + ob_start(); // hide debug warning + try { + $result = $DB->insert_record('test_table_cust0', $record, false); + } catch (dml_exception $e) { + $result = false; + } + ob_end_clean(); + $this->assertFalse($result); + + $dbman->drop_table($table); + } + + public function testChangeFieldDefault() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = new xmldb_table('test_table_cust0'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $field = new xmldb_field('name'); + $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle2'); + $dbman->change_field_default($table, $field); + + $record = new stdClass(); + $record->onenumber = 666; + $id = $DB->insert_record('test_table_cust0', $record); + + $record = $DB->get_record('test_table_cust0', array('id'=>$id)); + $this->assertEquals($record->name, 'Moodle2'); + + + $field = new xmldb_field('onenumber'); + $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 666); + $dbman->change_field_default($table, $field); + + $record = new stdClass(); + $record->name = 'something'; + $id = $DB->insert_record('test_table_cust0', $record); + + $record = $DB->get_record('test_table_cust0', array('id'=>$id)); + $this->assertEquals($record->onenumber, '666'); + + $dbman->drop_table($table); + } + + public function testAddUniqueIndex() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = new xmldb_table('test_table_cust0'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $record = new stdClass(); + $record->onenumber = 666; + $record->name = 'something'; + $DB->insert_record('test_table_cust0', $record, false); + + $index = new xmldb_index('onenumber-name'); + $index->set_attributes(XMLDB_INDEX_UNIQUE, array('onenumber', 'name')); + $dbman->add_index($table, $index); + + ob_start(); // hide debug warning + try { + $result = $DB->insert_record('test_table_cust0', $record, false); + } catch (dml_exception $e) { + $result = false;; + } + ob_end_clean(); + $this->assertFalse($result); + + $dbman->drop_table($table); + } + + public function testAddNonUniqueIndex() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $index = new xmldb_index('secondname'); + $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name')); + $dbman->add_index($table, $index); + } + + public function testFindIndexName() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $index = new xmldb_index('secondname'); + $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name')); + $dbman->add_index($table, $index); + + //DBM Systems name their indices differently - do not test the actual index name + $result = $dbman->find_index_name($table, $index); + $this->assertTrue(!empty($result)); + + $nonexistentindex = new xmldb_index('nonexistentindex'); + $nonexistentindex->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('name')); + $this->assertFalse($dbman->find_index_name($table, $nonexistentindex)); + } + + public function testDropIndex() { + $DB = $this->tdb; // do not use global $DB! + + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $index = new xmldb_index('secondname'); + $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name')); + $dbman->add_index($table, $index); + + $dbman->drop_index($table, $index); + $this->assertFalse($dbman->find_index_name($table, $index)); + + // Test we are able to drop indexes having hyphens. MDL-22804 + // Create index with hyphens (by hand) + $indexname = 'test-index-with-hyphens'; + switch ($DB->get_dbfamily()) { + case 'mysql': + $indexname = '`' . $indexname . '`'; + break; + default: + $indexname = '"' . $indexname . '"'; + } + $stmt = "CREATE INDEX {$indexname} ON {$DB->get_prefix()}test_table1 (course, name)"; + $DB->change_database_structure($stmt); + $this->assertNotEmpty($dbman->find_index_name($table, $index)); + // Index created, let's drop it using db manager stuff + $index = new xmldb_index('indexname', XMLDB_INDEX_NOTUNIQUE, array('course', 'name')); + $dbman->drop_index($table, $index); + $this->assertFalse($dbman->find_index_name($table, $index)); + } + + public function testAddUniqueKey() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $key = new xmldb_key('id-course-grade'); + $key->set_attributes(XMLDB_KEY_UNIQUE, array('id', 'course', 'grade')); + $dbman->add_key($table, $key); + } + + public function testAddForeignUniqueKey() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $this->create_deftable('test_table0'); + + $key = new xmldb_key('course'); + $key->set_attributes(XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('id')); + $dbman->add_key($table, $key); + } + + public function testDropKey() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $this->create_deftable('test_table0'); + + $key = new xmldb_key('course'); + $key->set_attributes(XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('id')); + $dbman->add_key($table, $key); + + $dbman->drop_key($table, $key); + } + + public function testAddForeignKey() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $this->create_deftable('test_table0'); + + $key = new xmldb_key('course'); + $key->set_attributes(XMLDB_KEY_FOREIGN, array('course'), 'test_table0', array('id')); + $dbman->add_key($table, $key); + } + + public function testDropForeignKey() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table1'); + $this->create_deftable('test_table0'); + + $key = new xmldb_key('course'); + $key->set_attributes(XMLDB_KEY_FOREIGN, array('course'), 'test_table0', array('id')); + $dbman->add_key($table, $key); + + $dbman->drop_key($table, $key); + } + + public function testRenameField() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + $field = new xmldb_field('type'); + $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'general', 'course'); + + $dbman->rename_field($table, $field, 'newfieldname'); + + $columns = $DB->get_columns('test_table0'); + + $this->assertFalse(array_key_exists('type', $columns)); + $this->assertTrue(array_key_exists('newfieldname', $columns)); + } + + + public function testIndexExists() { + // Skipping: this is just a test of find_index_name + } + + public function testFindKeyName() { + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + $key = $table->getKey('primary'); + + // With Mysql, the return value is actually "mdl_test_id_pk" + $result = $dbman->find_key_name($table, $key); + $this->assertTrue(!empty($result)); + } + + public function testDeleteTablesFromXmldbFile() { + global $CFG; + $dbman = $this->tdb->get_manager(); + + $this->create_deftable('test_table1'); + + $this->assertTrue($dbman->table_exists('test_table1')); + + // feed nonexistent file + try { + ob_start(); // hide debug warning + $dbman->delete_tables_from_xmldb_file('fpsoiudfposui'); + ob_end_clean(); + $this->assertTrue(false); + } catch (Exception $e) { + ob_end_clean(); + $this->assertTrue($e instanceof moodle_exception); + } + + // Real file but invalid xml file + $devhack = false; + if (!empty($CFG->xmldbdisablenextprevchecking)) { + $CFG->xmldbdisablenextprevchecking = false; + $devhack = true; + } + try { + ob_start(); // hide debug warning + $dbman->delete_tables_from_xmldb_file(__DIR__ . '/fixtures/invalid.xml'); + $this->assertTrue(false); + ob_end_clean(); + } catch (Exception $e) { + ob_end_clean(); + $this->assertTrue($e instanceof moodle_exception); + } + if ($devhack) { + $CFG->xmldbdisablenextprevchecking = true; + } + + // Check that the table has not been deleted from DB + $this->assertTrue($dbman->table_exists('test_table1')); + + // Real and valid xml file + //TODO: drop UNSINGED completely in Moodle 2.4 + $dbman->delete_tables_from_xmldb_file(__DIR__ . '/fixtures/xmldb_table.xml'); + + // Check that the table has been deleted from DB + $this->assertFalse($dbman->table_exists('test_table1')); + } + + public function testInstallFromXmldbFile() { + global $CFG; + $dbman = $this->tdb->get_manager(); + + // feed nonexistent file + try { + ob_start(); // hide debug warning + $dbman->install_from_xmldb_file('fpsoiudfposui'); + ob_end_clean(); + $this->assertTrue(false); + } catch (Exception $e) { + ob_end_clean(); + $this->assertTrue($e instanceof moodle_exception); + } + + // Real but invalid xml file + $devhack = false; + if (!empty($CFG->xmldbdisablenextprevchecking)) { + $CFG->xmldbdisablenextprevchecking = false; + $devhack = true; + } + try { + ob_start(); // hide debug warning + $dbman->install_from_xmldb_file(__DIR__ . '/fixtures/invalid.xml'); + ob_end_clean(); + $this->assertTrue(false); + } catch (Exception $e) { + ob_end_clean(); + $this->assertTrue($e instanceof moodle_exception); + } + if ($devhack) { + $CFG->xmldbdisablenextprevchecking = true; + } + + // Check that the table has not yet been created in DB + $this->assertFalse($dbman->table_exists('test_table1')); + + // Real and valid xml file + $dbman->install_from_xmldb_file(__DIR__ . '/fixtures/xmldb_table.xml'); + $this->assertTrue($dbman->table_exists('test_table1')); + } + + public function test_temp_tables() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // Create temp table0 + $table0 = $this->tables['test_table0']; + $dbman->create_temp_table($table0); + $this->assertTrue($dbman->table_exists('test_table0')); + + // Try to create temp table with same name, must throw exception + $dupetable = $this->tables['test_table0']; + try { + $dbman->create_temp_table($dupetable); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + // Try to create table with same name, must throw exception + $dupetable = $this->tables['test_table0']; + try { + $dbman->create_table($dupetable); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_exception); + } + + // Create another temp table1 + $table1 = $this->tables['test_table1']; + $dbman->create_temp_table($table1); + $this->assertTrue($dbman->table_exists('test_table1')); + + // Get columns and perform some basic tests + $columns = $DB->get_columns('test_table1'); + $this->assertEquals(count($columns), 11); + $this->assertTrue($columns['name'] instanceof database_column_info); + $this->assertEquals($columns['name']->max_length, 30); + $this->assertTrue($columns['name']->has_default); + $this->assertEquals($columns['name']->default_value, 'Moodle'); + + // Insert some records + $inserted = $this->fill_deftable('test_table1'); + $records = $DB->get_records('test_table1'); + $this->assertEquals(count($records), $inserted); + $this->assertEquals($records[1]->course, $this->records['test_table1'][0]->course); + $this->assertEquals($records[1]->secondname, $this->records['test_table1'][0]->secondname); + $this->assertEquals($records[2]->intro, $this->records['test_table1'][1]->intro); + + // Drop table1 + $dbman->drop_temp_table($table1); + $this->assertFalse($dbman->table_exists('test_table1')); + + // Try to drop non-existing temp table, must throw exception + $noetable = $this->tables['test_table1']; + try { + $dbman->drop_temp_table($noetable); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof ddl_table_missing_exception); + } + + // Fill/modify/delete a few table0 records + // TODO: that's + + // Drop table0 + $dbman->drop_temp_table($table0); + $this->assertFalse($dbman->table_exists('test_table0')); + + // Have dropped all these temp tables here, to avoid conflicts with other (normal tables) tests! + } + + public function test_concurrent_temp_tables() { + $DB = $this->tdb; // do not use global $DB! + $dbman = $this->tdb->get_manager(); + + // Define 2 records + $record1 = (object)array( + 'course' => 1, + 'secondname' => '11 important', + 'intro' => '111 important'); + $record2 = (object)array( + 'course' => 2, + 'secondname' => '22 important', + 'intro' => '222 important'); + + // Create temp table1 and insert 1 record (in DB) + $table = $this->tables['test_table1']; + $dbman->create_temp_table($table); + $this->assertTrue($dbman->table_exists('test_table1')); + $inserted = $DB->insert_record('test_table1', $record1); + + // Switch to new connection + $cfg = $DB->export_dbconfig(); + if (!isset($cfg->dboptions)) { + $cfg->dboptions = array(); + } + $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); + $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); + $dbman2 = $DB2->get_manager(); + $this->assertFalse($dbman2->table_exists('test_table1')); // Temp table not exists in DB2 + + // Create temp table1 and insert 1 record (in DB2) + $table = $this->tables['test_table1']; + $dbman2->create_temp_table($table); + $this->assertTrue($dbman2->table_exists('test_table1')); + $inserted = $DB2->insert_record('test_table1', $record2); + + $dbman2->drop_temp_table($table); // Drop temp table before closing DB2 + $this->assertFalse($dbman2->table_exists('test_table1')); + $DB2->dispose(); // Close DB2 + + $this->assertTrue($dbman->table_exists('test_table1')); // Check table continues existing for DB + $dbman->drop_temp_table($table); // Drop temp table + $this->assertFalse($dbman->table_exists('test_table1')); + } + + public function test_reset_sequence() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $dbman->create_table($table); + $tablename = $table->getName(); + $this->tables[$tablename] = $table; + + $record = (object)array('id'=>666, 'course'=>10); + $DB->import_record('testtable', $record); + $DB->delete_records('testtable'); + + $dbman->reset_sequence($table); // using xmldb object + $this->assertEquals(1, $DB->insert_record('testtable', (object)array('course'=>13))); + + $DB->import_record('testtable', $record); + $dbman->reset_sequence($tablename); // using string + $this->assertEquals(667, $DB->insert_record('testtable', (object)array('course'=>13))); + + $dbman->drop_table($table); + } + + public function test_reserved_words() { + $reserved = sql_generator::getAllReservedWords(); + $this->assertTrue(count($reserved) > 1); + } + + public function test_index_max_bytes() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $maxstr = ''; + for($i=0; $i<255; $i++) { + $maxstr .= '言'; // random long string that should fix exactly the limit for one char column + } + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $dbman->create_table($table); + $tablename = $table->getName(); + $this->tables[$tablename] = $table; + + $rec = new stdClass(); + $rec->name = $maxstr; + + $id = $DB->insert_record($tablename, $rec); + $this->assertTrue(!empty($id)); + + $rec = $DB->get_record($tablename, array('id'=>$id)); + $this->assertSame($rec->name, $maxstr); + + $dbman->drop_table($table); + + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, 255+1, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name')); + + try { + $dbman->create_table($table); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + public function test_index_composed_max_bytes() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $maxstr = ''; + for($i=0; $i<200; $i++) { + $maxstr .= '言'; + } + $reststr = ''; + for($i=0; $i<133; $i++) { + $reststr .= '言'; + } + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name1', XMLDB_TYPE_CHAR, 200, null, XMLDB_NOTNULL, null); + $table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $dbman->create_table($table); + $tablename = $table->getName(); + $this->tables[$tablename] = $table; + + $rec = new stdClass(); + $rec->name1 = $maxstr; + $rec->name2 = $reststr; + + $id = $DB->insert_record($tablename, $rec); + $this->assertTrue(!empty($id)); + + $rec = $DB->get_record($tablename, array('id'=>$id)); + $this->assertSame($rec->name1, $maxstr); + $this->assertSame($rec->name2, $reststr); + + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name1', XMLDB_TYPE_CHAR, 201, null, XMLDB_NOTNULL, null); + $table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + + try { + $dbman->create_table($table); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + public function test_char_size_limit() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, xmldb_field::CHAR_MAX_LENGTH, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $dbman->create_table($table); + $tablename = $table->getName(); + $this->tables[$tablename] = $table; + + // this has to work in all DBs + $maxstr = ''; + for($i=0; $iname = $maxstr; + + $id = $DB->insert_record($tablename, $rec); + $this->assertTrue(!empty($id)); + + $rec = $DB->get_record($tablename, array('id'=>$id)); + $this->assertSame($rec->name, $maxstr); + + + // Following test is supposed to fail in oracle + $maxstr = ''; + for($i=0; $iname = $maxstr; + + try { + $id = $DB->insert_record($tablename, $rec); + $this->assertTrue(!empty($id)); + + $rec = $DB->get_record($tablename, array('id'=>$id)); + $this->assertSame($rec->name, $maxstr); + } catch (dml_exception $e) { + if ($DB->get_dbfamily() === 'oracle') { + $this->fail('Oracle does not support text fields larger than 4000 bytes, this is not a big problem for mostly ascii based languages'); + } else { + throw $e; + } + } + + + $table = new xmldb_table('testtable'); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, xmldb_field::CHAR_MAX_LENGTH+1, null, XMLDB_NOTNULL, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + // Drop if exists + if ($dbman->table_exists($table)) { + $dbman->drop_table($table); + } + $tablename = $table->getName(); + $this->tables[$tablename] = $table; + + try { + $dbman->create_table($table); + $this->assertTrue(false); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + // Following methods are not supported == Do not test + /* + public function testRenameIndex() { + // unsupported! + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + $index = new xmldb_index('course'); + $index->set_attributes(XMLDB_INDEX_UNIQUE, array('course')); + + $this->assertTrue($dbman->rename_index($table, $index, 'newindexname')); + } + + public function testRenameKey() { + //unsupported + $dbman = $this->tdb->get_manager(); + + $table = $this->create_deftable('test_table0'); + $key = new xmldb_key('course'); + $key->set_attributes(XMLDB_KEY_UNIQUE, array('course')); + + $this->assertTrue($dbman->rename_key($table, $key, 'newkeyname')); + } + */ + +} diff --git a/lib/ddl/tests/fixtures/invalid.xml b/lib/ddl/tests/fixtures/invalid.xml new file mode 100644 index 0000000000000..36880563a31be --- /dev/null +++ b/lib/ddl/tests/fixtures/invalid.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + +
+
+
diff --git a/lib/ddl/tests/fixtures/xmldb_table.xml b/lib/ddl/tests/fixtures/xmldb_table.xml new file mode 100644 index 0000000000000..90cb13768a22d --- /dev/null +++ b/lib/ddl/tests/fixtures/xmldb_table.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + +
+
+
diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 9770988f0055a..192097308ccd7 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -410,7 +410,11 @@ public function get_indexes($table) { $sql = "SHOW INDEXES FROM {$this->prefix}$table"; $this->query_start($sql, null, SQL_QUERY_AUX); $result = $this->mysqli->query($sql); - $this->query_end($result); + try { + $this->query_end($result); + } catch (dml_read_exception $e) { + return $indexes; // table does not exist - no indexes... + } if ($result) { while ($res = $result->fetch_object()) { if ($res->Key_name === 'PRIMARY') { diff --git a/lib/dml/tests/dml_test.php b/lib/dml/tests/dml_test.php new file mode 100644 index 0000000000000..9fe16a4b8edf6 --- /dev/null +++ b/lib/dml/tests/dml_test.php @@ -0,0 +1,4555 @@ +. + +/** + * DML layer tests + * + * @package core + * @subpackage dml + * @category phpunit + * @copyright 2008 Nicolas Connault + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +class dml_testcase extends database_driver_testcase { + + /** + * Get a xmldb_table object for testing, deleting any existing table + * of the same name, for example if one was left over from a previous test + * run that crashed. + * + * @param string $suffix table name suffix, use if you need more test tables + * @return xmldb_table the table object. + */ + private function get_test_table($suffix = '') { + $tablename = "test_table"; + if ($suffix !== '') { + $tablename .= $suffix; + } + + $table = new xmldb_table($tablename); + $table->setComment("This is a test'n drop table. You can drop it safely"); + return new xmldb_table($tablename); + } + + protected function enable_debugging() { + ob_start(); // hide debug warning + } + + protected function get_debugging() { + $debuginfo = ob_get_contents(); + ob_end_clean(); + + return $debuginfo; + } + + // NOTE: please keep order of test methods here matching the order of moodle_database class methods + + function test_diagnose() { + $DB = $this->tdb; + $result = $DB->diagnose(); + $this->assertNull($result, 'Database self diagnostics failed %s'); + } + + function test_get_server_info() { + $DB = $this->tdb; + $result = $DB->get_server_info(); + $this->assertTrue(is_array($result)); + $this->assertTrue(array_key_exists('description', $result)); + $this->assertTrue(array_key_exists('version', $result)); + } + + public function test_get_in_or_equal() { + $DB = $this->tdb; + + // SQL_PARAMS_QM - IN or = + + // Correct usage of multiple values + $in_values = array('value1', 'value2', '3', 4, null, false, true); + list($usql, $params) = $DB->get_in_or_equal($in_values); + $this->assertEquals('IN ('.implode(',',array_fill(0, count($in_values), '?')).')', $usql); + $this->assertEquals(count($in_values), count($params)); + foreach ($params as $key => $value) { + $this->assertSame($in_values[$key], $value); + } + + // Correct usage of single value (in an array) + $in_values = array('value1'); + list($usql, $params) = $DB->get_in_or_equal($in_values); + $this->assertEquals("= ?", $usql); + $this->assertEquals(1, count($params)); + $this->assertEquals($in_values[0], $params[0]); + + // Correct usage of single value + $in_value = 'value1'; + list($usql, $params) = $DB->get_in_or_equal($in_values); + $this->assertEquals("= ?", $usql); + $this->assertEquals(1, count($params)); + $this->assertEquals($in_value, $params[0]); + + // SQL_PARAMS_QM - NOT IN or <> + + // Correct usage of multiple values + $in_values = array('value1', 'value2', 'value3', 'value4'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false); + $this->assertEquals("NOT IN (?,?,?,?)", $usql); + $this->assertEquals(4, count($params)); + foreach ($params as $key => $value) { + $this->assertEquals($in_values[$key], $value); + } + + // Correct usage of single value (in array() + $in_values = array('value1'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false); + $this->assertEquals("<> ?", $usql); + $this->assertEquals(1, count($params)); + $this->assertEquals($in_values[0], $params[0]); + + // Correct usage of single value + $in_value = 'value1'; + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, null, false); + $this->assertEquals("<> ?", $usql); + $this->assertEquals(1, count($params)); + $this->assertEquals($in_value, $params[0]); + + // SQL_PARAMS_NAMED - IN or = + + // Correct usage of multiple values + $in_values = array('value1', 'value2', 'value3', 'value4'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true); + $this->assertEquals(4, count($params)); + reset($in_values); + $ps = array(); + foreach ($params as $key => $value) { + $this->assertEquals(current($in_values), $value); + next($in_values); + $ps[] = ':'.$key; + } + $this->assertEquals("IN (".implode(',', $ps).")", $usql); + + // Correct usage of single values (in array) + $in_values = array('value1'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals("= :$key", $usql); + $this->assertEquals($in_value, $value); + + // Correct usage of single value + $in_value = 'value1'; + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', true); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals("= :$key", $usql); + $this->assertEquals($in_value, $value); + + // SQL_PARAMS_NAMED - NOT IN or <> + + // Correct usage of multiple values + $in_values = array('value1', 'value2', 'value3', 'value4'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false); + $this->assertEquals(4, count($params)); + reset($in_values); + $ps = array(); + foreach ($params as $key => $value) { + $this->assertEquals(current($in_values), $value); + next($in_values); + $ps[] = ':'.$key; + } + $this->assertEquals("NOT IN (".implode(',', $ps).")", $usql); + + // Correct usage of single values (in array) + $in_values = array('value1'); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals("<> :$key", $usql); + $this->assertEquals($in_value, $value); + + // Correct usage of single value + $in_value = 'value1'; + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals("<> :$key", $usql); + $this->assertEquals($in_value, $value); + + // make sure the param names are unique + list($usql1, $params1) = $DB->get_in_or_equal(array(1,2,3), SQL_PARAMS_NAMED, 'param'); + list($usql2, $params2) = $DB->get_in_or_equal(array(1,2,3), SQL_PARAMS_NAMED, 'param'); + $params1 = array_keys($params1); + $params2 = array_keys($params2); + $common = array_intersect($params1, $params2); + $this->assertEquals(count($common), 0); + + // Some incorrect tests + + // Incorrect usage passing not-allowed params type + $in_values = array(1, 2, 3); + try { + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_DOLLAR, 'param', false); + $this->fail('An Exception is missing, expected due to not supported SQL_PARAMS_DOLLAR'); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'typenotimplement'); + } + + // Incorrect usage passing empty array + $in_values = array(); + try { + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false); + $this->fail('An Exception is missing, expected due to empty array of items'); + } catch (exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + // Test using $onemptyitems + + // Correct usage passing empty array and $onemptyitems = NULL (equal = true, QM) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, NULL); + $this->assertEquals(' IS NULL', $usql); + $this->assertSame(array(), $params); + + // Correct usage passing empty array and $onemptyitems = NULL (equal = false, NAMED) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, NULL); + $this->assertEquals(' IS NOT NULL', $usql); + $this->assertSame(array(), $params); + + // Correct usage passing empty array and $onemptyitems = true (equal = true, QM) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, true); + $this->assertEquals('= ?', $usql); + $this->assertSame(array(true), $params); + + // Correct usage passing empty array and $onemptyitems = true (equal = false, NAMED) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, true); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals('<> :'.$key, $usql); + $this->assertSame($value, true); + + // Correct usage passing empty array and $onemptyitems = -1 (equal = true, QM) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, -1); + $this->assertEquals('= ?', $usql); + $this->assertSame(array(-1), $params); + + // Correct usage passing empty array and $onemptyitems = -1 (equal = false, NAMED) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, -1); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals('<> :'.$key, $usql); + $this->assertSame($value, -1); + + // Correct usage passing empty array and $onemptyitems = 'onevalue' (equal = true, QM) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_QM, 'param', true, 'onevalue'); + $this->assertEquals('= ?', $usql); + $this->assertSame(array('onevalue'), $params); + + // Correct usage passing empty array and $onemptyitems = 'onevalue' (equal = false, NAMED) + $in_values = array(); + list($usql, $params) = $DB->get_in_or_equal($in_values, SQL_PARAMS_NAMED, 'param', false, 'onevalue'); + $this->assertEquals(1, count($params)); + $value = reset($params); + $key = key($params); + $this->assertEquals('<> :'.$key, $usql); + $this->assertSame($value, 'onevalue'); + } + + public function test_fix_table_names() { + $DB = new moodle_database_for_testing(); + $prefix = $DB->get_prefix(); + + // Simple placeholder + $placeholder = "{user_123}"; + $this->assertSame($prefix."user_123", $DB->public_fix_table_names($placeholder)); + + // wrong table name + $placeholder = "{user-a}"; + $this->assertSame($placeholder, $DB->public_fix_table_names($placeholder)); + + // wrong table name + $placeholder = "{123user}"; + $this->assertSame($placeholder, $DB->public_fix_table_names($placeholder)); + + // Full SQL + $sql = "SELECT * FROM {user}, {funny_table_name}, {mdl_stupid_table} WHERE {user}.id = {funny_table_name}.userid"; + $expected = "SELECT * FROM {$prefix}user, {$prefix}funny_table_name, {$prefix}mdl_stupid_table WHERE {$prefix}user.id = {$prefix}funny_table_name.userid"; + $this->assertSame($expected, $DB->public_fix_table_names($sql)); + } + + function test_fix_sql_params() { + $DB = $this->tdb; + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + // Correct table placeholder substitution + $sql = "SELECT * FROM {{$tablename}}"; + $sqlarray = $DB->fix_sql_params($sql); + $this->assertEquals("SELECT * FROM {$DB->get_prefix()}".$tablename, $sqlarray[0]); + + // Conversions of all param types + $sql = array(); + $sql[SQL_PARAMS_NAMED] = "SELECT * FROM {$DB->get_prefix()}testtable WHERE name = :param1, course = :param2"; + $sql[SQL_PARAMS_QM] = "SELECT * FROM {$DB->get_prefix()}testtable WHERE name = ?, course = ?"; + $sql[SQL_PARAMS_DOLLAR] = "SELECT * FROM {$DB->get_prefix()}testtable WHERE name = \$1, course = \$2"; + + $params = array(); + $params[SQL_PARAMS_NAMED] = array('param1'=>'first record', 'param2'=>1); + $params[SQL_PARAMS_QM] = array('first record', 1); + $params[SQL_PARAMS_DOLLAR] = array('first record', 1); + + list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_NAMED], $params[SQL_PARAMS_NAMED]); + $this->assertSame($rsql, $sql[$rtype]); + $this->assertSame($rparams, $params[$rtype]); + + list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_QM], $params[SQL_PARAMS_QM]); + $this->assertSame($rsql, $sql[$rtype]); + $this->assertSame($rparams, $params[$rtype]); + + list($rsql, $rparams, $rtype) = $DB->fix_sql_params($sql[SQL_PARAMS_DOLLAR], $params[SQL_PARAMS_DOLLAR]); + $this->assertSame($rsql, $sql[$rtype]); + $this->assertSame($rparams, $params[$rtype]); + + + // Malformed table placeholder + $sql = "SELECT * FROM [testtable]"; + $sqlarray = $DB->fix_sql_params($sql); + $this->assertSame($sql, $sqlarray[0]); + + + // Mixed param types (colon and dollar) + $sql = "SELECT * FROM {{$tablename}} WHERE name = :param1, course = \$1"; + $params = array('param1' => 'record1', 'param2' => 3); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Mixed param types (question and dollar) + $sql = "SELECT * FROM {{$tablename}} WHERE name = ?, course = \$1"; + $params = array('param1' => 'record2', 'param2' => 5); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Too few params in sql + $sql = "SELECT * FROM {{$tablename}} WHERE name = ?, course = ?, id = ?"; + $params = array('record2', 3); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Too many params in array: no error, just use what is necessary + $params[] = 1; + $params[] = time(); + try { + $sqlarray = $DB->fix_sql_params($sql, $params); + $this->assertTrue(is_array($sqlarray)); + $this->assertEquals(count($sqlarray[1]), 3); + } catch (Exception $e) { + $this->fail("Unexpected ".get_class($e)." exception"); + } + + // Named params missing from array + $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :course"; + $params = array('wrongname' => 'record1', 'course' => 1); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Duplicate named param in query - this is a very important feature!! + // it helps with debugging of sloppy code + $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :name"; + $params = array('name' => 'record2', 'course' => 3); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Extra named param is ignored + $sql = "SELECT * FROM {{$tablename}} WHERE name = :name, course = :course"; + $params = array('name' => 'record1', 'course' => 1, 'extrastuff'=>'haha'); + try { + $sqlarray = $DB->fix_sql_params($sql, $params); + $this->assertTrue(is_array($sqlarray)); + $this->assertEquals(count($sqlarray[1]), 2); + } catch (Exception $e) { + $this->fail("Unexpected ".get_class($e)." exception"); + } + + // Params exceeding 30 chars length + $sql = "SELECT * FROM {{$tablename}} WHERE name = :long_placeholder_with_more_than_30"; + $params = array('long_placeholder_with_more_than_30' => 'record1'); + try { + $DB->fix_sql_params($sql, $params); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + // Booleans in NAMED params are casting to 1/0 int + $sql = "SELECT * FROM {{$tablename}} WHERE course = ? OR course = ?"; + $params = array(true, false); + list($sql, $params) = $DB->fix_sql_params($sql, $params); + $this->assertTrue(reset($params) === 1); + $this->assertTrue(next($params) === 0); + + // Booleans in QM params are casting to 1/0 int + $sql = "SELECT * FROM {{$tablename}} WHERE course = :course1 OR course = :course2"; + $params = array('course1' => true, 'course2' => false); + list($sql, $params) = $DB->fix_sql_params($sql, $params); + $this->assertTrue(reset($params) === 1); + $this->assertTrue(next($params) === 0); + + // Booleans in DOLLAR params are casting to 1/0 int + $sql = "SELECT * FROM {{$tablename}} WHERE course = \$1 OR course = \$2"; + $params = array(true, false); + list($sql, $params) = $DB->fix_sql_params($sql, $params); + $this->assertTrue(reset($params) === 1); + $this->assertTrue(next($params) === 0); + + // No data types are touched except bool + $sql = "SELECT * FROM {{$tablename}} WHERE name IN (?,?,?,?,?,?)"; + $inparams = array('abc', 'ABC', NULL, '1', 1, 1.4); + list($sql, $params) = $DB->fix_sql_params($sql, $inparams); + $this->assertSame(array_values($params), array_values($inparams)); + } + + public function test_strtok() { + // strtok was previously used by bound emulation, make sure it is not used any more + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $str = 'a?b?c?d'; + $this->assertSame(strtok($str, '?'), 'a'); + + $DB->get_records($tablename, array('id'=>1)); + + $this->assertSame(strtok('?'), 'b'); + } + + public function test_tweak_param_names() { + // Note the tweak_param_names() method is only available in the oracle driver, + // hence we look for expected results indirectly, by testing various DML methods + // with some "extreme" conditions causing the tweak to happen. + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + // Add some columns with 28 chars in the name + $table->add_field('long_int_columnname_with_28c', XMLDB_TYPE_INTEGER, '10'); + $table->add_field('long_dec_columnname_with_28c', XMLDB_TYPE_NUMBER, '10,2'); + $table->add_field('long_str_columnname_with_28c', XMLDB_TYPE_CHAR, '100'); + // Add some columns with 30 chars in the name + $table->add_field('long_int_columnname_with_30cxx', XMLDB_TYPE_INTEGER, '10'); + $table->add_field('long_dec_columnname_with_30cxx', XMLDB_TYPE_NUMBER, '10,2'); + $table->add_field('long_str_columnname_with_30cxx', XMLDB_TYPE_CHAR, '100'); + + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + $dbman->create_table($table); + + $this->assertTrue($dbman->table_exists($tablename)); + + // Test insert record + $rec1 = new stdClass(); + $rec1->long_int_columnname_with_28c = 28; + $rec1->long_dec_columnname_with_28c = 28.28; + $rec1->long_str_columnname_with_28c = '28'; + $rec1->long_int_columnname_with_30cxx = 30; + $rec1->long_dec_columnname_with_30cxx = 30.30; + $rec1->long_str_columnname_with_30cxx = '30'; + + // insert_record() + $rec1->id = $DB->insert_record($tablename, $rec1); + $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); + + // update_record() + $DB->update_record($tablename, $rec1); + $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); + + // set_field() + $rec1->long_int_columnname_with_28c = 280; + $DB->set_field($tablename, 'long_int_columnname_with_28c', $rec1->long_int_columnname_with_28c, + array('id' => $rec1->id, 'long_int_columnname_with_28c' => 28)); + $rec1->long_dec_columnname_with_28c = 280.28; + $DB->set_field($tablename, 'long_dec_columnname_with_28c', $rec1->long_dec_columnname_with_28c, + array('id' => $rec1->id, 'long_dec_columnname_with_28c' => 28.28)); + $rec1->long_str_columnname_with_28c = '280'; + $DB->set_field($tablename, 'long_str_columnname_with_28c', $rec1->long_str_columnname_with_28c, + array('id' => $rec1->id, 'long_str_columnname_with_28c' => '28')); + $rec1->long_int_columnname_with_30cxx = 300; + $DB->set_field($tablename, 'long_int_columnname_with_30cxx', $rec1->long_int_columnname_with_30cxx, + array('id' => $rec1->id, 'long_int_columnname_with_30cxx' => 30)); + $rec1->long_dec_columnname_with_30cxx = 300.30; + $DB->set_field($tablename, 'long_dec_columnname_with_30cxx', $rec1->long_dec_columnname_with_30cxx, + array('id' => $rec1->id, 'long_dec_columnname_with_30cxx' => 30.30)); + $rec1->long_str_columnname_with_30cxx = '300'; + $DB->set_field($tablename, 'long_str_columnname_with_30cxx', $rec1->long_str_columnname_with_30cxx, + array('id' => $rec1->id, 'long_str_columnname_with_30cxx' => '30')); + $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); + + // delete_records() + $rec2 = $DB->get_record($tablename, array('id' => $rec1->id)); + $rec2->id = $DB->insert_record($tablename, $rec2); + $this->assertEquals(2, $DB->count_records($tablename)); + $DB->delete_records($tablename, (array) $rec2); + $this->assertEquals(1, $DB->count_records($tablename)); + + // get_recordset() + $rs = $DB->get_recordset($tablename, (array) $rec1); + $iterations = 0; + foreach ($rs as $rec2) { + $iterations++; + } + $rs->close(); + $this->assertEquals(1, $iterations); + $this->assertEquals($rec1, $rec2); + + // get_records() + $recs = $DB->get_records($tablename, (array) $rec1); + $this->assertEquals(1, count($recs)); + $this->assertEquals($rec1, reset($recs)); + + // get_fieldset_select() + $select = 'id = :id AND + long_int_columnname_with_28c = :long_int_columnname_with_28c AND + long_dec_columnname_with_28c = :long_dec_columnname_with_28c AND + long_str_columnname_with_28c = :long_str_columnname_with_28c AND + long_int_columnname_with_30cxx = :long_int_columnname_with_30cxx AND + long_dec_columnname_with_30cxx = :long_dec_columnname_with_30cxx AND + long_str_columnname_with_30cxx = :long_str_columnname_with_30cxx'; + $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_28c', $select, (array)$rec1); + $this->assertEquals(1, count($fields)); + $this->assertEquals($rec1->long_int_columnname_with_28c, reset($fields)); + $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_28c', $select, (array)$rec1); + $this->assertEquals($rec1->long_dec_columnname_with_28c, reset($fields)); + $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_28c', $select, (array)$rec1); + $this->assertEquals($rec1->long_str_columnname_with_28c, reset($fields)); + $fields = $DB->get_fieldset_select($tablename, 'long_int_columnname_with_30cxx', $select, (array)$rec1); + $this->assertEquals($rec1->long_int_columnname_with_30cxx, reset($fields)); + $fields = $DB->get_fieldset_select($tablename, 'long_dec_columnname_with_30cxx', $select, (array)$rec1); + $this->assertEquals($rec1->long_dec_columnname_with_30cxx, reset($fields)); + $fields = $DB->get_fieldset_select($tablename, 'long_str_columnname_with_30cxx', $select, (array)$rec1); + $this->assertEquals($rec1->long_str_columnname_with_30cxx, reset($fields)); + + // overlapping placeholders (progressive str_replace) + $overlapselect = 'id = :p AND + long_int_columnname_with_28c = :param1 AND + long_dec_columnname_with_28c = :param2 AND + long_str_columnname_with_28c = :param_with_29_characters_long AND + long_int_columnname_with_30cxx = :param_with_30_characters_long_ AND + long_dec_columnname_with_30cxx = :param_ AND + long_str_columnname_with_30cxx = :param__'; + $overlapparams = array( + 'p' => $rec1->id, + 'param1' => $rec1->long_int_columnname_with_28c, + 'param2' => $rec1->long_dec_columnname_with_28c, + 'param_with_29_characters_long' => $rec1->long_str_columnname_with_28c, + 'param_with_30_characters_long_' => $rec1->long_int_columnname_with_30cxx, + 'param_' => $rec1->long_dec_columnname_with_30cxx, + 'param__' => $rec1->long_str_columnname_with_30cxx); + $recs = $DB->get_records_select($tablename, $overlapselect, $overlapparams); + $this->assertEquals(1, count($recs)); + $this->assertEquals($rec1, reset($recs)); + + // execute() + $DB->execute("DELETE FROM {{$tablename}} WHERE $select", (array)$rec1); + $this->assertEquals(0, $DB->count_records($tablename)); + } + + public function test_get_tables() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + // Need to test with multiple DBs + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $original_count = count($DB->get_tables()); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + $dbman->create_table($table); + $this->assertTrue(count($DB->get_tables()) == $original_count + 1); + + $dbman->drop_table($table); + $this->assertTrue(count($DB->get_tables()) == $original_count); + } + + public function test_get_indexes() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course')); + $table->add_index('course-id', XMLDB_INDEX_UNIQUE, array('course', 'id')); + $dbman->create_table($table); + + $indices = $DB->get_indexes($tablename); + $this->assertTrue(is_array($indices)); + $this->assertEquals(count($indices), 2); + // we do not care about index names for now + $first = array_shift($indices); + $second = array_shift($indices); + if (count($first['columns']) == 2) { + $composed = $first; + $single = $second; + } else { + $composed = $second; + $single = $first; + } + $this->assertFalse($single['unique']); + $this->assertTrue($composed['unique']); + $this->assertEquals(1, count($single['columns'])); + $this->assertEquals(2, count($composed['columns'])); + $this->assertEquals('course', $single['columns'][0]); + $this->assertEquals('course', $composed['columns'][0]); + $this->assertEquals('id', $composed['columns'][1]); + } + + public function test_get_columns() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala'); + $table->add_field('description', XMLDB_TYPE_TEXT, 'small', null, null, null, null); + $table->add_field('enumfield', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'test2'); + $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200); + $table->add_field('onefloat', XMLDB_TYPE_FLOAT, '10,2', null, null, null, 300); + $table->add_field('anotherfloat', XMLDB_TYPE_FLOAT, null, null, null, null, 400); + $table->add_field('negativedfltint', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '-1'); + $table->add_field('negativedfltnumber', XMLDB_TYPE_NUMBER, '10', null, XMLDB_NOTNULL, null, '-2'); + $table->add_field('negativedfltfloat', XMLDB_TYPE_FLOAT, '10', null, XMLDB_NOTNULL, null, '-3'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $columns = $DB->get_columns($tablename); + $this->assertTrue(is_array($columns)); + + $fields = $table->getFields(); + $this->assertEquals(count($columns), count($fields)); + + $field = $columns['id']; + $this->assertEquals('R', $field->meta_type); + $this->assertTrue($field->auto_increment); + $this->assertTrue($field->unique); + + $field = $columns['course']; + $this->assertEquals('I', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertTrue($field->has_default); + $this->assertEquals(0, $field->default_value); + $this->assertTrue($field->not_null); + + $field = $columns['name']; + $this->assertEquals('C', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertEquals(255, $field->max_length); + $this->assertTrue($field->has_default); + $this->assertSame('lala', $field->default_value); + $this->assertFalse($field->not_null); + + $field = $columns['description']; + $this->assertEquals('X', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertFalse($field->has_default); + $this->assertSame(null, $field->default_value); + $this->assertFalse($field->not_null); + + $field = $columns['enumfield']; + $this->assertEquals('C', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertSame('test2', $field->default_value); + $this->assertTrue($field->not_null); + + $field = $columns['onenum']; + $this->assertEquals('N', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertEquals(10, $field->max_length); + $this->assertEquals(2, $field->scale); + $this->assertTrue($field->has_default); + $this->assertEquals(200.0, $field->default_value); + $this->assertFalse($field->not_null); + + $field = $columns['onefloat']; + $this->assertEquals('N', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertTrue($field->has_default); + $this->assertEquals(300.0, $field->default_value); + $this->assertFalse($field->not_null); + + $field = $columns['anotherfloat']; + $this->assertEquals('N', $field->meta_type); + $this->assertFalse($field->auto_increment); + $this->assertTrue($field->has_default); + $this->assertEquals(400.0, $field->default_value); + $this->assertFalse($field->not_null); + + // Test negative defaults in numerical columns + $field = $columns['negativedfltint']; + $this->assertTrue($field->has_default); + $this->assertEquals(-1, $field->default_value); + + $field = $columns['negativedfltnumber']; + $this->assertTrue($field->has_default); + $this->assertEquals(-2, $field->default_value); + + $field = $columns['negativedfltfloat']; + $this->assertTrue($field->has_default); + $this->assertEquals(-3, $field->default_value); + + for ($i = 0; $i < count($columns); $i++) { + if ($i == 0) { + $next_column = reset($columns); + $next_field = reset($fields); + } else { + $next_column = next($columns); + $next_field = next($fields); + } + + $this->assertEquals($next_column->name, $next_field->name); + } + + // Test get_columns for non-existing table returns empty array. MDL-30147 + $columns = $DB->get_columns('xxxx'); + $this->assertEquals(array(), $columns); + } + + public function test_get_manager() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $this->assertTrue($dbman instanceof database_manager); + } + + public function test_setup_is_unicodedb() { + $DB = $this->tdb; + $this->assertTrue($DB->setup_is_unicodedb()); + } + + public function test_set_debug() { //tests get_debug() too + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $sql = "SELECT * FROM {{$tablename}}"; + + $prevdebug = $DB->get_debug(); + + ob_start(); + $DB->set_debug(true); + $this->assertTrue($DB->get_debug()); + $DB->execute($sql); + $DB->set_debug(false); + $this->assertFalse($DB->get_debug()); + $debuginfo = ob_get_contents(); + ob_end_clean(); + $this->assertFalse($debuginfo === ''); + + ob_start(); + $DB->execute($sql); + $debuginfo = ob_get_contents(); + ob_end_clean(); + $this->assertTrue($debuginfo === ''); + + $DB->set_debug($prevdebug); + } + + public function test_execute() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + + $table1 = $this->get_test_table('1'); + $tablename1 = $table1->getName(); + $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table1->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0'); + $table1->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course')); + $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table1); + + $table2 = $this->get_test_table('2'); + $tablename2 = $table2->getName(); + $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table2); + + $DB->insert_record($tablename1, array('course' => 3, 'name' => 'aaa')); + $DB->insert_record($tablename1, array('course' => 1, 'name' => 'bbb')); + $DB->insert_record($tablename1, array('course' => 7, 'name' => 'ccc')); + $DB->insert_record($tablename1, array('course' => 3, 'name' => 'ddd')); + + // select results are ignored + $sql = "SELECT * FROM {{$tablename1}} WHERE course = :course"; + $this->assertTrue($DB->execute($sql, array('course'=>3))); + + // throw exception on error + $sql = "XXUPDATE SET XSSD"; + try { + $DB->execute($sql); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // update records + $sql = "UPDATE {{$tablename1}} + SET course = 6 + WHERE course = ?"; + $this->assertTrue($DB->execute($sql, array('3'))); + $this->assertEquals($DB->count_records($tablename1, array('course' => 6)), 2); + + // update records with subquery condition + // confirm that the option not using table aliases is cross-db + $sql = "UPDATE {{$tablename1}} + SET course = 0 + WHERE NOT EXISTS ( + SELECT course + FROM {{$tablename2}} tbl2 + WHERE tbl2.course = {{$tablename1}}.course + AND 1 = 0)"; // Really we don't update anything, but verify the syntax is allowed + $this->assertTrue($DB->execute($sql)); + + // insert from one into second table + $sql = "INSERT INTO {{$tablename2}} (course) + + SELECT course + FROM {{$tablename1}}"; + $this->assertTrue($DB->execute($sql)); + $this->assertEquals($DB->count_records($tablename2), 4); + } + + public function test_get_recordset() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course')); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $data = array(array('id' => 1, 'course' => 3, 'name' => 'record1', 'onetext'=>'abc'), + array('id' => 2, 'course' => 3, 'name' => 'record2', 'onetext'=>'abcd'), + array('id' => 3, 'course' => 5, 'name' => 'record3', 'onetext'=>'abcde')); + + foreach ($data as $record) { + $DB->insert_record($tablename, $record); + } + + // standard recordset iteration + $rs = $DB->get_recordset($tablename); + $this->assertTrue($rs instanceof moodle_recordset); + reset($data); + foreach($rs as $record) { + $data_record = current($data); + foreach ($record as $k => $v) { + $this->assertEquals($data_record[$k], $v); + } + next($data); + } + $rs->close(); + + // iterator style usage + $rs = $DB->get_recordset($tablename); + $this->assertTrue($rs instanceof moodle_recordset); + reset($data); + while ($rs->valid()) { + $record = $rs->current(); + $data_record = current($data); + foreach ($record as $k => $v) { + $this->assertEquals($data_record[$k], $v); + } + next($data); + $rs->next(); + } + $rs->close(); + + // make sure rewind is ignored + $rs = $DB->get_recordset($tablename); + $this->assertTrue($rs instanceof moodle_recordset); + reset($data); + $i = 0; + foreach($rs as $record) { + $i++; + $rs->rewind(); + if ($i > 10) { + $this->fail('revind not ignored in recordsets'); + break; + } + $data_record = current($data); + foreach ($record as $k => $v) { + $this->assertEquals($data_record[$k], $v); + } + next($data); + } + $rs->close(); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $rs = $DB->get_recordset($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + + // notes: + // * limits are tested in test_get_recordset_sql() + // * where_clause() is used internally and is tested in test_get_records() + } + + public function test_get_recordset_iterator_keys() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, '0'); + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course')); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $data = array(array('id'=> 1, 'course' => 3, 'name' => 'record1'), + array('id'=> 2, 'course' => 3, 'name' => 'record2'), + array('id'=> 3, 'course' => 5, 'name' => 'record3')); + foreach ($data as $record) { + $DB->insert_record($tablename, $record); + } + + // Test repeated numeric keys are returned ok + $rs = $DB->get_recordset($tablename, NULL, NULL, 'course, name, id'); + + reset($data); + $count = 0; + foreach($rs as $key => $record) { + $data_record = current($data); + $this->assertEquals($data_record['course'], $key); + next($data); + $count++; + } + $rs->close(); + $this->assertEquals($count, 3); + + // Test string keys are returned ok + $rs = $DB->get_recordset($tablename, NULL, NULL, 'name, course, id'); + + reset($data); + $count = 0; + foreach($rs as $key => $record) { + $data_record = current($data); + $this->assertEquals($data_record['name'], $key); + next($data); + $count++; + } + $rs->close(); + $this->assertEquals($count, 3); + + // Test numeric not starting in 1 keys are returned ok + $rs = $DB->get_recordset($tablename, NULL, 'id DESC', 'id, course, name'); + + $data = array_reverse($data); + reset($data); + $count = 0; + foreach($rs as $key => $record) { + $data_record = current($data); + $this->assertEquals($data_record['id'], $key); + next($data); + $count++; + } + $rs->close(); + $this->assertEquals($count, 3); + } + + public function test_get_recordset_list() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course')); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 2)); + + $rs = $DB->get_recordset_list($tablename, 'course', array(3, 2)); + + $counter = 0; + foreach ($rs as $record) { + $counter++; + } + $this->assertEquals(3, $counter); + $rs->close(); + + $rs = $DB->get_recordset_list($tablename, 'course',array()); /// Must return 0 rows without conditions. MDL-17645 + + $counter = 0; + foreach ($rs as $record) { + $counter++; + } + $rs->close(); + $this->assertEquals(0, $counter); + + // notes: + // * limits are tested in test_get_recordset_sql() + // * where_clause() is used internally and is tested in test_get_records() + } + + public function test_get_recordset_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 2)); + + $rs = $DB->get_recordset_select($tablename, ''); + $counter = 0; + foreach ($rs as $record) { + $counter++; + } + $rs->close(); + $this->assertEquals(4, $counter); + + $this->assertNotEmpty($rs = $DB->get_recordset_select($tablename, 'course = 3')); + $counter = 0; + foreach ($rs as $record) { + $counter++; + } + $rs->close(); + $this->assertEquals(2, $counter); + + // notes: + // * limits are tested in test_get_recordset_sql() + } + + public function test_get_recordset_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $inskey1 = $DB->insert_record($tablename, array('course' => 3)); + $inskey2 = $DB->insert_record($tablename, array('course' => 5)); + $inskey3 = $DB->insert_record($tablename, array('course' => 4)); + $inskey4 = $DB->insert_record($tablename, array('course' => 3)); + $inskey5 = $DB->insert_record($tablename, array('course' => 2)); + $inskey6 = $DB->insert_record($tablename, array('course' => 1)); + $inskey7 = $DB->insert_record($tablename, array('course' => 0)); + + $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)); + $counter = 0; + foreach ($rs as $record) { + $counter++; + } + $rs->close(); + $this->assertEquals(2, $counter); + + // limits - only need to test this case, the rest have been tested by test_get_records_sql() + // only limitfrom = skips that number of records + $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0); + $records = array(); + foreach($rs as $key => $record) { + $records[$key] = $record; + } + $rs->close(); + $this->assertEquals(5, count($records)); + $this->assertEquals($inskey3, reset($records)->id); + $this->assertEquals($inskey7, end($records)->id); + + // note: fetching nulls, empties, LOBs already tested by test_insert_record() no needed here + } + + public function test_get_records() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 2)); + + // All records + $records = $DB->get_records($tablename); + $this->assertEquals(4, count($records)); + $this->assertEquals(3, $records[1]->course); + $this->assertEquals(3, $records[2]->course); + $this->assertEquals(5, $records[3]->course); + $this->assertEquals(2, $records[4]->course); + + // Records matching certain conditions + $records = $DB->get_records($tablename, array('course' => 3)); + $this->assertEquals(2, count($records)); + $this->assertEquals(3, $records[1]->course); + $this->assertEquals(3, $records[2]->course); + + // All records sorted by course + $records = $DB->get_records($tablename, null, 'course'); + $this->assertEquals(4, count($records)); + $current_record = reset($records); + $this->assertEquals(4, $current_record->id); + $current_record = next($records); + $this->assertEquals(1, $current_record->id); + $current_record = next($records); + $this->assertEquals(2, $current_record->id); + $current_record = next($records); + $this->assertEquals(3, $current_record->id); + + // All records, but get only one field + $records = $DB->get_records($tablename, null, '', 'id'); + $this->assertFalse(isset($records[1]->course)); + $this->assertTrue(isset($records[1]->id)); + $this->assertEquals(4, count($records)); + + // Booleans into params + $records = $DB->get_records($tablename, array('course' => true)); + $this->assertEquals(0, count($records)); + $records = $DB->get_records($tablename, array('course' => false)); + $this->assertEquals(0, count($records)); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $records = $DB->get_records($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + + // test get_records passing non-existing table + // with params + try { + $records = $DB->get_records('xxxx', array('id' => 0)); + $this->fail('An Exception is missing, expected due to query against non-existing table'); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + if (debugging()) { + // information for developers only, normal users get general error message + $this->assertEquals($e->errorcode, 'ddltablenotexist'); + } + } + // and without params + try { + $records = $DB->get_records('xxxx', array()); + $this->fail('An Exception is missing, expected due to query against non-existing table'); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + if (debugging()) { + // information for developers only, normal users get general error message + $this->assertEquals($e->errorcode, 'ddltablenotexist'); + } + } + + // test get_records passing non-existing column + try { + $records = $DB->get_records($tablename, array('xxxx' => 0)); + $this->fail('An Exception is missing, expected due to query against non-existing column'); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + if (debugging()) { + // information for developers only, normal users get general error message + $this->assertEquals($e->errorcode, 'ddlfieldnotexist'); + } + } + + // note: delegate limits testing to test_get_records_sql() + } + + public function test_get_records_list() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 2)); + + $records = $DB->get_records_list($tablename, 'course', array(3, 2)); + $this->assertTrue(is_array($records)); + $this->assertEquals(3, count($records)); + $this->assertEquals(1, reset($records)->id); + $this->assertEquals(2, next($records)->id); + $this->assertEquals(4, next($records)->id); + + $this->assertSame(array(), $records = $DB->get_records_list($tablename, 'course', array())); /// Must return 0 rows without conditions. MDL-17645 + $this->assertEquals(0, count($records)); + + // note: delegate limits testing to test_get_records_sql() + } + + public function test_get_records_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $inskey1 = $DB->insert_record($tablename, array('course' => 3)); + $inskey2 = $DB->insert_record($tablename, array('course' => 5)); + $inskey3 = $DB->insert_record($tablename, array('course' => 4)); + $inskey4 = $DB->insert_record($tablename, array('course' => 3)); + $inskey5 = $DB->insert_record($tablename, array('course' => 2)); + $inskey6 = $DB->insert_record($tablename, array('course' => 1)); + $inskey7 = $DB->insert_record($tablename, array('course' => 0)); + + $table2 = $this->get_test_table("2"); + $tablename2 = $table2->getName(); + $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table2->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null); + $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table2); + + $DB->insert_record($tablename2, array('course'=>3, 'nametext'=>'badabing')); + $DB->insert_record($tablename2, array('course'=>4, 'nametext'=>'badabang')); + $DB->insert_record($tablename2, array('course'=>5, 'nametext'=>'badabung')); + $DB->insert_record($tablename2, array('course'=>6, 'nametext'=>'badabong')); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)); + $this->assertEquals(2, count($records)); + $this->assertEquals($inskey1, reset($records)->id); + $this->assertEquals($inskey4, next($records)->id); + + // Awful test, requires debug enabled and sent to browser. Let's do that and restore after test + $this->enable_debugging(); + $records = $DB->get_records_sql("SELECT course AS id, course AS course FROM {{$tablename}}", null); + $this->assertFalse($this->get_debugging() === ''); + $this->assertEquals(6, count($records)); + + // negative limits = no limits + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, -1, -1); + $this->assertEquals(7, count($records)); + + // zero limits = no limits + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 0); + $this->assertEquals(7, count($records)); + + // only limitfrom = skips that number of records + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 2, 0); + $this->assertEquals(5, count($records)); + $this->assertEquals($inskey3, reset($records)->id); + $this->assertEquals($inskey7, end($records)->id); + + // only limitnum = fetches that number of records + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 0, 3); + $this->assertEquals(3, count($records)); + $this->assertEquals($inskey1, reset($records)->id); + $this->assertEquals($inskey3, end($records)->id); + + // both limitfrom and limitnum = skips limitfrom records and fetches limitnum ones + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} ORDER BY id", null, 3, 2); + $this->assertEquals(2, count($records)); + $this->assertEquals($inskey4, reset($records)->id); + $this->assertEquals($inskey5, end($records)->id); + + // both limitfrom and limitnum in query having subqueris + // note the subquery skips records with course = 0 and 3 + $sql = "SELECT * FROM {{$tablename}} + WHERE course NOT IN ( + SELECT course FROM {{$tablename}} + WHERE course IN (0, 3)) + ORDER BY course"; + $records = $DB->get_records_sql($sql, null, 0, 2); // Skip 0, get 2 + $this->assertEquals(2, count($records)); + $this->assertEquals($inskey6, reset($records)->id); + $this->assertEquals($inskey5, end($records)->id); + $records = $DB->get_records_sql($sql, null, 2, 2); // Skip 2, get 2 + $this->assertEquals(2, count($records)); + $this->assertEquals($inskey3, reset($records)->id); + $this->assertEquals($inskey2, end($records)->id); + + // test 2 tables with aliases and limits with order bys + $sql = "SELECT t1.id, t1.course AS cid, t2.nametext + FROM {{$tablename}} t1, {{$tablename2}} t2 + WHERE t2.course=t1.course + ORDER BY t1.course, ". $DB->sql_compare_text('t2.nametext'); + $records = $DB->get_records_sql($sql, null, 2, 2); // Skip courses 3 and 6, get 4 and 5 + $this->assertEquals(2, count($records)); + $this->assertEquals('5', end($records)->cid); + $this->assertEquals('4', reset($records)->cid); + + // test 2 tables with aliases and limits with the highest INT limit works + $records = $DB->get_records_sql($sql, null, 2, PHP_INT_MAX); // Skip course {3,6}, get {4,5} + $this->assertEquals(2, count($records)); + $this->assertEquals('5', end($records)->cid); + $this->assertEquals('4', reset($records)->cid); + + // test 2 tables with aliases and limits with order bys (limit which is highest INT number) + $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, 2); // Skip all courses + $this->assertEquals(0, count($records)); + + // test 2 tables with aliases and limits with order bys (limit which s highest INT number) + $records = $DB->get_records_sql($sql, null, PHP_INT_MAX, PHP_INT_MAX); // Skip all courses + $this->assertEquals(0, count($records)); + + // TODO: Test limits in queries having DISTINCT clauses + + // note: fetching nulls, empties, LOBs already tested by test_update_record() no needed here + } + + public function test_get_records_menu() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 2)); + + $records = $DB->get_records_menu($tablename, array('course' => 3)); + $this->assertTrue(is_array($records)); + $this->assertEquals(2, count($records)); + $this->assertFalse(empty($records[1])); + $this->assertFalse(empty($records[2])); + $this->assertEquals(3, $records[1]); + $this->assertEquals(3, $records[2]); + + // note: delegate limits testing to test_get_records_sql() + } + + public function test_get_records_select_menu() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + + $records = $DB->get_records_select_menu($tablename, "course > ?", array(2)); + $this->assertTrue(is_array($records)); + + $this->assertEquals(3, count($records)); + $this->assertFalse(empty($records[1])); + $this->assertTrue(empty($records[2])); + $this->assertFalse(empty($records[3])); + $this->assertFalse(empty($records[4])); + $this->assertEquals(3, $records[1]); + $this->assertEquals(3, $records[3]); + $this->assertEquals(5, $records[4]); + + // note: delegate limits testing to test_get_records_sql() + } + + public function test_get_records_sql_menu() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + + $records = $DB->get_records_sql_menu("SELECT * FROM {{$tablename}} WHERE course > ?", array(2)); + $this->assertTrue(is_array($records)); + + $this->assertEquals(3, count($records)); + $this->assertFalse(empty($records[1])); + $this->assertTrue(empty($records[2])); + $this->assertFalse(empty($records[3])); + $this->assertFalse(empty($records[4])); + $this->assertEquals(3, $records[1]); + $this->assertEquals(3, $records[3]); + $this->assertEquals(5, $records[4]); + + // note: delegate limits testing to test_get_records_sql() + } + + public function test_get_record() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + + $record = $DB->get_record($tablename, array('id' => 2)); + $this->assertTrue($record instanceof stdClass); + + $this->assertEquals(2, $record->course); + $this->assertEquals(2, $record->id); + } + + + public function test_get_record_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + + $record = $DB->get_record_select($tablename, "id = ?", array(2)); + $this->assertTrue($record instanceof stdClass); + + $this->assertEquals(2, $record->course); + + // note: delegates limit testing to test_get_records_sql() + } + + public function test_get_record_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + + // standard use + $record = $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(2)); + $this->assertTrue($record instanceof stdClass); + $this->assertEquals(2, $record->course); + $this->assertEquals(2, $record->id); + + // backwards compatibility with $ignoremultiple + $this->assertFalse((bool)IGNORE_MISSING); + $this->assertTrue((bool)IGNORE_MULTIPLE); + + // record not found - ignore + $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MISSING)); + $this->assertFalse($DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), IGNORE_MULTIPLE)); + + // record not found error + try { + $DB->get_record_sql("SELECT * FROM {{$tablename}} WHERE id = ?", array(666), MUST_EXIST); + $this->fail("Exception expected"); + } catch (dml_missing_record_exception $e) { + $this->assertTrue(true); + } + + $this->enable_debugging(); + $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MISSING)); + $this->assertFalse($this->get_debugging() === ''); + + // multiple matches ignored + $this->assertNotEmpty($DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), IGNORE_MULTIPLE)); + + // multiple found error + try { + $DB->get_record_sql("SELECT * FROM {{$tablename}}", array(), MUST_EXIST); + $this->fail("Exception expected"); + } catch (dml_multiple_records_exception $e) { + $this->assertTrue(true); + } + } + + public function test_get_field() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $id1 = $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 5)); + + $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id1))); + $this->assertEquals(3, $DB->get_field($tablename, 'course', array('course' => 3))); + + $this->assertSame(false, $DB->get_field($tablename, 'course', array('course' => 11), IGNORE_MISSING)); + try { + $DB->get_field($tablename, 'course', array('course' => 4), MUST_EXIST); + $this->assertFail('Exception expected due to missing record'); + } catch (dml_exception $ex) { + $this->assertTrue(true); + } + + $this->enable_debugging(); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MULTIPLE)); + $this->assertSame($this->get_debugging(), ''); + + $this->enable_debugging(); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('course' => 5), IGNORE_MISSING)); + $this->assertFalse($this->get_debugging() === ''); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $DB->get_field($tablename, 'course', $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + } + + public function test_get_field_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertEquals(3, $DB->get_field_select($tablename, 'course', "id = ?", array(1))); + } + + public function test_get_field_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertEquals(3, $DB->get_field_sql("SELECT course FROM {{$tablename}} WHERE id = ?", array(1))); + } + + public function test_get_fieldset_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 6)); + + $fieldset = $DB->get_fieldset_select($tablename, 'course', "course > ?", array(1)); + $this->assertTrue(is_array($fieldset)); + + $this->assertEquals(3, count($fieldset)); + $this->assertEquals(3, $fieldset[0]); + $this->assertEquals(2, $fieldset[1]); + $this->assertEquals(6, $fieldset[2]); + } + + public function test_get_fieldset_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 6)); + + $fieldset = $DB->get_fieldset_sql("SELECT * FROM {{$tablename}} WHERE course > ?", array(1)); + $this->assertTrue(is_array($fieldset)); + + $this->assertEquals(3, count($fieldset)); + $this->assertEquals(2, $fieldset[0]); + $this->assertEquals(3, $fieldset[1]); + $this->assertEquals(4, $fieldset[2]); + } + + public function test_insert_record_raw() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $record = (object)array('course' => 1, 'onechar' => 'xx'); + $before = clone($record); + $result = $DB->insert_record_raw($tablename, $record); + $this->assertSame(1, $result); + $this->assertEquals($record, $before); + + $record = $DB->get_record($tablename, array('course' => 1)); + $this->assertTrue($record instanceof stdClass); + $this->assertSame('xx', $record->onechar); + + $result = $DB->insert_record_raw($tablename, array('course' => 2, 'onechar' => 'yy'), false); + $this->assertSame(true, $result); + + // note: bulk not implemented yet + $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'zz'), true, true); + $record = $DB->get_record($tablename, array('course' => 3)); + $this->assertTrue($record instanceof stdClass); + $this->assertSame('zz', $record->onechar); + + // custom sequence (id) - returnid is ignored + $result = $DB->insert_record_raw($tablename, array('id' => 10, 'course' => 3, 'onechar' => 'bb'), true, false, true); + $this->assertSame(true, $result); + $record = $DB->get_record($tablename, array('id' => 10)); + $this->assertTrue($record instanceof stdClass); + $this->assertSame('bb', $record->onechar); + + // custom sequence - missing id error + try { + $DB->insert_record_raw($tablename, array('course' => 3, 'onechar' => 'bb'), true, false, true); + $this->assertFail('Exception expected due to missing record'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // wrong column error + try { + $DB->insert_record_raw($tablename, array('xxxxx' => 3, 'onechar' => 'bb')); + $this->assertFail('Exception expected due to invalid column'); + } catch (dml_exception $ex) { + $this->assertTrue(true); + } + } + + public function test_insert_record() { + // All the information in this test is fetched from DB by get_recordset() so we + // have such method properly tested against nulls, empties and friends... + + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, 100); + $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true)); + $record = $DB->get_record($tablename, array('course' => 1)); + $this->assertEquals(1, $record->id); + $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied + $this->assertEquals(200, $record->onenum); + $this->assertSame('onestring', $record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // without returning id, bulk not implemented + $result = $this->assertSame(true, $DB->insert_record($tablename, array('course' => 99), false, true)); + $record = $DB->get_record($tablename, array('course' => 99)); + $this->assertEquals(2, $record->id); + $this->assertEquals(99, $record->course); + + // Check nulls are set properly for all types + $record = new stdClass(); + $record->oneint = null; + $record->onenum = null; + $record->onechar = null; + $record->onetext = null; + $record->onebinary = null; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals(0, $record->course); + $this->assertNull($record->oneint); + $this->assertNull($record->onenum); + $this->assertNull($record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // Check zeros are set properly for all types + $record = new stdClass(); + $record->oneint = 0; + $record->onenum = 0; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals(0, $record->oneint); + $this->assertEquals(0, $record->onenum); + + // Check booleans are set properly for all types + $record = new stdClass(); + $record->oneint = true; // trues + $record->onenum = true; + $record->onechar = true; + $record->onetext = true; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals(1, $record->oneint); + $this->assertEquals(1, $record->onenum); + $this->assertEquals(1, $record->onechar); + $this->assertEquals(1, $record->onetext); + + $record = new stdClass(); + $record->oneint = false; // falses + $record->onenum = false; + $record->onechar = false; + $record->onetext = false; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals(0, $record->oneint); + $this->assertEquals(0, $record->onenum); + $this->assertEquals(0, $record->onechar); + $this->assertEquals(0, $record->onetext); + + // Check string data causes exception in numeric types + $record = new stdClass(); + $record->oneint = 'onestring'; + $record->onenum = 0; + try { + $DB->insert_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + $record = new stdClass(); + $record->oneint = 0; + $record->onenum = 'onestring'; + try { + $DB->insert_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Check empty string data is stored as 0 in numeric datatypes + $record = new stdClass(); + $record->oneint = ''; // empty string + $record->onenum = 0; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0); + + $record = new stdClass(); + $record->oneint = 0; + $record->onenum = ''; // empty string + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0); + + // Check empty strings are set properly in string types + $record = new stdClass(); + $record->oneint = 0; + $record->onenum = 0; + $record->onechar = ''; + $record->onetext = ''; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertTrue($record->onechar === ''); + $this->assertTrue($record->onetext === ''); + + // Check operation ((210.10 + 39.92) - 150.02) against numeric types + $record = new stdClass(); + $record->oneint = ((210.10 + 39.92) - 150.02); + $record->onenum = ((210.10 + 39.92) - 150.02); + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals(100, $record->oneint); + $this->assertEquals(100, $record->onenum); + + // Check various quotes/backslashes combinations in string types + $teststrings = array( + 'backslashes and quotes alone (even): "" \'\' \\\\', + 'backslashes and quotes alone (odd): """ \'\'\' \\\\\\', + 'backslashes and quotes sequences (even): \\"\\" \\\'\\\'', + 'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\''); + foreach ($teststrings as $teststring) { + $record = new stdClass(); + $record->onechar = $teststring; + $record->onetext = $teststring; + $recid = $DB->insert_record($tablename, $record); + $record = $DB->get_record($tablename, array('id' => $recid)); + $this->assertEquals($teststring, $record->onechar); + $this->assertEquals($teststring, $record->onetext); + } + + // Check LOBs in text/binary columns + $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt'); + $blob = file_get_contents(__DIR__ . '/fixtures/randombinary'); + $record = new stdClass(); + $record->onetext = $clob; + $record->onebinary = $blob; + $recid = $DB->insert_record($tablename, $record); + $rs = $DB->get_recordset($tablename, array('id' => $recid)); + $record = $rs->current(); + $rs->close(); + $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)'); + $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)'); + + // And "small" LOBs too, just in case + $newclob = substr($clob, 0, 500); + $newblob = substr($blob, 0, 250); + $record = new stdClass(); + $record->onetext = $newclob; + $record->onebinary = $newblob; + $recid = $DB->insert_record($tablename, $record); + $rs = $DB->get_recordset($tablename, array('id' => $recid)); + $record = $rs->current(); + $rs->close(); + $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)'); + $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)'); + $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing + + // And "diagnostic" LOBs too, just in case + $newclob = '\'"\\;/ěščřžýáíé'; + $newblob = '\'"\\;/ěščřžýáíé'; + $record = new stdClass(); + $record->onetext = $newclob; + $record->onebinary = $newblob; + $recid = $DB->insert_record($tablename, $record); + $rs = $DB->get_recordset($tablename, array('id' => $recid)); + $record = $rs->current(); + $rs->close(); + $this->assertSame($newclob, $record->onetext); + $this->assertSame($newblob, $record->onebinary); + $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing + + // test data is not modified + $record = new stdClass(); + $record->id = -1; // has to be ignored + $record->course = 3; + $record->lalala = 'lalal'; // unused + $before = clone($record); + $DB->insert_record($tablename, $record); + $this->assertEquals($record, $before); + + // make sure the id is always increasing and never reuses the same id + $id1 = $DB->insert_record($tablename, array('course' => 3)); + $id2 = $DB->insert_record($tablename, array('course' => 3)); + $this->assertTrue($id1 < $id2); + $DB->delete_records($tablename, array('id'=>$id2)); + $id3 = $DB->insert_record($tablename, array('course' => 3)); + $this->assertTrue($id2 < $id3); + $DB->delete_records($tablename, array()); + $id4 = $DB->insert_record($tablename, array('course' => 3)); + $this->assertTrue($id3 < $id4); + + // Test saving a float in a CHAR column, and reading it back. + $id = $DB->insert_record($tablename, array('onechar' => 1.0)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onechar' => 1e20)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onechar' => 1e-4)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onechar' => 1e-5)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onechar' => 1e-300)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onechar' => 1e300)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + + // Test saving a float in a TEXT column, and reading it back. + $id = $DB->insert_record($tablename, array('onetext' => 1.0)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onetext' => 1e20)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onetext' => 1e-4)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onetext' => 1e-5)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onetext' => 1e-300)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $id = $DB->insert_record($tablename, array('onetext' => 1e300)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + } + + public function test_import_record() { + // All the information in this test is fetched from DB by get_recordset() so we + // have such method properly tested against nulls, empties and friends... + + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, 100); + $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertSame(1, $DB->insert_record($tablename, array('course' => 1), true)); + $record = $DB->get_record($tablename, array('course' => 1)); + $this->assertEquals(1, $record->id); + $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied + $this->assertEquals(200, $record->onenum); + $this->assertSame('onestring', $record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // ignore extra columns + $record = (object)array('id'=>13, 'course'=>2, 'xxxx'=>788778); + $before = clone($record); + $this->assertSame(true, $DB->import_record($tablename, $record)); + $this->assertEquals($record, $before); + $records = $DB->get_records($tablename); + $this->assertEquals(2, $records[13]->course); + + // Check nulls are set properly for all types + $record = new stdClass(); + $record->id = 20; + $record->oneint = null; + $record->onenum = null; + $record->onechar = null; + $record->onetext = null; + $record->onebinary = null; + $this->assertTrue($DB->import_record($tablename, $record)); + $record = $DB->get_record($tablename, array('id' => 20)); + $this->assertEquals(0, $record->course); + $this->assertNull($record->oneint); + $this->assertNull($record->onenum); + $this->assertNull($record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // Check zeros are set properly for all types + $record = new stdClass(); + $record->id = 23; + $record->oneint = 0; + $record->onenum = 0; + $this->assertTrue($DB->import_record($tablename, $record)); + $record = $DB->get_record($tablename, array('id' => 23)); + $this->assertEquals(0, $record->oneint); + $this->assertEquals(0, $record->onenum); + + // Check string data causes exception in numeric types + $record = new stdClass(); + $record->id = 32; + $record->oneint = 'onestring'; + $record->onenum = 0; + try { + $DB->import_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + $record = new stdClass(); + $record->id = 35; + $record->oneint = 0; + $record->onenum = 'onestring'; + try { + $DB->import_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Check empty strings are set properly in string types + $record = new stdClass(); + $record->id = 44; + $record->oneint = 0; + $record->onenum = 0; + $record->onechar = ''; + $record->onetext = ''; + $this->assertTrue($DB->import_record($tablename, $record)); + $record = $DB->get_record($tablename, array('id' => 44)); + $this->assertTrue($record->onechar === ''); + $this->assertTrue($record->onetext === ''); + + // Check operation ((210.10 + 39.92) - 150.02) against numeric types + $record = new stdClass(); + $record->id = 47; + $record->oneint = ((210.10 + 39.92) - 150.02); + $record->onenum = ((210.10 + 39.92) - 150.02); + $this->assertTrue($DB->import_record($tablename, $record)); + $record = $DB->get_record($tablename, array('id' => 47)); + $this->assertEquals(100, $record->oneint); + $this->assertEquals(100, $record->onenum); + + // Check various quotes/backslashes combinations in string types + $i = 50; + $teststrings = array( + 'backslashes and quotes alone (even): "" \'\' \\\\', + 'backslashes and quotes alone (odd): """ \'\'\' \\\\\\', + 'backslashes and quotes sequences (even): \\"\\" \\\'\\\'', + 'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\''); + foreach ($teststrings as $teststring) { + $record = new stdClass(); + $record->id = $i; + $record->onechar = $teststring; + $record->onetext = $teststring; + $this->assertTrue($DB->import_record($tablename, $record)); + $record = $DB->get_record($tablename, array('id' => $i)); + $this->assertEquals($teststring, $record->onechar); + $this->assertEquals($teststring, $record->onetext); + $i = $i + 3; + } + + // Check LOBs in text/binary columns + $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt'); + $record = new stdClass(); + $record->id = 70; + $record->onetext = $clob; + $record->onebinary = ''; + $this->assertTrue($DB->import_record($tablename, $record)); + $rs = $DB->get_recordset($tablename, array('id' => 70)); + $record = $rs->current(); + $rs->close(); + $this->assertEquals($clob, $record->onetext, 'Test CLOB insert (full contents output disabled)'); + + $blob = file_get_contents(__DIR__ . '/fixtures/randombinary'); + $record = new stdClass(); + $record->id = 71; + $record->onetext = ''; + $record->onebinary = $blob; + $this->assertTrue($DB->import_record($tablename, $record)); + $rs = $DB->get_recordset($tablename, array('id' => 71)); + $record = $rs->current(); + $rs->close(); + $this->assertEquals($blob, $record->onebinary, 'Test BLOB insert (full contents output disabled)'); + + // And "small" LOBs too, just in case + $newclob = substr($clob, 0, 500); + $newblob = substr($blob, 0, 250); + $record = new stdClass(); + $record->id = 73; + $record->onetext = $newclob; + $record->onebinary = $newblob; + $this->assertTrue($DB->import_record($tablename, $record)); + $rs = $DB->get_recordset($tablename, array('id' => 73)); + $record = $rs->current(); + $rs->close(); + $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB insert (full contents output disabled)'); + $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB insert (full contents output disabled)'); + $this->assertEquals(false, $rs->key()); // Ensure recordset key() method to be working ok after closing + } + + public function test_update_record_raw() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 3)); + + $record = $DB->get_record($tablename, array('course' => 1)); + $record->course = 2; + $this->assertTrue($DB->update_record_raw($tablename, $record)); + $this->assertEquals(0, $DB->count_records($tablename, array('course' => 1))); + $this->assertEquals(1, $DB->count_records($tablename, array('course' => 2))); + $this->assertEquals(1, $DB->count_records($tablename, array('course' => 3))); + + $record = $DB->get_record($tablename, array('course' => 3)); + $record->xxxxx = 2; + try { + $DB->update_record_raw($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof moodle_exception); + } + + $record = $DB->get_record($tablename, array('course' => 3)); + unset($record->id); + try { + $DB->update_record_raw($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + public function test_update_record() { + + // All the information in this test is fetched from DB by get_record() so we + // have such method properly tested against nulls, empties and friends... + + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, 100); + $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $record = $DB->get_record($tablename, array('course' => 1)); + $record->course = 2; + + $this->assertTrue($DB->update_record($tablename, $record)); + $this->assertFalse($record = $DB->get_record($tablename, array('course' => 1))); + $this->assertNotEmpty($record = $DB->get_record($tablename, array('course' => 2))); + $this->assertEquals(100, $record->oneint); // Just check column defaults have been applied + $this->assertEquals(200, $record->onenum); + $this->assertEquals('onestring', $record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // Check nulls are set properly for all types + $record->oneint = null; + $record->onenum = null; + $record->onechar = null; + $record->onetext = null; + $record->onebinary = null; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertNull($record->oneint); + $this->assertNull($record->onenum); + $this->assertNull($record->onechar); + $this->assertNull($record->onetext); + $this->assertNull($record->onebinary); + + // Check zeros are set properly for all types + $record->oneint = 0; + $record->onenum = 0; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals(0, $record->oneint); + $this->assertEquals(0, $record->onenum); + + // Check booleans are set properly for all types + $record->oneint = true; // trues + $record->onenum = true; + $record->onechar = true; + $record->onetext = true; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals(1, $record->oneint); + $this->assertEquals(1, $record->onenum); + $this->assertEquals(1, $record->onechar); + $this->assertEquals(1, $record->onetext); + + $record->oneint = false; // falses + $record->onenum = false; + $record->onechar = false; + $record->onetext = false; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals(0, $record->oneint); + $this->assertEquals(0, $record->onenum); + $this->assertEquals(0, $record->onechar); + $this->assertEquals(0, $record->onetext); + + // Check string data causes exception in numeric types + $record->oneint = 'onestring'; + $record->onenum = 0; + try { + $DB->update_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + $record->oneint = 0; + $record->onenum = 'onestring'; + try { + $DB->update_record($tablename, $record); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Check empty string data is stored as 0 in numeric datatypes + $record->oneint = ''; // empty string + $record->onenum = 0; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertTrue(is_numeric($record->oneint) && $record->oneint == 0); + + $record->oneint = 0; + $record->onenum = ''; // empty string + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertTrue(is_numeric($record->onenum) && $record->onenum == 0); + + // Check empty strings are set properly in string types + $record->oneint = 0; + $record->onenum = 0; + $record->onechar = ''; + $record->onetext = ''; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertTrue($record->onechar === ''); + $this->assertTrue($record->onetext === ''); + + // Check operation ((210.10 + 39.92) - 150.02) against numeric types + $record->oneint = ((210.10 + 39.92) - 150.02); + $record->onenum = ((210.10 + 39.92) - 150.02); + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals(100, $record->oneint); + $this->assertEquals(100, $record->onenum); + + // Check various quotes/backslashes combinations in string types + $teststrings = array( + 'backslashes and quotes alone (even): "" \'\' \\\\', + 'backslashes and quotes alone (odd): """ \'\'\' \\\\\\', + 'backslashes and quotes sequences (even): \\"\\" \\\'\\\'', + 'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\''); + foreach ($teststrings as $teststring) { + $record->onechar = $teststring; + $record->onetext = $teststring; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals($teststring, $record->onechar); + $this->assertEquals($teststring, $record->onetext); + } + + // Check LOBs in text/binary columns + $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt'); + $blob = file_get_contents(__DIR__ . '/fixtures/randombinary'); + $record->onetext = $clob; + $record->onebinary = $blob; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals($clob, $record->onetext, 'Test CLOB update (full contents output disabled)'); + $this->assertEquals($blob, $record->onebinary, 'Test BLOB update (full contents output disabled)'); + + // And "small" LOBs too, just in case + $newclob = substr($clob, 0, 500); + $newblob = substr($blob, 0, 250); + $record->onetext = $newclob; + $record->onebinary = $newblob; + $DB->update_record($tablename, $record); + $record = $DB->get_record($tablename, array('course' => 2)); + $this->assertEquals($newclob, $record->onetext, 'Test "small" CLOB update (full contents output disabled)'); + $this->assertEquals($newblob, $record->onebinary, 'Test "small" BLOB update (full contents output disabled)'); + + // Test saving a float in a CHAR column, and reading it back. + $id = $DB->insert_record($tablename, array('onechar' => 'X')); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1.0)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e20)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-4)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-5)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e-300)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onechar' => 1e300)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + + // Test saving a float in a TEXT column, and reading it back. + $id = $DB->insert_record($tablename, array('onetext' => 'X')); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1.0)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e20)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-4)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-5)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e-300)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->update_record($tablename, array('id' => $id, 'onetext' => 1e300)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + } + + public function test_set_field() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + // simple set_field + $id1 = $DB->insert_record($tablename, array('course' => 1)); + $id2 = $DB->insert_record($tablename, array('course' => 1)); + $id3 = $DB->insert_record($tablename, array('course' => 3)); + $this->assertTrue($DB->set_field($tablename, 'course', 2, array('id' => $id1))); + $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => $id1))); + $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2))); + $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3))); + $DB->delete_records($tablename, array()); + + // multiple fields affected + $id1 = $DB->insert_record($tablename, array('course' => 1)); + $id2 = $DB->insert_record($tablename, array('course' => 1)); + $id3 = $DB->insert_record($tablename, array('course' => 3)); + $DB->set_field($tablename, 'course', '5', array('course' => 1)); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1))); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2))); + $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3))); + $DB->delete_records($tablename, array()); + + // no field affected + $id1 = $DB->insert_record($tablename, array('course' => 1)); + $id2 = $DB->insert_record($tablename, array('course' => 1)); + $id3 = $DB->insert_record($tablename, array('course' => 3)); + $DB->set_field($tablename, 'course', '5', array('course' => 0)); + $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id1))); + $this->assertEquals(1, $DB->get_field($tablename, 'course', array('id' => $id2))); + $this->assertEquals(3, $DB->get_field($tablename, 'course', array('id' => $id3))); + $DB->delete_records($tablename, array()); + + // all fields - no condition + $id1 = $DB->insert_record($tablename, array('course' => 1)); + $id2 = $DB->insert_record($tablename, array('course' => 1)); + $id3 = $DB->insert_record($tablename, array('course' => 3)); + $DB->set_field($tablename, 'course', 5, array()); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id1))); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id2))); + $this->assertEquals(5, $DB->get_field($tablename, 'course', array('id' => $id3))); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $DB->set_field($tablename, 'onechar', 'frog', $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + + // Test saving a float in a CHAR column, and reading it back. + $id = $DB->insert_record($tablename, array('onechar' => 'X')); + $DB->set_field($tablename, 'onechar', 1.0, array('id' => $id)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->set_field($tablename, 'onechar', 1e20, array('id' => $id)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->set_field($tablename, 'onechar', 1e-4, array('id' => $id)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->set_field($tablename, 'onechar', 1e-5, array('id' => $id)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->set_field($tablename, 'onechar', 1e-300, array('id' => $id)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + $DB->set_field($tablename, 'onechar', 1e300, array('id' => $id)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onechar', array('id' => $id))); + + // Test saving a float in a TEXT column, and reading it back. + $id = $DB->insert_record($tablename, array('onetext' => 'X')); + $DB->set_field($tablename, 'onetext', 1.0, array('id' => $id)); + $this->assertEquals(1.0, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->set_field($tablename, 'onetext', 1e20, array('id' => $id)); + $this->assertEquals(1e20, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->set_field($tablename, 'onetext', 1e-4, array('id' => $id)); + $this->assertEquals(1e-4, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->set_field($tablename, 'onetext', 1e-5, array('id' => $id)); + $this->assertEquals(1e-5, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->set_field($tablename, 'onetext', 1e-300, array('id' => $id)); + $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + $DB->set_field($tablename, 'onetext', 1e300, array('id' => $id)); + $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id))); + + // Note: All the nulls, booleans, empties, quoted and backslashes tests + // go to set_field_select() because set_field() is just one wrapper over it + } + + public function test_set_field_select() { + + // All the information in this test is fetched from DB by get_field() so we + // have such method properly tested against nulls, empties and friends... + + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null); + $table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null); + $table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_field('onebinary', XMLDB_TYPE_BINARY, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + + $this->assertTrue($DB->set_field_select($tablename, 'course', 2, 'id = ?', array(1))); + $this->assertEquals(2, $DB->get_field($tablename, 'course', array('id' => 1))); + + // Check nulls are set properly for all types + $DB->set_field_select($tablename, 'oneint', null, 'id = ?', array(1)); // trues + $DB->set_field_select($tablename, 'onenum', null, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onechar', null, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onetext', null, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onebinary', null, 'id = ?', array(1)); + $this->assertNull($DB->get_field($tablename, 'oneint', array('id' => 1))); + $this->assertNull($DB->get_field($tablename, 'onenum', array('id' => 1))); + $this->assertNull($DB->get_field($tablename, 'onechar', array('id' => 1))); + $this->assertNull($DB->get_field($tablename, 'onetext', array('id' => 1))); + $this->assertNull($DB->get_field($tablename, 'onebinary', array('id' => 1))); + + // Check zeros are set properly for all types + $DB->set_field_select($tablename, 'oneint', 0, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onenum', 0, 'id = ?', array(1)); + $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1))); + $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1))); + + // Check booleans are set properly for all types + $DB->set_field_select($tablename, 'oneint', true, 'id = ?', array(1)); // trues + $DB->set_field_select($tablename, 'onenum', true, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onechar', true, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onetext', true, 'id = ?', array(1)); + $this->assertEquals(1, $DB->get_field($tablename, 'oneint', array('id' => 1))); + $this->assertEquals(1, $DB->get_field($tablename, 'onenum', array('id' => 1))); + $this->assertEquals(1, $DB->get_field($tablename, 'onechar', array('id' => 1))); + $this->assertEquals(1, $DB->get_field($tablename, 'onetext', array('id' => 1))); + + $DB->set_field_select($tablename, 'oneint', false, 'id = ?', array(1)); // falses + $DB->set_field_select($tablename, 'onenum', false, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onechar', false, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onetext', false, 'id = ?', array(1)); + $this->assertEquals(0, $DB->get_field($tablename, 'oneint', array('id' => 1))); + $this->assertEquals(0, $DB->get_field($tablename, 'onenum', array('id' => 1))); + $this->assertEquals(0, $DB->get_field($tablename, 'onechar', array('id' => 1))); + $this->assertEquals(0, $DB->get_field($tablename, 'onetext', array('id' => 1))); + + // Check string data causes exception in numeric types + try { + $DB->set_field_select($tablename, 'oneint', 'onestring', 'id = ?', array(1)); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + try { + $DB->set_field_select($tablename, 'onenum', 'onestring', 'id = ?', array(1)); + $this->fail("Expecting an exception, none occurred"); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + } + + // Check empty string data is stored as 0 in numeric datatypes + $DB->set_field_select($tablename, 'oneint', '', 'id = ?', array(1)); + $field = $DB->get_field($tablename, 'oneint', array('id' => 1)); + $this->assertTrue(is_numeric($field) && $field == 0); + + $DB->set_field_select($tablename, 'onenum', '', 'id = ?', array(1)); + $field = $DB->get_field($tablename, 'onenum', array('id' => 1)); + $this->assertTrue(is_numeric($field) && $field == 0); + + // Check empty strings are set properly in string types + $DB->set_field_select($tablename, 'onechar', '', 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onetext', '', 'id = ?', array(1)); + $this->assertTrue($DB->get_field($tablename, 'onechar', array('id' => 1)) === ''); + $this->assertTrue($DB->get_field($tablename, 'onetext', array('id' => 1)) === ''); + + // Check operation ((210.10 + 39.92) - 150.02) against numeric types + $DB->set_field_select($tablename, 'oneint', ((210.10 + 39.92) - 150.02), 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onenum', ((210.10 + 39.92) - 150.02), 'id = ?', array(1)); + $this->assertEquals(100, $DB->get_field($tablename, 'oneint', array('id' => 1))); + $this->assertEquals(100, $DB->get_field($tablename, 'onenum', array('id' => 1))); + + // Check various quotes/backslashes combinations in string types + $teststrings = array( + 'backslashes and quotes alone (even): "" \'\' \\\\', + 'backslashes and quotes alone (odd): """ \'\'\' \\\\\\', + 'backslashes and quotes sequences (even): \\"\\" \\\'\\\'', + 'backslashes and quotes sequences (odd): \\"\\"\\" \\\'\\\'\\\''); + foreach ($teststrings as $teststring) { + $DB->set_field_select($tablename, 'onechar', $teststring, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onetext', $teststring, 'id = ?', array(1)); + $this->assertEquals($teststring, $DB->get_field($tablename, 'onechar', array('id' => 1))); + $this->assertEquals($teststring, $DB->get_field($tablename, 'onetext', array('id' => 1))); + } + + // Check LOBs in text/binary columns + $clob = file_get_contents(__DIR__ . '/fixtures/clob.txt'); + $blob = file_get_contents(__DIR__ . '/fixtures/randombinary'); + $DB->set_field_select($tablename, 'onetext', $clob, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onebinary', $blob, 'id = ?', array(1)); + $this->assertEquals($clob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test CLOB set_field (full contents output disabled)'); + $this->assertEquals($blob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test BLOB set_field (full contents output disabled)'); + + // And "small" LOBs too, just in case + $newclob = substr($clob, 0, 500); + $newblob = substr($blob, 0, 250); + $DB->set_field_select($tablename, 'onetext', $newclob, 'id = ?', array(1)); + $DB->set_field_select($tablename, 'onebinary', $newblob, 'id = ?', array(1)); + $this->assertEquals($newclob, $DB->get_field($tablename, 'onetext', array('id' => 1)), 'Test "small" CLOB set_field (full contents output disabled)'); + $this->assertEquals($newblob, $DB->get_field($tablename, 'onebinary', array('id' => 1)), 'Test "small" BLOB set_field (full contents output disabled)'); + + // This is the failure from MDL-24863. This was giving an error on MSSQL, + // which converts the '1' to an integer, which cannot then be compared with + // onetext cast to a varchar. This should be fixed and working now. + $newchar = 'frog'; + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $params = array('onetext' => '1'); + try { + $DB->set_field_select($tablename, 'onechar', $newchar, $DB->sql_compare_text('onetext') . ' = ?', $params); + $this->assertTrue(true, 'No exceptions thrown with numerical text param comparison for text field.'); + } catch (dml_exception $e) { + $this->assertFalse(true, 'We have an unexpected exception.'); + throw $e; + } + + + } + + public function test_count_records() { + $DB = $this->tdb; + + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 4)); + $DB->insert_record($tablename, array('course' => 5)); + + $this->assertEquals(3, $DB->count_records($tablename)); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $DB->count_records($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + } + + public function test_count_records_select() { + $DB = $this->tdb; + + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 4)); + $DB->insert_record($tablename, array('course' => 5)); + + $this->assertEquals(2, $DB->count_records_select($tablename, 'course > ?', array(3))); + } + + public function test_count_records_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 4)); + $DB->insert_record($tablename, array('course' => 5)); + + $this->assertEquals(2, $DB->count_records_sql("SELECT COUNT(*) FROM {{$tablename}} WHERE course > ?", array(3))); + } + + public function test_record_exists() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $this->assertFalse($DB->record_exists($tablename, array('course' => 3))); + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertTrue($DB->record_exists($tablename, array('course' => 3))); + + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => '1'); + try { + $DB->record_exists($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + } + + public function test_record_exists_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $this->assertFalse($DB->record_exists_select($tablename, "course = ?", array(3))); + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertTrue($DB->record_exists_select($tablename, "course = ?", array(3))); + } + + public function test_record_exists_sql() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertEquals(0, $DB->count_records($tablename)); + + $this->assertFalse($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3))); + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertTrue($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3))); + } + + public function test_recordset_locks_delete() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + //Setup + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 4)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 6)); + + // Test against db write locking while on an open recordset + $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4} + foreach ($rs as $record) { + $cid = $record->course; + $DB->delete_records($tablename, array('course' => $cid)); + $this->assertFalse($DB->record_exists($tablename, array('course' => $cid))); + } + $rs->close(); + + $this->assertEquals(4, $DB->count_records($tablename, array())); + } + + public function test_recordset_locks_update() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + //Setup + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 4)); + $DB->insert_record($tablename, array('course' => 5)); + $DB->insert_record($tablename, array('course' => 6)); + + // Test against db write locking while on an open recordset + $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4} + foreach ($rs as $record) { + $cid = $record->course; + $DB->set_field($tablename, 'course', 10, array('course' => $cid)); + $this->assertFalse($DB->record_exists($tablename, array('course' => $cid))); + } + $rs->close(); + + $this->assertEquals(2, $DB->count_records($tablename, array('course' => 10))); + } + + public function test_delete_records() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 2)); + + // Delete all records + $this->assertTrue($DB->delete_records($tablename)); + $this->assertEquals(0, $DB->count_records($tablename)); + + // Delete subset of records + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 2)); + + $this->assertTrue($DB->delete_records($tablename, array('course' => 2))); + $this->assertEquals(1, $DB->count_records($tablename)); + + // delete all + $this->assertTrue($DB->delete_records($tablename, array())); + $this->assertEquals(0, $DB->count_records($tablename)); + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext'=>'1'); + try { + $DB->delete_records($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + + // test for exception throwing on text conditions being compared. (MDL-24863, unwanted auto conversion of param to int) + $conditions = array('onetext' => 1); + try { + $DB->delete_records($tablename, $conditions); + if (debugging()) { + // only in debug mode - hopefully all devs test code in debug mode... + $this->fail('An Exception is missing, expected due to equating of text fields'); + } + } catch (exception $e) { + $this->assertTrue($e instanceof dml_exception); + $this->assertEquals($e->errorcode, 'textconditionsnotallowed'); + } + } + + public function test_delete_records_select() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 2)); + + $this->assertTrue($DB->delete_records_select($tablename, 'course = ?', array(2))); + $this->assertEquals(1, $DB->count_records($tablename)); + } + + public function test_delete_records_list() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 1)); + $DB->insert_record($tablename, array('course' => 2)); + $DB->insert_record($tablename, array('course' => 3)); + + $this->assertTrue($DB->delete_records_list($tablename, 'course', array(2, 3))); + $this->assertEquals(1, $DB->count_records($tablename)); + + $this->assertTrue($DB->delete_records_list($tablename, 'course', array())); /// Must delete 0 rows without conditions. MDL-17645 + $this->assertEquals(1, $DB->count_records($tablename)); + } + + public function test_object_params() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $o = new stdClass(); // objects without __toString - never worked + try { + $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + // objects with __toString() forbidden everywhere since 2.3 + $o = new dml_test_object_one(); + try { + $DB->fix_sql_params("SELECT {{$tablename}} WHERE course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $DB->execute("SELECT {{$tablename}} WHERE course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $DB->get_recordset_sql("SELECT {{$tablename}} WHERE course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $DB->get_records_sql("SELECT {{$tablename}} WHERE course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $record = new stdClass(); + $record->course = $o; + $DB->insert_record_raw($tablename, $record); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $record = new stdClass(); + $record->course = $o; + $DB->insert_record($tablename, $record); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $record = new stdClass(); + $record->course = $o; + $DB->import_record($tablename, $record); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $record = new stdClass(); + $record->id = 1; + $record->course = $o; + $DB->update_record_raw($tablename, $record); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $record = new stdClass(); + $record->id = 1; + $record->course = $o; + $DB->update_record($tablename, $record); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $DB->set_field_select($tablename, 'course', 1, "course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + + try { + $DB->delete_records_select($tablename, "course = ? ", array($o)); + $this->fail('coding_exception expected'); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + function test_sql_null_from_clause() { + $DB = $this->tdb; + $sql = "SELECT 1 AS id ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 1); + } + + function test_sql_bitand() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10)); + + $sql = "SELECT ".$DB->sql_bitand(10, 3)." AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 2); + + $sql = "SELECT id, ".$DB->sql_bitand('col1', 'col2')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 2); + + $sql = "SELECT id, ".$DB->sql_bitand('col1', '?')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql, array(10)); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 2); + } + + function test_sql_bitnot() { + $DB = $this->tdb; + + $not = $DB->sql_bitnot(2); + $notlimited = $DB->sql_bitand($not, 7); // might be positive or negative number which can not fit into PHP INT! + + $sql = "SELECT $notlimited AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 5); + } + + function test_sql_bitor() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10)); + + $sql = "SELECT ".$DB->sql_bitor(10, 3)." AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 11); + + $sql = "SELECT id, ".$DB->sql_bitor('col1', 'col2')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 11); + + $sql = "SELECT id, ".$DB->sql_bitor('col1', '?')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql, array(10)); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 11); + } + + function test_sql_bitxor() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('col1', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('col2', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('col1' => 3, 'col2' => 10)); + + $sql = "SELECT ".$DB->sql_bitxor(10, 3)." AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 9); + + $sql = "SELECT id, ".$DB->sql_bitxor('col1', 'col2')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 9); + + $sql = "SELECT id, ".$DB->sql_bitxor('col1', '?')." AS res FROM {{$tablename}}"; + $result = $DB->get_records_sql($sql, array(10)); + $this->assertEquals(count($result), 1); + $this->assertEquals(reset($result)->res, 9); + } + + function test_sql_modulo() { + $DB = $this->tdb; + $sql = "SELECT ".$DB->sql_modulo(10, 7)." AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 3); + } + + function test_sql_ceil() { + $DB = $this->tdb; + $sql = "SELECT ".$DB->sql_ceil(665.666)." AS res ".$DB->sql_null_from_clause(); + $this->assertEquals($DB->get_field_sql($sql), 666); + } + + function test_cast_char2int() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table1 = $this->get_test_table("1"); + $tablename1 = $table1->getName(); + + $table1->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table1->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table1->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null); + $table1->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table1); + + $DB->insert_record($tablename1, array('name'=>'0100', 'nametext'=>'0200')); + $DB->insert_record($tablename1, array('name'=>'10', 'nametext'=>'20')); + + $table2 = $this->get_test_table("2"); + $tablename2 = $table2->getName(); + $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table2->add_field('res', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table2->add_field('restext', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table2); + + $DB->insert_record($tablename2, array('res'=>100, 'restext'=>200)); + + // casting varchar field + $sql = "SELECT * + FROM {".$tablename1."} t1 + JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.name")." = t2.res "; + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 1); + // also test them in order clauses + $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('name'); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 2); + $this->assertEquals(reset($records)->name, '10'); + $this->assertEquals(next($records)->name, '0100'); + + // casting text field + $sql = "SELECT * + FROM {".$tablename1."} t1 + JOIN {".$tablename2."} t2 ON ".$DB->sql_cast_char2int("t1.nametext", true)." = t2.restext "; + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 1); + // also test them in order clauses + $sql = "SELECT * FROM {{$tablename1}} ORDER BY ".$DB->sql_cast_char2int('nametext', true); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 2); + $this->assertEquals(reset($records)->nametext, '20'); + $this->assertEquals(next($records)->nametext, '0200'); + } + + function test_cast_char2real() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null); + $table->add_field('res', XMLDB_TYPE_NUMBER, '12, 7', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'10.10', 'nametext'=>'10.10', 'res'=>5.1)); + $DB->insert_record($tablename, array('name'=>'91.10', 'nametext'=>'91.10', 'res'=>666)); + $DB->insert_record($tablename, array('name'=>'011.10','nametext'=>'011.10','res'=>10.1)); + + // casting varchar field + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('name')." > res"; + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 2); + // also test them in order clauses + $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('name'); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 3); + $this->assertEquals(reset($records)->name, '10.10'); + $this->assertEquals(next($records)->name, '011.10'); + $this->assertEquals(next($records)->name, '91.10'); + + // casting text field + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_cast_char2real('nametext', true)." > res"; + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 2); + // also test them in order clauses + $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_cast_char2real('nametext', true); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 3); + $this->assertEquals(reset($records)->nametext, '10.10'); + $this->assertEquals(next($records)->nametext, '011.10'); + $this->assertEquals(next($records)->nametext, '91.10'); + } + + function sql_compare_text() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'abcd', 'description'=>'abcd')); + $DB->insert_record($tablename, array('name'=>'abcdef', 'description'=>'bbcdef')); + $DB->insert_record($tablename, array('name'=>'aaaabb', 'description'=>'aaaacccccccccccccccccc')); + + $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description'); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE name = ".$DB->sql_compare_text('description', 4); + $records = $DB->get_records_sql($sql); + $this->assertEquals(count($records), 2); + } + + function test_unique_index_collation_trouble() { + // note: this is a work in progress, we should probably move this to ddl test + + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_index('name', XMLDB_INDEX_UNIQUE, array('name')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'aaa')); + + try { + $DB->insert_record($tablename, array('name'=>'AAA')); + } catch (Exception $e) { + //TODO: ignore case insensitive uniqueness problems for now + //$this->fail("Unique index is case sensitive - this may cause problems in some tables"); + } + + try { + $DB->insert_record($tablename, array('name'=>'aäa')); + $DB->insert_record($tablename, array('name'=>'aáa')); + $this->assertTrue(true); + } catch (Exception $e) { + $family = $DB->get_dbfamily(); + if ($family === 'mysql' or $family === 'mssql') { + $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation."); + } else { + // this should not happen, PostgreSQL and Oracle do not support accent insensitive uniqueness. + $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages."); + } + throw($e); + } + } + + function test_sql_binary_equal() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'aaa')); + $DB->insert_record($tablename, array('name'=>'aáa')); + $DB->insert_record($tablename, array('name'=>'aäa')); + $DB->insert_record($tablename, array('name'=>'bbb')); + $DB->insert_record($tablename, array('name'=>'BBB')); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('bbb')); + $this->assertEquals(count($records), 1, 'SQL operator "=" is expected to be case sensitive'); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = ?", array('aaa')); + $this->assertEquals(count($records), 1, 'SQL operator "=" is expected to be accent sensitive'); + } + + function test_sql_like() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'SuperDuperRecord')); + $DB->insert_record($tablename, array('name'=>'Nodupor')); + $DB->insert_record($tablename, array('name'=>'ouch')); + $DB->insert_record($tablename, array('name'=>'ouc_')); + $DB->insert_record($tablename, array('name'=>'ouc%')); + $DB->insert_record($tablename, array('name'=>'aui')); + $DB->insert_record($tablename, array('name'=>'aüi')); + $DB->insert_record($tablename, array('name'=>'aÜi')); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false); + $records = $DB->get_records_sql($sql, array("%dup_r%")); + $this->assertEquals(count($records), 2); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true); + $records = $DB->get_records_sql($sql, array("%dup%")); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?'); // defaults + $records = $DB->get_records_sql($sql, array("%dup%")); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true); + $records = $DB->get_records_sql($sql, array("ouc\\_")); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, false, '|'); + $records = $DB->get_records_sql($sql, array($DB->sql_like_escape("ouc%", '|'))); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true); + $records = $DB->get_records_sql($sql, array('aui')); + $this->assertEquals(count($records), 1); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, true, true); // NOT LIKE + $records = $DB->get_records_sql($sql, array("%o%")); + $this->assertEquals(count($records), 3); + + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, true, true); // NOT ILIKE + $records = $DB->get_records_sql($sql, array("%D%")); + $this->assertEquals(count($records), 6); + + // TODO: we do not require accent insensitivness yet, just make sure it does not throw errors + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', true, false); + $records = $DB->get_records_sql($sql, array('aui')); + //$this->assertEquals(count($records), 2, 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.'); + $sql = "SELECT * FROM {{$tablename}} WHERE ".$DB->sql_like('name', '?', false, false); + $records = $DB->get_records_sql($sql, array('aui')); + //$this->assertEquals(count($records), 3, 'Accent insensitive LIKE searches may not be supported in all databases, this is not a problem.'); + } + + function test_coalesce() { + $DB = $this->tdb; + + // Testing not-null ocurrences, return 1st + $sql = "SELECT COALESCE('returnthis', 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array())); + $sql = "SELECT COALESCE(:paramvalue, 'orthis', 'orwhynotthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); + + // Testing null ocurrences, return 2nd + $sql = "SELECT COALESCE(null, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array())); + $sql = "SELECT COALESCE(:paramvalue, 'returnthis', 'orthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null))); + $sql = "SELECT COALESCE(null, :paramvalue, 'orthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); + + // Testing null ocurrences, return 3rd + $sql = "SELECT COALESCE(null, null, 'returnthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array())); + $sql = "SELECT COALESCE(null, :paramvalue, 'returnthis') AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array('paramvalue' => null))); + $sql = "SELECT COALESCE(null, null, :paramvalue) AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('returnthis', $DB->get_field_sql($sql, array('paramvalue' => 'returnthis'))); + + // Testing all null ocurrences, return null + // Note: under mssql, if all elements are nulls, at least one must be a "typed" null, hence + // we cannot test this in a cross-db way easily, so next 2 tests are using + // different queries depending of the DB family + $customnull = $DB->get_dbfamily() == 'mssql' ? 'CAST(null AS varchar)' : 'null'; + $sql = "SELECT COALESCE(null, null, " . $customnull . ") AS test" . $DB->sql_null_from_clause(); + $this->assertNull($DB->get_field_sql($sql, array())); + $sql = "SELECT COALESCE(null, :paramvalue, " . $customnull . ") AS test" . $DB->sql_null_from_clause(); + $this->assertNull($DB->get_field_sql($sql, array('paramvalue' => null))); + + // Check there are not problems with whitespace strings + $sql = "SELECT COALESCE(null, '', null) AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('', $DB->get_field_sql($sql, array())); + $sql = "SELECT COALESCE(null, :paramvalue, null) AS test" . $DB->sql_null_from_clause(); + $this->assertEquals('', $DB->get_field_sql($sql, array('paramvalue' => ''))); + } + + function test_sql_concat() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + /// Testing all sort of values + $sql = "SELECT ".$DB->sql_concat("?", "?", "?")." AS fullname ". $DB->sql_null_from_clause(); + // string, some unicode chars + $params = array('name', 'áéíóú', 'name3'); + $this->assertEquals('nameáéíóúname3', $DB->get_field_sql($sql, $params)); + // string, spaces and numbers + $params = array('name', ' ', 12345); + $this->assertEquals('name 12345', $DB->get_field_sql($sql, $params)); + // float, empty and strings + $params = array(123.45, '', 'test'); + $this->assertEquals('123.45test', $DB->get_field_sql($sql, $params)); + // only integers + $params = array(12, 34, 56); + $this->assertEquals('123456', $DB->get_field_sql($sql, $params)); + // float, null and strings + $params = array(123.45, null, 'test'); + $this->assertNull($DB->get_field_sql($sql, $params), 'ANSI behaviour: Concatenating NULL must return NULL - But in Oracle :-(. [%s]'); // Concatenate NULL with anything result = NULL + + /// Testing fieldnames + values and also integer fieldnames + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('description'=>'áéíóú')); + $DB->insert_record($tablename, array('description'=>'dxxx')); + $DB->insert_record($tablename, array('description'=>'bcde')); + + // fieldnames and values mixed + $sql = 'SELECT id, ' . $DB->sql_concat('description', "'harcoded'", '?', '?') . ' AS result FROM {' . $tablename . '}'; + $records = $DB->get_records_sql($sql, array(123.45, 'test')); + $this->assertEquals(count($records), 3); + $this->assertEquals($records[1]->result, 'áéíóúharcoded123.45test'); + // integer fieldnames and values + $sql = 'SELECT id, ' . $DB->sql_concat('id', "'harcoded'", '?', '?') . ' AS result FROM {' . $tablename . '}'; + $records = $DB->get_records_sql($sql, array(123.45, 'test')); + $this->assertEquals(count($records), 3); + $this->assertEquals($records[1]->result, '1harcoded123.45test'); + // all integer fieldnames + $sql = 'SELECT id, ' . $DB->sql_concat('id', 'id', 'id') . ' AS result FROM {' . $tablename . '}'; + $records = $DB->get_records_sql($sql, array()); + $this->assertEquals(count($records), 3); + $this->assertEquals($records[1]->result, '111'); + + } + + function test_concat_join() { + $DB = $this->tdb; + $sql = "SELECT ".$DB->sql_concat_join("' '", array("?", "?", "?"))." AS fullname ".$DB->sql_null_from_clause(); + $params = array("name", "name2", "name3"); + $result = $DB->get_field_sql($sql, $params); + $this->assertEquals("name name2 name3", $result); + } + + function test_sql_fullname() { + $DB = $this->tdb; + $sql = "SELECT ".$DB->sql_fullname(':first', ':last')." AS fullname ".$DB->sql_null_from_clause(); + $params = array('first'=>'Firstname', 'last'=>'Surname'); + $this->assertEquals("Firstname Surname", $DB->get_field_sql($sql, $params)); + } + + function sql_sql_order_by_text() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('description'=>'abcd')); + $DB->insert_record($tablename, array('description'=>'dxxx')); + $DB->insert_record($tablename, array('description'=>'bcde')); + + $sql = "SELECT * FROM {{$tablename}} ORDER BY ".$DB->sql_order_by_text('description'); + $records = $DB->get_records_sql($sql); + $first = array_shift($records); + $this->assertEquals(1, $first->id); + $second = array_shift($records); + $this->assertEquals(3, $second->id); + $last = array_shift($records); + $this->assertEquals(2, $last->id); + } + + function test_sql_substring() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $string = 'abcdefghij'; + + $DB->insert_record($tablename, array('name'=>$string)); + + $sql = "SELECT id, ".$DB->sql_substr("name", 5)." AS name FROM {{$tablename}}"; + $record = $DB->get_record_sql($sql); + $this->assertEquals(substr($string, 5-1), $record->name); + + $sql = "SELECT id, ".$DB->sql_substr("name", 5, 2)." AS name FROM {{$tablename}}"; + $record = $DB->get_record_sql($sql); + $this->assertEquals(substr($string, 5-1, 2), $record->name); + + try { + // silence php warning ;-) + @$DB->sql_substr("name"); + $this->fail("Expecting an exception, none occurred"); + } catch (Exception $e) { + $this->assertTrue($e instanceof coding_exception); + } + } + + function test_sql_length() { + $DB = $this->tdb; + $this->assertEquals($DB->get_field_sql( + "SELECT ".$DB->sql_length("'aeiou'").$DB->sql_null_from_clause()), 5); + $this->assertEquals($DB->get_field_sql( + "SELECT ".$DB->sql_length("'áéíóú'").$DB->sql_null_from_clause()), 5); + } + + function test_sql_position() { + $DB = $this->tdb; + $this->assertEquals($DB->get_field_sql( + "SELECT ".$DB->sql_position("'ood'", "'Moodle'").$DB->sql_null_from_clause()), 2); + $this->assertEquals($DB->get_field_sql( + "SELECT ".$DB->sql_position("'Oracle'", "'Moodle'").$DB->sql_null_from_clause()), 0); + } + + function test_sql_empty() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('namenotnull', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, 'default value'); + $table->add_field('namenotnullnodeflt', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'', 'namenotnull'=>'')); + $DB->insert_record($tablename, array('name'=>null)); + $DB->insert_record($tablename, array('name'=>'lalala')); + $DB->insert_record($tablename, array('name'=>0)); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE name = '".$DB->sql_empty()."'"); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->name, ''); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnull = '".$DB->sql_empty()."'"); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->namenotnull, ''); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE namenotnullnodeflt = '".$DB->sql_empty()."'"); + $this->assertEquals(count($records), 4); + $record = reset($records); + $this->assertEquals($record->namenotnullnodeflt, ''); + } + + function test_sql_isempty() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); + $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'', 'namenull'=>'', 'description'=>'', 'descriptionnull'=>'')); + $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null)); + $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala')); + $DB->insert_record($tablename, array('name'=>0, 'namenull'=>0, 'description'=>0, 'descriptionnull'=>0)); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'name', false, false)); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->name, ''); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'namenull', true, false)); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->namenull, ''); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'description', false, true)); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->description, ''); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isempty($tablename, 'descriptionnull', true, true)); + $this->assertEquals(count($records), 1); + $record = reset($records); + $this->assertEquals($record->descriptionnull, ''); + } + + function test_sql_isnotempty() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('namenull', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null); + $table->add_field('descriptionnull', XMLDB_TYPE_TEXT, 'big', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'', 'namenull'=>'', 'description'=>'', 'descriptionnull'=>'')); + $DB->insert_record($tablename, array('name'=>'??', 'namenull'=>null, 'description'=>'??', 'descriptionnull'=>null)); + $DB->insert_record($tablename, array('name'=>'la', 'namenull'=>'la', 'description'=>'la', 'descriptionnull'=>'lalala')); + $DB->insert_record($tablename, array('name'=>0, 'namenull'=>0, 'description'=>0, 'descriptionnull'=>0)); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'name', false, false)); + $this->assertEquals(count($records), 3); + $record = reset($records); + $this->assertEquals($record->name, '??'); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'namenull', true, false)); + $this->assertEquals(count($records), 2); // nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour + $record = reset($records); + $this->assertEquals($record->namenull, 'la'); // so 'la' is the first non-empty 'namenull' record + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'description', false, true)); + $this->assertEquals(count($records), 3); + $record = reset($records); + $this->assertEquals($record->description, '??'); + + $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE ".$DB->sql_isnotempty($tablename, 'descriptionnull', true, true)); + $this->assertEquals(count($records), 2); // nulls aren't comparable (so they aren't "not empty"). SQL expected behaviour + $record = reset($records); + $this->assertEquals($record->descriptionnull, 'lalala'); // so 'lalala' is the first non-empty 'descriptionnull' record + } + + function test_sql_regex() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('name'=>'lalala')); + $DB->insert_record($tablename, array('name'=>'holaaa')); + $DB->insert_record($tablename, array('name'=>'aouch')); + + $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex()." ?"; + $params = array('a$'); + if ($DB->sql_regex_supported()) { + $records = $DB->get_records_sql($sql, $params); + $this->assertEquals(count($records), 2); + } else { + $this->assertTrue(true, 'Regexp operations not supported. Test skipped'); + } + + $sql = "SELECT * FROM {{$tablename}} WHERE name ".$DB->sql_regex(false)." ?"; + $params = array('.a'); + if ($DB->sql_regex_supported()) { + $records = $DB->get_records_sql($sql, $params); + $this->assertEquals(count($records), 1); + } else { + $this->assertTrue(true, 'Regexp operations not supported. Test skipped'); + } + + } + + /** + * Test some more complex SQL syntax which moodle uses and depends on to work + * useful to determine if new database libraries can be supported. + */ + public function test_get_records_sql_complicated() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('content', XMLDB_TYPE_TEXT, 'big', XMLDB_UNSIGNED, XMLDB_NOTNULL); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => 3, 'content' => 'hello', 'name'=>'xyz')); + $DB->insert_record($tablename, array('course' => 3, 'content' => 'world', 'name'=>'abc')); + $DB->insert_record($tablename, array('course' => 5, 'content' => 'hello', 'name'=>'def')); + $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc')); + + // test grouping by expressions in the query. MDL-26819. Note that there are 4 ways: + // - By column position (GROUP by 1) - Not supported by mssql & oracle + // - By column name (GROUP by course) - Supported by all, but leading to wrong results + // - By column alias (GROUP by casecol) - Not supported by mssql & oracle + // - By complete expression (GROUP BY CASE ...) - 100% cross-db, this test checks it + $sql = "SELECT (CASE WHEN course = 3 THEN 1 ELSE 0 END) AS casecol, + COUNT(1) AS countrecs, + MAX(name) AS maxname + FROM {{$tablename}} + GROUP BY CASE WHEN course = 3 THEN 1 ELSE 0 END + ORDER BY casecol DESC"; + $result = array( + 1 => (object)array('casecol' => 1, 'countrecs' => 2, 'maxname' => 'xyz'), + 0 => (object)array('casecol' => 0, 'countrecs' => 2, 'maxname' => 'def')); + $records = $DB->get_records_sql($sql, null); + $this->assertEquals($result, $records); + + // another grouping by CASE expression just to ensure it works ok for multiple WHEN + $sql = "SELECT CASE name + WHEN 'xyz' THEN 'last' + WHEN 'def' THEN 'mid' + WHEN 'abc' THEN 'first' + END AS casecol, + COUNT(1) AS countrecs, + MAX(name) AS maxname + FROM {{$tablename}} + GROUP BY CASE name + WHEN 'xyz' THEN 'last' + WHEN 'def' THEN 'mid' + WHEN 'abc' THEN 'first' + END + ORDER BY casecol DESC"; + $result = array( + 'mid' => (object)array('casecol' => 'mid', 'countrecs' => 1, 'maxname' => 'def'), + 'last' => (object)array('casecol' => 'last', 'countrecs' => 1, 'maxname' => 'xyz'), + 'first'=> (object)array('casecol' => 'first', 'countrecs' => 2, 'maxname' => 'abc')); + $records = $DB->get_records_sql($sql, null); + $this->assertEquals($result, $records); + + // test limits in queries with DISTINCT/ALL clauses and multiple whitespace. MDL-25268 + $sql = "SELECT DISTINCT course + FROM {{$tablename}} + ORDER BY course"; + // only limitfrom + $records = $DB->get_records_sql($sql, null, 1); + $this->assertEquals(2, count($records)); + $this->assertEquals(3, reset($records)->course); + $this->assertEquals(5, next($records)->course); + // only limitnum + $records = $DB->get_records_sql($sql, null, 0, 2); + $this->assertEquals(2, count($records)); + $this->assertEquals(2, reset($records)->course); + $this->assertEquals(3, next($records)->course); + // both limitfrom and limitnum + $records = $DB->get_records_sql($sql, null, 2, 2); + $this->assertEquals(1, count($records)); + $this->assertEquals(5, reset($records)->course); + + // we have sql like this in moodle, this syntax breaks on older versions of sqlite for example.. + $sql = "SELECT a.id AS id, a.course AS course + FROM {{$tablename}} a + JOIN (SELECT * FROM {{$tablename}}) b ON a.id = b.id + WHERE a.course = ?"; + + $records = $DB->get_records_sql($sql, array(3)); + $this->assertEquals(2, count($records)); + $this->assertEquals(1, reset($records)->id); + $this->assertEquals(2, next($records)->id); + + // do NOT try embedding sql_xxxx() helper functions in conditions array of count_records(), they don't break params/binding! + $count = $DB->count_records_select($tablename, "course = :course AND ".$DB->sql_compare_text('content')." = :content", array('course' => 3, 'content' => 'hello')); + $this->assertEquals(1, $count); + + // test int x string comparison + $sql = "SELECT * + FROM {{$tablename}} c + WHERE name = ?"; + $this->assertEquals(count($DB->get_records_sql($sql, array(10))), 0); + $this->assertEquals(count($DB->get_records_sql($sql, array("10"))), 0); + $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1')); + $DB->insert_record($tablename, array('course' => 7, 'content' => 'yy', 'name'=>'2')); + $this->assertEquals(count($DB->get_records_sql($sql, array(1))), 1); + $this->assertEquals(count($DB->get_records_sql($sql, array("1"))), 1); + $this->assertEquals(count($DB->get_records_sql($sql, array(10))), 0); + $this->assertEquals(count($DB->get_records_sql($sql, array("10"))), 0); + $DB->insert_record($tablename, array('course' => 7, 'content' => 'xx', 'name'=>'1abc')); + $this->assertEquals(count($DB->get_records_sql($sql, array(1))), 1); + $this->assertEquals(count($DB->get_records_sql($sql, array("1"))), 1); + } + + function test_onelevel_commit() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $transaction = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $this->assertEquals(0, $DB->count_records($tablename)); + $DB->insert_record($tablename, $data); + $this->assertEquals(1, $DB->count_records($tablename)); + $transaction->allow_commit(); + $this->assertEquals(1, $DB->count_records($tablename)); + } + + function test_onelevel_rollback() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + // this might in fact encourage ppl to migrate from myisam to innodb + + $transaction = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $this->assertEquals(0, $DB->count_records($tablename)); + $DB->insert_record($tablename, $data); + $this->assertEquals(1, $DB->count_records($tablename)); + try { + $transaction->rollback(new Exception('test')); + $this->fail('transaction rollback must rethrow exception'); + } catch (Exception $e) { + } + $this->assertEquals(0, $DB->count_records($tablename)); + } + + function test_nested_transactions() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + // two level commit + $this->assertFalse($DB->is_transaction_started()); + $transaction1 = $DB->start_delegated_transaction(); + $this->assertTrue($DB->is_transaction_started()); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>4); + $DB->insert_record($tablename, $data); + $transaction2->allow_commit(); + $this->assertTrue($DB->is_transaction_started()); + $transaction1->allow_commit(); + $this->assertFalse($DB->is_transaction_started()); + $this->assertEquals(2, $DB->count_records($tablename)); + + $DB->delete_records($tablename); + + // rollback from top level + $transaction1 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>4); + $DB->insert_record($tablename, $data); + $transaction2->allow_commit(); + try { + $transaction1->rollback(new Exception('test')); + $this->fail('transaction rollback must rethrow exception'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'Exception'); + } + $this->assertEquals(0, $DB->count_records($tablename)); + + $DB->delete_records($tablename); + + // rollback from nested level + $transaction1 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>4); + $DB->insert_record($tablename, $data); + try { + $transaction2->rollback(new Exception('test')); + $this->fail('transaction rollback must rethrow exception'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'Exception'); + } + $this->assertEquals(2, $DB->count_records($tablename)); // not rolled back yet + try { + $transaction1->allow_commit(); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + $this->assertEquals(2, $DB->count_records($tablename)); // not rolled back yet + // the forced rollback is done from the default_exception handler and similar places, + // let's do it manually here + $this->assertTrue($DB->is_transaction_started()); + $DB->force_transaction_rollback(); + $this->assertFalse($DB->is_transaction_started()); + $this->assertEquals(0, $DB->count_records($tablename)); // finally rolled back + + $DB->delete_records($tablename); + } + + function test_transactions_forbidden() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->transactions_forbidden(); + $transaction = $DB->start_delegated_transaction(); + $data = (object)array('course'=>1); + $DB->insert_record($tablename, $data); + try { + $DB->transactions_forbidden(); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + // the previous test does not force rollback + $transaction->allow_commit(); + $this->assertFalse($DB->is_transaction_started()); + $this->assertEquals(1, $DB->count_records($tablename)); + } + + function test_wrong_transactions() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + + // wrong order of nested commits + $transaction1 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>4); + $DB->insert_record($tablename, $data); + try { + $transaction1->allow_commit(); + $this->fail('wrong order of commits must throw exception'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + try { + $transaction2->allow_commit(); + $this->fail('first wrong commit forces rollback'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + // this is done in default exception handler usually + $this->assertTrue($DB->is_transaction_started()); + $this->assertEquals(2, $DB->count_records($tablename)); // not rolled back yet + $DB->force_transaction_rollback(); + $this->assertEquals(0, $DB->count_records($tablename)); + $DB->delete_records($tablename); + + + // wrong order of nested rollbacks + $transaction1 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>4); + $DB->insert_record($tablename, $data); + try { + // this first rollback should prevent all other rollbacks + $transaction1->rollback(new Exception('test')); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'Exception'); + } + try { + $transaction2->rollback(new Exception('test')); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'Exception'); + } + try { + $transaction1->rollback(new Exception('test')); + } catch (Exception $e) { + // the rollback was used already once, no way to use it again + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + // this is done in default exception handler usually + $this->assertTrue($DB->is_transaction_started()); + $DB->force_transaction_rollback(); + $DB->delete_records($tablename); + + + // unknown transaction object + $transaction1 = $DB->start_delegated_transaction(); + $data = (object)array('course'=>3); + $DB->insert_record($tablename, $data); + $transaction2 = new moodle_transaction($DB); + try { + $transaction2->allow_commit(); + $this->fail('foreign transaction must fail'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + try { + $transaction1->allow_commit(); + $this->fail('first wrong commit forces rollback'); + } catch (Exception $e) { + $this->assertEquals(get_class($e), 'dml_transaction_exception'); + } + $DB->force_transaction_rollback(); + $DB->delete_records($tablename); + } + + function test_concurent_transactions() { + // Notes about this test: + // 1- MySQL needs to use one engine with transactions support (InnoDB). + // 2- MSSQL needs to have enabled versioning for read committed + // transactions (ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON) + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $transaction = $DB->start_delegated_transaction(); + $data = (object)array('course'=>1); + $this->assertEquals(0, $DB->count_records($tablename)); + $DB->insert_record($tablename, $data); + $this->assertEquals(1, $DB->count_records($tablename)); + + //open second connection + $cfg = $DB->export_dbconfig(); + if (!isset($cfg->dboptions)) { + $cfg->dboptions = array(); + } + $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); + $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); + + // second instance should not see pending inserts + $this->assertEquals(0, $DB2->count_records($tablename)); + $data = (object)array('course'=>2); + $DB2->insert_record($tablename, $data); + $this->assertEquals(1, $DB2->count_records($tablename)); + + // first should see the changes done from second + $this->assertEquals(2, $DB->count_records($tablename)); + + // now commit and we should see it finally in second connections + $transaction->allow_commit(); + $this->assertEquals(2, $DB2->count_records($tablename)); + + // let's try delete all is also working on (this checks MDL-29198) + // initially both connections see all the records in the table (2) + $this->assertEquals(2, $DB->count_records($tablename)); + $this->assertEquals(2, $DB2->count_records($tablename)); + $transaction = $DB->start_delegated_transaction(); + + // delete all from within transaction + $DB->delete_records($tablename); + + // transactional $DB, sees 0 records now + $this->assertEquals(0, $DB->count_records($tablename)); + + // others ($DB2) get no changes yet + $this->assertEquals(2, $DB2->count_records($tablename)); + + // now commit and we should see changes + $transaction->allow_commit(); + $this->assertEquals(0, $DB2->count_records($tablename)); + + $DB2->dispose(); + } + + public function test_session_locks() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + // Open second connection + $cfg = $DB->export_dbconfig(); + if (!isset($cfg->dboptions)) { + $cfg->dboptions = array(); + } + $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary); + $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions); + + // Testing that acquiring a lock efectively locks + // Get a session lock on connection1 + $rowid = rand(100, 200); + $timeout = 1; + $DB->get_session_lock($rowid, $timeout); + + // Try to get the same session lock on connection2 + try { + $DB2->get_session_lock($rowid, $timeout); + $DB2->release_session_lock($rowid); // Should not be excuted, but here for safety + $this->fail('An Exception is missing, expected due to session lock acquired.'); + } catch (exception $e) { + $this->assertTrue($e instanceof dml_sessionwait_exception); + $DB->release_session_lock($rowid); // Release lock on connection1 + } + + // Testing that releasing a lock efectively frees + // Get a session lock on connection1 + $rowid = rand(100, 200); + $timeout = 1; + $DB->get_session_lock($rowid, $timeout); + // Release the lock on connection1 + $DB->release_session_lock($rowid); + + // Get the just released lock on connection2 + $DB2->get_session_lock($rowid, $timeout); + // Release the lock on connection2 + $DB2->release_session_lock($rowid); + + $DB2->dispose(); + } + + public function test_bound_param_types() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('content', XMLDB_TYPE_TEXT, 'big', XMLDB_UNSIGNED, XMLDB_NOTNULL); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => '1', 'content'=>'xx'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 2, 'content'=>'yy'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'somestring', 'content'=>'zz'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'aa', 'content'=>'1'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'bb', 'content'=>2))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'cc', 'content'=>'sometext'))); + + + // Conditions in CHAR columns + $this->assertTrue($DB->record_exists($tablename, array('name'=>1))); + $this->assertTrue($DB->record_exists($tablename, array('name'=>'1'))); + $this->assertFalse($DB->record_exists($tablename, array('name'=>111))); + $this->assertNotEmpty($DB->get_record($tablename, array('name'=>1))); + $this->assertNotEmpty($DB->get_record($tablename, array('name'=>'1'))); + $this->assertEmpty($DB->get_record($tablename, array('name'=>111))); + $sqlqm = "SELECT * + FROM {{$tablename}} + WHERE name = ?"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1))); + $this->assertEquals(1, count($records)); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1'))); + $this->assertEquals(1, count($records)); + $records = $DB->get_records_sql($sqlqm, array(222)); + $this->assertEquals(0, count($records)); + $sqlnamed = "SELECT * + FROM {{$tablename}} + WHERE name = :name"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => 2))); + $this->assertEquals(1, count($records)); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('name' => '2'))); + $this->assertEquals(1, count($records)); + + // Conditions in TEXT columns always must be performed with the sql_compare_text + // helper function on both sides of the condition + $sqlqm = "SELECT * + FROM {{$tablename}} + WHERE " . $DB->sql_compare_text('content') . " = " . $DB->sql_compare_text('?'); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array('1'))); + $this->assertEquals(1, count($records)); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, array(1))); + $this->assertEquals(1, count($records)); + $sqlnamed = "SELECT * + FROM {{$tablename}} + WHERE " . $DB->sql_compare_text('content') . " = " . $DB->sql_compare_text(':content'); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => 2))); + $this->assertEquals(1, count($records)); + $this->assertNotEmpty($records = $DB->get_records_sql($sqlnamed, array('content' => '2'))); + $this->assertEquals(1, count($records)); + } + + public function test_bound_param_reserved() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $DB->insert_record($tablename, array('course' => '1')); + + // make sure reserved words do not cause fatal problems in query parameters + + $DB->execute("UPDATE {{$tablename}} SET course = 1 WHERE ID = :select", array('select'=>1)); + $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1)); + $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}} WHERE course = :select", array('select'=>1)); + $rs->close(); + $DB->get_fieldset_sql("SELECT id FROM {{$tablename}} WHERE course = :select", array('select'=>1)); + $DB->set_field_select($tablename, 'course', '1', "id = :select", array('select'=>1)); + $DB->delete_records_select($tablename, "id = :select", array('select'=>1)); + } + + public function test_limits_and_offsets() { + $DB = $this->tdb; + $dbman = $DB->get_manager(); + + if (false) $DB = new moodle_database (); + + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('content', XMLDB_TYPE_TEXT, 'big', XMLDB_UNSIGNED, XMLDB_NOTNULL); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $dbman->create_table($table); + + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'd', 'content'=>'four'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'e', 'content'=>'five'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'f', 'content'=>'six'))); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4)); + $this->assertEquals(2, count($records)); + $this->assertEquals('e', reset($records)->name); + $this->assertEquals('f', end($records)->name); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertEmpty($records = $DB->get_records_sql($sqlqm, null, 8)); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 4)); + $this->assertEquals(4, count($records)); + $this->assertEquals('a', reset($records)->name); + $this->assertEquals('d', end($records)->name); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8)); + $this->assertEquals(6, count($records)); + $this->assertEquals('a', reset($records)->name); + $this->assertEquals('f', end($records)->name); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 1, 4)); + $this->assertEquals(4, count($records)); + $this->assertEquals('b', reset($records)->name); + $this->assertEquals('e', end($records)->name); + + $sqlqm = "SELECT * + FROM {{$tablename}}"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); + $this->assertEquals(2, count($records)); + $this->assertEquals('e', reset($records)->name); + $this->assertEquals('f', end($records)->name); + + $sqlqm = "SELECT t.*, t.name AS test + FROM {{$tablename}} t + ORDER BY t.id ASC"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); + $this->assertEquals(2, count($records)); + $this->assertEquals('e', reset($records)->name); + $this->assertEquals('f', end($records)->name); + + $sqlqm = "SELECT DISTINCT t.name, t.name AS test + FROM {{$tablename}} t + ORDER BY t.name DESC"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 4, 4)); + $this->assertEquals(2, count($records)); + $this->assertEquals('b', reset($records)->name); + $this->assertEquals('a', end($records)->name); + + $sqlqm = "SELECT 1 + FROM {{$tablename}} t + WHERE t.name = 'a'"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 1)); + $this->assertEquals(1, count($records)); + + $sqlqm = "SELECT 'constant' + FROM {{$tablename}} t + WHERE t.name = 'a'"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 8)); + $this->assertEquals(1, count($records)); + + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'a', 'content'=>'one'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'b', 'content'=>'two'))); + $this->assertNotEmpty($DB->insert_record($tablename, array('name' => 'c', 'content'=>'three'))); + + $sqlqm = "SELECT t.name, COUNT(DISTINCT t2.id) AS count, 'Test' AS teststring + FROM {{$tablename}} t + LEFT JOIN ( + SELECT t.id, t.name + FROM {{$tablename}} t + ) t2 ON t2.name = t.name + GROUP BY t.name + ORDER BY t.name ASC"; + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm)); + $this->assertEquals(6, count($records)); // a,b,c,d,e,f + $this->assertEquals(2, reset($records)->count); // a has 2 records now + $this->assertEquals(1, end($records)->count); // f has 1 record still + + $this->assertNotEmpty($records = $DB->get_records_sql($sqlqm, null, 0, 2)); + $this->assertEquals(2, count($records)); + $this->assertEquals(2, reset($records)->count); + $this->assertEquals(2, end($records)->count); + } +} + +/** + * This class is not a proper subclass of moodle_database. It is + * intended to be used only in unit tests, in order to gain access to the + * protected methods of moodle_database, and unit test them. + */ +class moodle_database_for_testing extends moodle_database { + protected $prefix = 'mdl_'; + + public function public_fix_table_names($sql) { + return $this->fix_table_names($sql); + } + + public function driver_installed(){} + public function get_dbfamily(){} + protected function get_dbtype(){} + protected function get_dblibrary(){} + public function get_name(){} + public function get_configuration_help(){} + public function get_configuration_hints(){} + public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null){} + public function get_server_info(){} + protected function allowed_param_types(){} + public function get_last_error(){} + public function get_tables($usecache=true){} + public function get_indexes($table){} + public function get_columns($table, $usecache=true){} + protected function normalise_value($column, $value){} + public function set_debug($state){} + public function get_debug(){} + public function set_logging($state){} + public function change_database_structure($sql){} + public function execute($sql, array $params=null){} + public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0){} + public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0){} + public function get_fieldset_sql($sql, array $params=null){} + public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false){} + public function insert_record($table, $dataobject, $returnid=true, $bulk=false){} + public function import_record($table, $dataobject){} + public function update_record_raw($table, $params, $bulk=false){} + public function update_record($table, $dataobject, $bulk=false){} + public function set_field_select($table, $newfield, $newvalue, $select, array $params=null){} + public function delete_records_select($table, $select, array $params=null){} + public function sql_concat(){} + public function sql_concat_join($separator="' '", $elements=array()){} + public function sql_substr($expr, $start, $length=false){} + public function begin_transaction() {} + public function commit_transaction() {} + public function rollback_transaction() {} +} + + +/** + * Dumb test class with toString() returrning 1. + */ +class dml_test_object_one { + public function __toString() { + return 1; + } +} diff --git a/lib/dml/tests/fixtures/clob.txt b/lib/dml/tests/fixtures/clob.txt new file mode 100644 index 0000000000000..1e073fc9c8616 --- /dev/null +++ b/lib/dml/tests/fixtures/clob.txt @@ -0,0 +1,197 @@ +제 1장 총 칙 + +제 1조 (개요) + +이 약관은 전기통신사업법 및 동법 시행령에 의거 ㈜네오플(이하 "회사")이 제공하는 서비스 (이하 "서비스")의 이용조건, 절차, 의무, 책임사항 및 이용에 필요한 기타사항을 규정합니다. + +제 2조 (목적) + +이 약관은 다음과 같은 내용을 목적으로 합니다. +1. 안정적인 서비스 제공 및 서비스, 시스템의 보호를 목적으로 한다. +2. 이용자의 안정적인 서비스 이용 및 이용자의 데이타 보호를 목적으로 한다. +3. 네트워크 및 서비스 이용자 네트워크의 안정성, 프라이버시, 보안성 유지를 목적으로 한다. +4. 네트워크 및 서비스의 사용 원칙 가이드라인 제시를 목적으로 한다. + +제 3조 (약관의 효력과 변경) + +1. 회사는 귀하가 본 약관 내용에 동의하는 것을 조건으로 서비스를 제공 합니다. +2. 이 약관은 서비스 내에 게시하여 공시함으로써 효력을 발생합니다. +3. 당사의 서비스 제공 행위 및 귀하의 서비스 사용 행위에는 본 약관이 우선적으로 적용될 것입니다. +4. 회사는 이 약관을 개정할 경우에는 적용일자 및 개정사유를 명시하여 현행약관과 함께 초기화면에 그 적용일자 7일이전부터 적용일자 전일까지 공지합니다. +5. 이용자는 회원등록을 취소(회원탈퇴)할 수 있으며, 계속 사용의 경우는 약관 변경에 대한 동의로 간주됩니다. 변경된 약관은 공지와 동시에 그 효력이 발생됩니다. + +제 4조 (약관외 준칙) + +1. 서비스 이용에 관하여는 이 약관을 적용하며, 이 약관에 명시되지 아니 한 사항에 대하여는 전기통신 기본법, 전기통신사업법, 정보통신망 이용 촉진 및 정보보호 등에 관한 법률(이하통신망법), 기타 관련법령 및 회사의 공지,이용안내를 적용 합니다. +2. 이 약관의 공지 및 변경사항은 회사의 지정된 홈페이지(http://www.candybar.co.kr)에 게시하는 방법으로 공지합니다. + +제 5조 (용어의 정의) + +이 약관에서 사용하는 용어의 정의는 다음과 같습니다. +1. 이용자 : 서비스 이용을 신청하고 회사가 이를 승낙하여 회원ID를 발급 받은 자를 말합니다. +2. 가 입 : 회사가 제공하는 양식에 해당 정보를 기입하고, 이 약관에 동의하여 서비스 이용계약을 완료시키는 행위를 말합니다. +3. 회 원 : 회사에 개인 정보를 제공하여 회원 등록을 한 자로서, 회사가 제공하는 서비스를 이용할 수 있는 자를 말합니다. +4. 캔디바 ID : 회원의 서비스 이용을 위하여 회원이 신청하고 회사가 승인하는 문자 및 숫자의 조합을 말합니다. +5. 비밀번호 : 회원ID가 일치하는지를 확인하고 자신의 비밀보호를 위하여 이용자 자신이 선정한 문자와 숫자의 조합을 말합니다. +6. 탈퇴 및 해지 : 회원이 이용계약을 종료 시키는 행위를 말합니다. + +제 2장 서비스 이용 계약 + +제 6조 (이용 계약의 성립) + +1. 이용계약은 당사 소정의 가입신청 양식에서 요구하는 사항을 기록하여 가입을 완료 하는것으로 성립함을 원칙으로 합니다. +2. 이용신청은 이용신청자 자신의 실명으로 하여야 합니다. 실명이 아닌경우는 여러가지 불이익을 받을수 도 있습니다. +3. 이용계약은 서비스 이용희망자의 이용약관 동의 후 이용 신청에 대하여 회사가 승낙함으로써 성립합니다. +4. 서비스 이용 희망자가 14세 미만의 미성년자 또는 한정치산자인 경우 부모등 법정대리인의 동의를 받아 이용신청하며 납입책임자는 법정대리인으로 합니다. 또한 금치산자인 경우에는 법정대리인 을 이용자 및 납입책임자로 하여 신청합니다. + +제 7조 (이용신청의 승낙) +1. 회사는 제 6조의 규정에 의한 서비스 이용희망자에 대하여 업무수행상 또는 기술상 지장이 없는 경우에는 원칙적으로 접수순서에 따라 이용신청을 승낙합니다. +2. 이용자의 이용신청에 대하여 회사가 이를 승낙한 경우, 회사는 회원 ID와 기타 회사가 필요하다고 인정하는 내용을 이용자에게 통지합니다. + +제 8조 (이용신청에 대한 불승낙과 승낙의 보류) +1. 이용자가 신청한 이용 아이디(ID)가 이미 다른 이용자에 의해 쓰여지고 있는 경우 승낙을 하지 아니할 수 있습니다. +2. 회사는 다음 각호에 해당하는 이용신청에 대하여는 승낙을 하지 아니할 수 있습니다. +1) 타인 명의의 신청 +2) 허위의 신청이거나 허위사실을 기재한 경우 예) 주민등록 생성기를 이용한 비실명 가입 +3) 기타 이용신청고객의 귀책사유로 이용승낙이 곤란한 경우 +3. 회사는 전항의 경우에는 이를 이용신청고객에게 가능한 빠른 시일 내에 통지하여야 합니다. +4. 회사는 이용신청고객이 미성년자, 한정치산자인 경우에 법정대리인(부모 등)의 동의없는 이용신청에 대하여 승낙을 보류할 수 있습니다. +5. 회사는 이용신청이 불승낙되거나 승낙을 제한하는 경우에는 이를 이용신청자에게 즉시 통보합니다. + +제 9조 (이용 아이디 관리 및 변경) + +1. 이용 아이디(ID) 및 비밀번호에 대한 모든 관리책임은 이용자에게 있습니다. +2. 이용 아이디(ID)는 다음에 해당하는 경우에는 이용자와 합의하여 변경할 수 있습니다. +(단, 이용 아이디(ID)를 변경할 경우 기존 이용ID는 소멸됩니다.) +1) 이용 아이디(ID)가 이용자의 전화번호 또는 주민등록번호 등으로 등록되어 사생활 침해가 우려 되는 경우 +2) 타인에게 혐오감을 주거나 미풍양속에 어긋나는 경우 +3) 기타 회사가 인정하는 합리적인 사유가 있는 경우 +3. 회사는 이용 아이디(ID)에 의하여 서비스 이용요금 청구 및 게시판 관리 등 제반 이용자 관리 업무를 수행하며 이용자는 이용 아이디(ID)를 공유, 양도 또는 변경할 수 없습니다. 단, 그 사유가 명백하고 회사가 인정하는 경우에는 그러하지 아니 합니다. +4. 이용자가 신청 또는 변경하여 사용하는 이용 아이디(ID) 및 비밀번호에 의하여 발생하는 서비스 이용상의 과실 또는 제3자에 의한 부정사용등에 대한 모든 책임은 이용자에게 있습니다. 단, 회사의 고의 또는 중대한 과실이 있는 경우에는 그러하지 아니합니다. +5. 기타 아이디의 관리 및 변경 등에 관한 사항은 이 약관과 회사의 공지, 이용안내 에서 정하는 바에 의합니다. + +제 3장 계약당사자의 의무 및 책임 + +제 10조 (회사의 의무) + +1. 회사는 이용자로부터 제기되는 의견이나 불만이 정당하다고 인정할 경우에는 즉시 처리하여야 합니다. 즉시 처리가 곤란한 경우에는 그 사유와 처리일정을 서면 또는 전화 등으로 통보합니다. +2. 회사는 서비스 제공과 관련하여 취득한 이용자의 정보를 본인의 동의없이 타인에게 누설 배포할 수 없으며 서비스 관련 업무 이외의 목적으로도 사용할 수 없습니다. +3. 제2항의 경우 관계법령에 의한 규정에 의하여 수사상의 목적으로 관계기관으로부터 요구받은 경우나 정보통신윤리위원회의 요청이 있는 경우 또는 통신망법 24조 1항 '정보통신서비스의 제공에 따른 요금정산을 위하여 필요한 경우'에는 개인정보의 이용 혹은 제 3자에게 제공될수 있으며 그 범위는 회사의 개인정보보호정책을 따릅니다. +4. 회사는 계속적이고 안정적인 서비스 제공을 위하여 설비에 장애가 생기거나 멸실된 경우에는 지체없이 이를 수리 또는 복구하며, 서비스를 다시 이용할 수 있게 된 경우 이 사실을 이용자에게 통지하여야 합니다. 단 천재지변, 비상사태 또는 그밖의 부득이한 경우에는 서비스를 일시 중단하거나 중지할 수 있습니다. +5. 회사는 이용계약의 체결, 계약사항의 변경 및 해지 등 이용자와의 계약에 관련된 절차 및 내용 등에 있어서 이용자에게 편의를 제공하도록 노력합니다. +6. 회사는 이용자가 안전하게 당사서비스를 이용할 수 있도록 이용자의 개인정보(신용정보 포함)보호를 위한 보안시스템을 갖추어야 합니다. +7. 회사는 이용자가 제11조의 이용자의 의무를 위반한 경우 및 고의 또는 중대한 과실로 회사에 손해를 입힌 경우에는 사전 통보 없이 이용계약을 해지하거나 또는 기간을 정하여 서비스의 이용을 중지할 수 있습니다. + +제 11 조 (이용자의 의무) + +1. 이용자는 이 약관에서 규정하는 사항과 서비스 이용안내 또는 주의사항을 준수하여야 하며,기타 회사의 업무수행에 현저한 지장을 초래하는 행위를 하여서는 아니 됩니다. +2. 이용자는 서비스를 이용함에 있어 문제 발생소지 정보의 책임은 이용자에게 있습니다. +3. 이용자는 서비스를 이용하여 얻은 정보를 가공, 판매하는 행위 등 게재 된 자료를 상업적으로 이용할 수 없으며 이를 위반하여 발생하는 제반 문제에 대한 책임은 이용자에게 있습니다. +4. 이용자는 이용계약에 따라 요금을 지불하여야 하며, 서비스 이용요금의 미납으로 인하여 발생되는 모든 문제에 대한 책임은 이용자에게 있습니다. 단, 회사의 고의 또는 중과실의 경우에는 그러하지 아니 합니다. +5. 이용자는 회원가입시 정보는 정확하게 기입하여야 합니다. 주민등록생성기등을 이용하여 허위로 가입을 하거나 주민등록 생성기를 인터넷에 올리거나 이를 이용해 만든 행위에 관하여서도 주민등록법 개정안에 의거하여 엄중히 법적인 제재를 가할 수 있습니다. 또한 이미 제공된 귀하에 대한 정보가 정확한 정보가 되도록 유지, 갱신하여야 하며, 회원은 자신의 캔디바 ID 및 비밀번호를 제3자에게 이용하게 해서는 안됩니다. +6. 이용자는 당사의 사전 승낙없이 서비스를 이용하여 어떠한 영리행위도 할 수 없습니다. +7. 이용자는 당사 서비스를 이용하여 얻은 정보를 당사의 사전승낙 없이 복사, 복제, 변경, 번역, 출판·방송 기타의 방법으로 사용하거나 이를 타인에게 제공할 수 없습니다. +8. 이용자는 당사 서비스 이용과 관련하여 다음 각 호의 행위를 하여서는 안됩니다. +1) 다른 회원의 캔디바 ID를 부정 사용하는 행위 +2) 범죄행위를 목적으로 하거나 기타 범죄행위와 관련된 행위 +3) 선량한 풍속, 기타 사회질서를 해하는 행위 +4) 타인의 명예를 훼손하거나 모욕하는 행위 +5) 타인의 지적재산권 등의 권리를 침해하는 행위 +6) 해킹행위 또는 컴퓨터 바이러스의 유포행위 +7) 타인의 의사에 반하여 광고성 정보 등 일정한 내용을 지속적으로 전송하는 행위 +8) 서비스의 안전적인 운영에 지장을 주거나 줄 우려가 있는 일체의 행위 +9) 당사 사이트에 게시된 정보의 변경 +10) 기타 전기통신법 제53조와 전기통신사업법 시행령 16조(불온통신), 통신사업법 제53조3항에 위배되는 행위 +9. 다른 이용자들에게 피해를 주는 다음 각 호에 해당하는 행위를 하여서는 안됩니다. +1) 회사가 인정하는 서비스의 공식 운영자인 아이디를 사칭하는 내용 +2) 선정적이고 음란한 내용 +3) 반사회적이고 관계법령에 저촉되는 내용 +4) 기타 제3자의 상표권, 저작권에 위배될 가능성이 있는 내용 +5) 비어, 속어라고 판단되는 내용 +6) 욕설이나 노골적인 성 묘사를 하는 내용 +7) 다른 이용자를 희롱하거나, 위협하거나, 특정 이용자에게 지속적으로 고통을 주거나,불편을 주는 행위 +8) 서비스 내 또는 웹사이트 상에서 다른 회사 제품의 광고나 판촉활동을 하는 행위 +9) 저작권자의 허가 없이 저작권 문제가 발생할 소지가 있는 내용을 서비스 내 또는 웹사이트 상에 배포하는 행위 + +제 4장 서비스 이용·제한·정지등 + +제 12조 (서비스의 이용) + +1. 캔디바 서비스의 이용은 기본적으로 무료입니다. 단 회사에서 정한 별도의 유효정보 및 유료서비스에 대해서는 그러지 아니하며, 유료서비스 이용에 관한 사항은 회사가 별도로 정한 약관 및 정책에 따릅니다. +2. 유료 서비스 이용의 결제에 관한 사항(충전방법, 사용, 환불 등)은 바(Bar)정책 이용약관에 따릅니다. +3. 아바타 및 게임 아이템 등 유료서비스의 소유권은 회사에 있으며, 웹상의 사용권은 해당 컨텐츠를 구매한 이용자에게 있습니다. + +제 13조 (서비스 이용시간 및 중지) + +1. 서비스 이용은 회사의 업무상 또는 기술상 특별한 지장이 없는 한 연중 무휴, 1일 24시간을 원칙으로 합니다. 다만, 회사는 서비스의 데이터 베이스별로 이용가능시간을 정할 수 있으며, 이 경우 그 내용은 회사가 정하여 서비스에 게시하거나 별도로 공시하는 바에 따릅니다. +1) 제1항의 규정에도 불구하고 정기점검 등의 필요로 회사가 정한 날 또는 시간은 예외적으로 서비스 이용을 제한할 수 있습니다. +2. 전시,사변,천재지변 또는 이에 준하는 국가비상사태가 발생하거나 발생할 우려가 있는 경우와 전기통신사업법에 의한 기간통신사업자가 전기통신서비스를 중지하는등 기타 부득이한 사유가 있는 경우에는 서비스의 전부 또는 일부를 제한하거나 정지할 수 있습니다. +1) 회사는 제1항의 규정에 의하여 서비스의 전부 또는 일부를 제한하거나 정지한 때에는 지체없이 이용자에게 알려야 합니다. +3. 이용자가 국익 또는 사회적 공익을 저해할 목적이나 범죄적 목적으로 서비스를 이용하고 있다고 판단되는 경우에 회사는 이용자에게 사전 통보 없이 서비스를 중단할 수 있으며 그에 따른 데이터도 복구를 전제로 하지않고 삭제할 수 있습니다. +4. 회사는 이용정지 기간 중에 사유가 해소된 것이 확인된 경우에는 지체없이 서비스를 재개통 또는 이용정지를 해제합니다. + +제 14조 (각종 자료의 저장기간) + +회사는 필요한 경우 서비스의 일정 부분별로 이용자가 게시한 자료나 이용자의 필요에 의해 저장하고 있는 자료에 대해 일정한 게재기간 또는 저장기간을 정할 수 있으며, 필요에 따라 기간을 변경할 수 있습니다. + +제 15조 (게시물의 저작권) + +1. 이용자가 서비스 홈페이지에 게시하거나 등록한 자료의 지적재산권은 이용자에게 귀속됩니다. 단, 회사는 서비스 홈페이지의 게재권을 가지며 비상업적 목적으로는 이용자의 게시물을 활용할 수 있습니다. +2. 이용자는 서비스를 이용하여 얻은 정보를 가공, 판매하는 행위 등 게재 된 자료를 상업적으로 이용할 수 없으며 이를 위반하여 발생하는 제반 문제에 대한 책임은 이용자에게 있습니다. +3. 회사는 이용자 게시물의 내용 검열, 검색 및 관리에 따른 일체의 손해배상 책임을 지지아니 합니다. + +제 5장 개인정보 보호 + +제 16조 개인정보보호 +회사는 관계법령이 정하는 바에 따라 회원등록정보를 포함한 회원의 개인정보를 보호하기 위하여 노력을 합니다. 회원의 개인정보보호에 관하여 관계법령 및 회사가 정하는 개인정보보호정책에 정한 바에 따릅니다. + +제 6장 계약해지 및 이용제한 + +제 17조 (계약의 해지) + +회원이 이용계약을 해지하고자 하는 경우에는 회원 본인이 온라인을 통하여 등록해지신청을 하여야 합니다. + +제 18조 (이용제한) + +1. 회사는 회원이 다음 각 호의 사유에 해당하는 경우 사전통지 없이 회원의 서비스 이용제한 및 적법한 조치를 취할 수 있으며 이용계약을 해지하거나 또는 기간을 정하여 서비스를 중지할 수 있습니다. +1) 회원 가입신청 또는 변경시 허위 내용을 등록한 경우 +2) 타인의 서비스 이용을 방해하거나 그 정보를 도용한 경우 +3) 회사의 운영진, 직원 또는 관계자를 사칭하는 경우 +4) 회사의 사전승락없이 서비스를 이용하여 영업활동을 하는 경우 +5) 회원ID를 타인과 거래하거나 회원ID의 게임상 사이버 자산을 타인과 매매하는 행위를 하는 경우 +6) 회사 프로그램상의 버그를 악용하여 정상적이지 아니한 방법으로 게임상 사이버자산을 취득하는 행위를 하는 경우 +7) 서비스를 통하여 얻은 정보를 회사의 사전승낙없이 서비스 이용 외이 목적으로 복제하거나 이를 출판 및 방송 등에 사용하거나, 제3자에게 제공하는 경우 +8) 회사 또는 제3자의 저작권 등 기타 지적재산권을 침해하는 내용을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우 +9) 공공질서 및 미풍약속에 위반되는 음란한 내용의 정보, 문장, 도형, 음향, 동영상을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우 +10) 심히 모욕적이거나 개인신상에 대한 내용이어서 타인의 명예나 프라이버시를 침해할 수 있는 내용을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우 +11) 서비스에 위해를 가하거나 고의로 방해한 경우 +12) 다른 회원을 희롱, 위협하거나 특정 이용자에게 지속적으로 고통, 불편을 주는 행위를 하는 경우 +13) 범죄와 관련이 있다고 객관적으로 판단되는 행위를 하는 경우 +14) 본 서비스를 이용함에 있어 본 약관 및 기타 회사가 정한 정책 또한 운영 규칙을 위반하는 경우 +15) 기타 관련 법령에 위배하는 행위를 하는 경우 + +제 7장 분쟁조정 및 기타사항 + +제 19조 (손해배상) + +회사는 무료로 제공되는 서비스 이용과 관련하여 회원에게 발생한 어떠한 손해에 관하여도 책임을 지지 않습니다. + +제 20조 (면책조항) + +1. 회사는 천재지변 또는 이에 준하는 불가항력으로 인하여 서비스를 제공 할 수 없는 경우에는 서비스 제공에 관한 책임이 면제됩니다. +2. 회사는 회원의 귀책사유로 인한 서비스 이용의 장애에 대하여 책임을 지지 않습니다. +3. 회사는 회원이 서비스를 이용하여 기대하는 수익을 상실한 것이나 서비스를 통하여 얻은 자료로 인한 손해에 관하여 책임을 지지 않습니다. +4. 회사는 회원이 서비스에 게재한 정보, 자료, 사실의 신뢰도, 정확성 등 내용에 관하여는 책임을 지지 않습니다. +5. 회사는 서비스 이용과 관련하여 가입자에게 발생한 손해 가운데 가입자의 고의, 과실에 의한 손해에 대하여 책임을 지지 않습니다. +6. 회사는 회원간 또는 회원과 제3자간에 서비스를 매개로 하여 물품거래 혹은 금전적 거래등과 관련하여 어떠한 책임도 부담하지 않습니다. + + +제 21조(분쟁조정) + +회사와 이용고객은 개인정보에 관한 분쟁이 있는 경우 신속하고 효과적인 분쟁해결을 위하여 한국정보보호진흥원내의 개인정보분쟁조정위원회에 그 처리를 의뢰할 수 있습니다. + + +제 22조(회사의 소유권) + +1. 회사의 서비스, 소프트웨어, 이미지, 마크, 로고, 디자인, 서비스명칭, 정보 및 상표 등과 관련된 지적재산권 및 기타 권리는 회사에게 소유권이 있습니다. +2. 이용자는 회사가 명시적으로 승인한 경우를 제외하고는 전항의 소정의 각 재산에 대한 전부 또는 일부의 수정, 대여, 대출, 판매, 배포, 제 작, 양도, 재라이센스, 담보권 설정 행위, 상업적 이용 행위를 할 수 없으며, 제3자로 하여금 이와 같은 행위를 하도록 허락할 수 없습니다. diff --git a/lib/dml/tests/fixtures/randombinary b/lib/dml/tests/fixtures/randombinary new file mode 100644 index 0000000000000000000000000000000000000000..91634b696a065a264558e97ccd21841a32525668 GIT binary patch literal 10000 zcmV+rC-2x@xLiKH#fm3ghx7u4)j?Ry=NZA%IuVCVZ9QJ`0s}R2xn(^2oDiZ{`FK4$ zPoufw{`n!YjflOS;}LInrAR$pP>px1I3Hy0c3U`(l<1@!W_rV-)C|31;zn1E5&^`` zQ}yL8TP)eDuRoAe^F!^ zo*E9s#)c(vQAF)qlgVy4d#O8?0b!VJ1@RhYC;vJ=Vauut%U(w+tBghxHDs##T<=N} zrYL~=)D*-~3Zt8$Hn?w9@r^p*JlA|9it}`cJK!%FERSUzZc7SniivEVSH(E`kKHlO ztK{pd*4vzoo$rg~e{^!06p6)CR#Q(zKzKD2X^(u=*MXHFx&FQO>%R;=x9t>evY6|0 z0ob!#5ZDGi7>Fy$jJ6dEKrtur%^1twNdh5p7KL(%yrf`F)DFHG@K?LgrXGWW5|&Y! z-&@i!F`>xVfn!PFsDA9R%9aHZNB2OR~6o!%2vn^fvzO$>%d~_#G z5>kAqBk$7YUGYRo?(m~)A1OE4dr34L0nkUlJHK;NX5!}jsgj;4$y7~Fh&(EqnKbYx z)siUE3YThwp1IR04dyLZZm~WmNv^@H03a}y!Axa8rtSXyrlE9AGHTRcHn#l@XkRcS zf$$1txjVDrEY4(-UD-T2=3abOeV|cM3=>j?^UT1Igp0%3e1P64VUc$;-6^J{Hbg5E?1*{{ykhn__euHIj zp{#I;qmr5)RW_9?aGCnNhMEW7!k$X5KMH_vu$`05>`^oXhAYAPC?YW zLDE8=^vSR=(ss8}N%X?d&~8}Q)7SgAD~b6;6u1fylj!*^!VZ*mg`6+1r1}+|r7P}@ zW}NixBm)J7?O%mo_df-w;7`*{`zL@v55bjl_b^|xse;rVlRpY-#+>)5)*UW!%BU|k zj0xHSds?)engZl=FCvaOk0(8tH*eAe?Z+?^#-MTV+H|ZGDRVn%2X{584V-TqSePNG zrt6l{!G_a%Wu|2V_FUaT6Ddd&olL1-7|e30jb-ZFU8c@^uzP4PJf@WQY*Z3eM6LbW z<$mQ0OZ4j0&d};2TuM%5f%jL%FXF*}perSaw6k2oQ(%ET#tw zCW+SOk3VRqwKA-Gi)%@eC45}Xj>hyvv`tnV21Ynzk*aW^_(x^3x5%2g`T0dD`3rFE{i_ zg`$`qL+P&JYUcN>c8z8k^1dG5ps9lErlaYg<-9Ya<5(|q5xTkhrgp)`h_CYY}z$sM1!OHHf*9fBIMOrAoZJs0A=r90{1 z^&)h!_h3A`H`PnQpboU_zUOQ`h{V_1Qj|RY9l0>-NiXw>zPQj{UaXuzxy}D)9UW-a z%idQ7QDMn0z}u93zv*BIYP3F9PHE7n)yS?oyd_^`W6_LI3?LP!&jvJ9Zbh+)7d6F% zlqtB3I*=A{IL_kfu&o@nEU3L!8V{P1iGXnQqss4e!dtNe+bot^IVH<|FD$4LkB zgDt`rtQPY4nkWB-U6=JRbaEW4Kf$^++K2tz-G#jW>B2kGhQY-Q*E!Y1^J9hX*c%Z% zc1);8HXQFaSedZH|Eho^^6Ycd6^YT%*wJm_lh}C4BAeP7&|Hp)@F^SrhnjJru1tzh z33wq8oZJrH!H|Eg&~&d4HMqf1NNy42$AN#0O40#Pm~szdkQMJv=`mlw;3!%7w-f3dDsA`MeJ0S==OWCd(dytnyEy$#dotw8OY$$2T}}>I;-MsDHMx*Lk8XD%KGqH&@j@W73w;H!N=9%}iWPM2v!- zv9Q3_;8NFm;i$6_FFgA9(EnTH8UU%D@Z_#2l-r0{^$bU47SQ&7sDf$3YA2X-1kn>& zkR}C0-boB|j)S!2KOKgIb2w34391b2Hh9>B_cUG8?%C@mK(%#zugT;X=@B;!i&a2j z4n?XGO@=9EZUV@qg)4(oyh$8xkrDQ>mh~B@28sv zKB4^c3%Fdkx2+cj_uT(qYoq&~SU+t2%;~(2yVNXJ&i!auoblVAn0KD{uRfUFdX+q!=1TWMZV+eCQOlwCBc-n2MY7etrz+V0HCH`&ocnMFHocT&m53Y+_UU_Bo$EcS690UU`py51r_jtF9#UVg#z=Az0Nu-Xzz z%zUw;fpp@^I!f%&z*+0lcn+&_08N=!h?H>~0QZ59E z;T5W5yhc^s-SDCF-b^auN~yncF--yjT)g=%8m5@jwW0RJ)1%r;uKpfTir~N2fCXGIFgrf_kF%1mxCZDYXfe>cC6>ldV#%{W;! z!LNb1qZi+nr26zvp{+Y^Z>Fqe=pi*xZz?a!bscePOoSnwKqUuilaY?W#5qx5rhX_I$OD_ z-{%@M?ML~S*8hDPWwEt7%jqugzm82&gP6)s;cNV>A^lez7>tvkv*oLh9yo{&?u8t2 zW`;~^N}|3Qlb)*p7?l}^w`+ZZ;Ege_=&N$Su0DiTXm;DVel|!C`^J4$f*b4>N$HFU z3gAVXTLk*o4;QVNE{ z9fg<07EK6~wCKr&Gvph*b#KnLLVLlSI2J520)Fh_kpyFDD6f2Ps-Z0YU|_`%UZc~f zKe_uj3r?lEbW-~|=phN&pBBMoMQt5kg_}z%s1;EoX z56>H`fuM5R$)k<$jdPVB^>Q}c^x9*|zX_4H3t-u+!FC@J_nBcB@aR(fg(X{E1Z)h@ zN{4^BfGt$(CE8Z(pvzd)99h)sunLBM-ZJn*b3wqGuX=LMv}NMlQz=DjBG;0=VXgn` z8X916OVp9C_>{%hb)0#I6usIWYVoPBl)pW)kDzE!ZcJOVhBqxK5t)^uPg3zxMPEw4 z1eJ)I3_?3AIn6S}D5VBQQg`^o)cf}l!F^4$|I>un)og^s=NpO07WRk?+om(Un9ScXepbucub?(U%B}ASC1$aLvnoGQYANY436uknYsFFWjfApMAfvpscWvp7-taDTgc zy%_Sf)jTTOWly_`7giux^x)x(N6jMmn#|+dxaKF$=|>9f361<;pD7ywR{=-rgpCp_ zQF|5oIA4%HGr;++shwdzDYe%MW7?=zwK^q&s3pqL%w^mB^VeoI=WflxX+;_1v zDkEo|4x*@Ej$Do=Zw-@%7vPx)LJY&AnZw~7>v_&DC*UKl9U>3f6MO^bMvPUc_V>1wPfv_QX(Sc@Br{3hcuS zB9oe6`7bc<=kLK5}$!y1v3hs zlO!_AN5U^96Z%me+9Oz^lq{B!Cv`>=<>tAe^@xvW5ZJxn1Jg3Gtzq?|Hd-+zz~S?V zZRo{uF=;a(tFAO_`AMVyGxh7IBBMXAqz@W=jve$2J`Bw*5h)Ae2jPIpv6EpoFj`54 zB!BE~3vF*0(Zl!y;{Kd54EN+h!TUy#gZ?BU#2W9eY6mod`AvNwp#Wx)*??#~lJG+H zv#HRmNxT!`9fO4L+M@hv0}y}ZC0@RFS?l?Dn09FLY!}4K23uZ(imrZ}*$!)C$z`%~FDTd$uu=qI&tr;Y;|7cVlYeLU z)W+{1)Kg#_31(5q{x-0ZlvoowNu`IbGJ+6u$T2!?<`ujXJjo0lDtWJ93Z~7!PIW$w zQ`AXJZ)^x1H6h?p4K|&Zr~Wdq_Z2}58F~R>)hU4!B_Jnp=3GLLa58l*GOI2+fImi} zq1Vf+NSFsvDtrRY-md|9UJ3MW-o}DCLDQfOh-{$X2ZVB%6)W$@MY%)%*m>q`^|gU2 z(lWCubQf~R0nk7OUO@CglH1b8?N)fLmi>$gWsxw(9c37Cfh`vn6XxC^;jzMN>xdfD zjD-^K*=CXa7aMM0ABU@BAUV~JovGI&QaQx8d-WkwO(vhIZb(2-Dz1(#&Z6umr2_Gw zt*=Tbcxxc{9Mkc2QyqC>cxAAYR1vWGV}I()k*!GHG6c{<_>GOS|2574Dwu+e%I=e1 zH^FH28Eb7IGw+`*()@THZoy9uL1z2iurAgOs@GB#VU%8^q5Z|`PJbn7w^WBuPMeSf zEbTbuRH&VmzRI2L8#E1gm5rOvGr#tgBq7ea8mM`|Z*R@N!|)G7r=vct*j$hzL+s`v z5}-rJFXaqNBcpy2aRZvWDkv%KUFPiiattM%hMG1DOhr*BA$&DxFL@mc8#m5d-ZWQr zLC{GsCSNfic$1|&sWWSyJZ+4}lA&HdiN8C#vuW$Mv>C>tYF9%PneT%+EVWg=ElDI##*<1mQEyIiPWll)# zD~3|h`7@*x-)d9`tKb#FeBaj@ggm9}sr+z?=#(Y-tI#`M5s~ulLt6HX87aM)v|b6? z_DVPoI7fJ`hP?)bd`}iLhTLB{PE3C$wXB2;W{~l0DPHdQM=q86e8@nI+ik|PL4Td+ z2A5YklqFd|@LR2gdRfGm-es0$$bOx7*u67T+x{PMa2Me(MqF7XK-S}c5IQxV{KW|y zWY zyFfz2nU&l}$ZC8{7p%@fdu!ue#gF4zfL>Wm-?mK(Lf7P7T@Lj()D8PbT|I!blEtVM z&U#P2xXiR}jNH)W#ud3-V5TA7h_EWfboievg5IH z!M=(91N%}~hpWEylB%0n*So;_d(a@XjH&0yOm`3@2KA^zWk;^jH6-dQevK#@fw?N} zijk0)L;bLDi`DTN6zR)YwL{PM`76e3^o#6Kp*ohNLgS3UI17@cUG$(8{LQBFa*6r||3`O`nca9J~o}7nslA&^ZBj4sRtvVI@nmo*9PARjVpWzg#{sI)a?eXxa)M<6<93=q`ENwq0k6_Yx zvNDsUX>EJini?RHiLuB#*$uKpW0eiB`gc*HlIDQkzyaz8z*UtBNRk2F_gyU@2fRVFC=O`fF zv*@5PtEobkj_czCSVKE!yFyf{jZSGw%n{RN`hM0TUzJf{cCE25BjJNKlTFtqCfy8~ zpGV|6DYUt}=vsbIrDk%s1bd@K4V`M5jw4QfU44NdqpB67_ANX+!~;~rO)dxB1d2P1 zwDEifw(gp!v^j?>h-d8Grtq^l|@~7T)k|ysT2+BwyluwVsj*u7}2@LrR&}2|+73 z%w=S}7nB$$BuCiA$Ca)R!Yf~W36>W?ZLW1kS&>T2vd__+FWjtCffjzr%FC+Pi-HnC@gkL z+A-{)-+Q!$=A^2G7**+EQeWBtD%uW{L@1vBX_{Q}BCceK!k3pqOJABG_HMZHUEOdoB#_OW{JaeRkc z=hAoS+J4@=!RI<*3YI=%wb5PIy7jM`(2My9ZOq=%*-AZj$e5yf8&njFu#;DxpodD7iVfhhmpkva2TO-S#qN|l)pmh+wtM)9FTVF>!(+)mRQ?g5RiM_Uo zR!5yx80?g|?teuA@v%G%`js`KWTknm$IvC16B8zq$=IQS*ts zIiK8)yVYGTd?%}f@h5(=dVaOsbD*}a0}p=6e}pUvteEJaHuk-0`NDt)1Q}x>qik# z)29eBGS{ds6cs)E)`{>-A>Wc}Sdmd6=!Kv#Uv7TK>w(>DmM?u2IOAz>B*Epxb-Y+5kxgF)DF4;jn-~M~bIkD^GtL)}ZeSz{<)c8-=aAqV z#&(y+;iwJUQZyXPR9D-I*Malr)r~?(u=^u+SdW0CMx|{-Iis9o*^fc7Z*98ap`4u& z00IyKit`e z;k&KgqR=ip?6&b0Ek@27$6z5d7`mgQJP)ROY$%4ra-C?3B%23n-4a*E${t{2=7d5N=7l=kohcP;|ZV>b^S!OsB-cq^k)MtM2~rGO1>LG&`qx0eZt+#Z*51uGgNr zv6N4|89rt0$6SBxv+GyUu*WYwkj}yVDw@pA$K}%wwSE=BY*Tt4hzQa`j2Yen`>8;LaDX7lQcB92xpBB;7J>ke*n9{F)f=5H{#JODe>(xdzXq2Mpht|M-&4htLu5rwNSSee{jA2gp>pVEL zul!pSrO>6~C&~ZjSK3IrYsQvs^(=Ieb-Gn^0}sL4J_mGVt%#^?7db5 z%$qvmXwSxKSwhHV^u?DnGiL*OPIc1M6NflCvU>LNSi0zwD0_B073ow{V?78*0(7v0 z?~HGiT#{?B%CGyIIC)6=9C4}T5F4o`$P!%)#5}eS2jz1=&Aiu79z@L-n+>p+370$I z&~*&+1r*E9758zHxRo2%7Y0WyCz8R{yHWGCqID(fA8m^)?~W?@V>HZ0Dx))FjD0bZ zw(^qjS3!ikaEM%`!wf9+9#HV!MZzz*yTCs?!lRfoONp89TRw>^U({Sl_sG*jqpPJP zLhak{&|>@mzqZ_WBy}w2xR9WoxoriS{a-~gC3A7@!_p5U@|@tF%Rr2LiThD_NL>?h z+7z;b!V|MIE$0;wm6U}`H60c=ROawknjB$5t(wU8eBfAzCgEux?YW%E(m~zSJPym0 ztdEtO)n*DUi*h>aEN)iZZTz6X^h}#D07=zRcYeH@_Gw^r#6PgZPS9|?fuwFI$Ei`O+Q$=YW;=?U{b@(m18DJYwnTTX@SnWp(;Y*#7tO`EI`^wLB)h~@@pLA~?Hanp6 zDbphGd~bcN!O;;`oJ$74uig7S%C&$7343MNc$FW2;@dL!&GKSD4=*Kf)K`GT3_idF9hn&5u+a>QM%h{7A zuFpFF^D5C9qUt$S80*s~V;;k8RiLKXI^080cL#X3oF}D-(h+Z33kigwS%Mt|R-pa- z9?zq`F*s*F_D(k_HkkIoYwZi%3fHaxoS#P`xtEaBcm#HUoFaQaD^hX#We)Q!$hyW9 z;E&-Hpr))v@J;T2L6j`~oA3aXRAFNtnz34)EEd$|)U9f&KQmzV30kGMwPod12-JdT zyzZ(2>c{e_gh1SeY_JQ1!F9^li{;!kN9? z=}Q!K_{!Lrbgh61IO(|n!#E6taXhHP0 at{iLsq(N(V1Y?5g`FFwGqW5iDCW3ZxVP)9> literal 0 HcmV?d00001 diff --git a/lib/form/tests/duration_test.php b/lib/form/tests/duration_test.php new file mode 100644 index 0000000000000..4a345fc2a198d --- /dev/null +++ b/lib/form/tests/duration_test.php @@ -0,0 +1,127 @@ +. + + +/** + * Unit tests for forms lib. + * + * This file contains all unit test related to forms library. + * + * @package core_form + * @category phpunit + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/form/duration.php'); + +/** + * Unit tests for MoodleQuickForm_duration + * + * Contains test cases for testing MoodleQuickForm_duration + * + * @package core_form + * @category unittest + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class duration_form_element_testcase extends basic_testcase { + /** @var MoodleQuickForm_duration Keeps reference of MoodleQuickForm_duration object */ + private $element; + + /** + * Initalize test wide variable, it is called in start of the testcase + */ + function setUp() { + $this->element = new MoodleQuickForm_duration(); + } + + /** + * Clears the data set in the setUp() method call. + * @see duration_form_element_test::setUp() + */ + function tearDown() { + $this->element = null; + } + + /** + * Testcase for testing contructor. + * @expectedException coding_exception + * @retrun void + */ + function test_constructor() { + // Test trying to create with an invalid unit. + $this->element = new MoodleQuickForm_duration('testel', null, array('defaultunit' => 123)); + } + + /** + * Testcase for testing units (seconds, minutes, hours and days) + */ + function test_get_units() { + $units = $this->element->get_units(); + ksort($units); + $this->assertEquals($units, array(1 => get_string('seconds'), 60 => get_string('minutes'), + 3600 => get_string('hours'), 86400 => get_string('days'))); + } + + /** + * Testcase for testing conversion of seconds to the best possible unit + */ + function test_seconds_to_unit() { + $this->assertEquals($this->element->seconds_to_unit(0), array(0, 60)); // Zero minutes, for a nice default unit. + $this->assertEquals($this->element->seconds_to_unit(1), array(1, 1)); + $this->assertEquals($this->element->seconds_to_unit(3601), array(3601, 1)); + $this->assertEquals($this->element->seconds_to_unit(60), array(1, 60)); + $this->assertEquals($this->element->seconds_to_unit(180), array(3, 60)); + $this->assertEquals($this->element->seconds_to_unit(3600), array(1, 3600)); + $this->assertEquals($this->element->seconds_to_unit(7200), array(2, 3600)); + $this->assertEquals($this->element->seconds_to_unit(86400), array(1, 86400)); + $this->assertEquals($this->element->seconds_to_unit(90000), array(25, 3600)); + + $this->element = new MoodleQuickForm_duration('testel', null, array('defaultunit' => 86400)); + $this->assertEquals($this->element->seconds_to_unit(0), array(0, 86400)); // Zero minutes, for a nice default unit. + } + + /** + * Testcase to check generated timestamp + */ + function test_exportValue() { + $el = new MoodleQuickForm_duration('testel'); + $el->_createElements(); + $values = array('testel' => array('number' => 10, 'timeunit' => 1)); + $this->assertEquals($el->exportValue($values), array('testel' => 10)); + $values = array('testel' => array('number' => 3, 'timeunit' => 60)); + $this->assertEquals($el->exportValue($values), array('testel' => 180)); + $values = array('testel' => array('number' => 1.5, 'timeunit' => 60)); + $this->assertEquals($el->exportValue($values), array('testel' => 90)); + $values = array('testel' => array('number' => 2, 'timeunit' => 3600)); + $this->assertEquals($el->exportValue($values), array('testel' => 7200)); + $values = array('testel' => array('number' => 1, 'timeunit' => 86400)); + $this->assertEquals($el->exportValue($values), array('testel' => 86400)); + $values = array('testel' => array('number' => 0, 'timeunit' => 3600)); + $this->assertEquals($el->exportValue($values), array('testel' => 0)); + + $el = new MoodleQuickForm_duration('testel', null, array('optional' => true)); + $el->_createElements(); + $values = array('testel' => array('number' => 10, 'timeunit' => 1)); + $this->assertEquals($el->exportValue($values), array('testel' => 0)); + $values = array('testel' => array('number' => 20, 'timeunit' => 1, 'enabled' => 1)); + $this->assertEquals($el->exportValue($values), array('testel' => 20)); + } +} diff --git a/lib/mathslib.php b/lib/mathslib.php index bc0b38d6665de..da187930b771b 100644 --- a/lib/mathslib.php +++ b/lib/mathslib.php @@ -50,7 +50,7 @@ class calc_formula { */ function calc_formula($formula, $params=false) { $this->_em = new EvalMath(); - $this->_em->suppress_errors = !debugging('', DEBUG_DEVELOPER); + $this->_em->suppress_errors = true; // no PHP errors! if (strpos($formula, '=') !== 0) { $this->_error = "missing leading '='"; return; diff --git a/lib/moodlelib.php b/lib/moodlelib.php index db18276dcfe8f..bd3be093eb7ca 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -10755,17 +10755,22 @@ public function __construct($identifier, $component = '', $a = null, $lang = nul $this->a = $a; } else if ($a instanceof lang_string) { $this->a = $a->out(); - } else if (is_object($a)) { - $this->a = new stdClass; - foreach (get_object_vars($a) as $key => $value) { - // Make sure conversion errors don't get displayed (results in '') - $this->a->$key = @(string)$value; - } - } else if (is_array($a)) { + } else if (is_object($a) or is_array($a)) { + $a = (array)$a; $this->a = array(); foreach ($a as $key => $value) { // Make sure conversion errors don't get displayed (results in '') - $this->a[$key] = @(string)$value; + if (is_array($value)) { + $this->a[$key] = ''; + } else if (is_object($value)) { + if (method_exists($value, '__toString')) { + $this->a[$key] = $value->__toString(); + } else { + $this->a[$key] = ''; + } + } else { + $this->a[$key] = (string)$value; + } } } } diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php index 1530c005e51e0..ba62a35b975ff 100644 --- a/lib/phpunit/bootstrap.php +++ b/lib/phpunit/bootstrap.php @@ -22,7 +22,8 @@ * 1 - general error * 130 - coding error * 131 - configuration problem - * 132 - drop data, then install new test database + * 132 - install new test database + * 133 - drop old data, then install new test database * * @package core * @category phpunit @@ -36,13 +37,24 @@ ini_set('log_errors', '1'); if (isset($_SERVER['REMOTE_ADDR'])) { - phpunit_bootstrap_error('Unit tests can be executed only from commandline!', 1); + phpunit_bootstrap_error('Unit tests can be executed only from command line!', 1); } -if (defined('PHPUNITTEST')) { - phpunit_bootstrap_error("PHPUNITTEST constant must not be manually defined anywhere!", 130); +if (defined('PHPUNIT_TEST')) { + phpunit_bootstrap_error("PHPUNIT_TEST constant must not be manually defined anywhere!", 130); +} +/** PHPUnit testing framework active */ +define('PHPUNIT_TEST', true); + +if (!defined('PHPUNIT_LONGTEST')) { + /** Execute longer version of tests */ + define('PHPUNIT_LONGTEST', false); +} + +if (!defined('PHPUNIT_UTIL')) { + /** Identifies utility scripts */ + define('PHPUNIT_UTIL', false); } -define('PHPUNITTEST', true); if (defined('CLI_SCRIPT')) { phpunit_bootstrap_error('CLI_SCRIPT must not be manually defined in any PHPUnit test scripts', 130); @@ -56,9 +68,10 @@ require(__DIR__ . '/../../config.php'); // remove error handling overrides done in config.php -error_reporting(E_ALL); +error_reporting(E_ALL | E_STRICT); ini_set('display_errors', '1'); ini_set('log_errors', '1'); +set_time_limit(0); // no time limit in CLI scripts, user may cancel execution // prepare dataroot umask(0); @@ -113,7 +126,10 @@ // verify db prefix if (!isset($CFG->phpunit_prefix)) { - phpunit_bootstrap_error('Missing $CFG->phpunit_dataroot in config.php, can not run tests!', 131); + phpunit_bootstrap_error('Missing $CFG->phpunit_prefix in config.php, can not run tests!', 131); +} +if ($CFG->phpunit_prefix === '') { + phpunit_bootstrap_error('$CFG->phpunit_prefix can not be empty, 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); @@ -141,12 +157,14 @@ unset($productioncfg); // force the same CFG settings in all sites -$CFG->debug = (E_ALL | E_STRICT | 38911); // can not use DEBUG_DEVELOPER here +$CFG->debug = (E_ALL | E_STRICT); // can not use DEBUG_DEVELOPER yet $CFG->debugdisplay = 1; error_reporting($CFG->debug); ini_set('display_errors', '1'); ini_set('log_errors', '0'); +$CFG->passwordsaltmain = 'phpunit'; // makes login via normal UI impossible + $CFG->noemailever = true; // better not mail anybody from tests, override temporarily if necessary $CFG->cachetext = 0; // disable this very nasty setting @@ -163,20 +181,27 @@ raise_memory_limit(MEMORY_EXTRA); -if (defined('PHPUNIT_CLI_UTIL')) { - // all other tests are done in the CLI scripts... +if (PHPUNIT_UTIL) { + // we are not going to do testing, this is 'true' in utility scripts that init database usually 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); -} +// is database and dataroot ready for testing? +$problem = phpunit_util::testing_ready_problem(); -// refresh data in all tables, clear caches, etc. -phpunit_util::reset_all_data(); +if ($problem) { + switch ($problem) { + case 132: + phpunit_bootstrap_error('Database was not initialised to run unit tests, please use "php admin/tool/phpunit/cli/util.php --install"', $problem); + case 133: + phpunit_bootstrap_error('Database was initialised for different version, please use "php admin/tool/phpunit/cli/util.php --drop; php admin/tool/phpunit/cli/util.php --install"', $problem); + default: + phpunit_bootstrap_error('Unknown problem initialising test database', $problem); + } +} -// store fresh globals -phpunit_util::init_globals(); +// prepare for the first test run - store fresh globals, reset dataroot, etc. +phpunit_util::bootstrap_init(); //========================================================= diff --git a/lib/phpunit/generatorlib.php b/lib/phpunit/generatorlib.php new file mode 100644 index 0000000000000..07242a86bb5b2 --- /dev/null +++ b/lib/phpunit/generatorlib.php @@ -0,0 +1,360 @@ +. + +/** + * PHPUnit data generator class + * + * @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 + */ + +defined('MOODLE_INTERNAL') || die(); + +class phpunit_data_generator { + protected $usercounter = 0; + protected $categorycount = 0; + protected $coursecount = 0; + protected $blockcount = 0; + protected $modulecount = 0; + + /** + * To be called from data reset code only, + * do not use in tests. + * @return void + */ + public function reset() { + $this->usercounter = 0; + $this->categorycount = 0; + $this->coursecount = 0; + $this->blockcount = 0; + $this->modulecount = 0; + } + + /** + * Create a test user + * @param array|stdClass $record + * @param array $options + * @return stdClass + */ + public function create_user($record=null, array $options=null) { + global $DB, $CFG; + + $this->usercounter++; + $i = $this->usercounter; + + $record = (array)$record; + + if (!isset($record['auth'])) { + $record['auth'] = 'manual'; + } + + if (!isset($record['firstname'])) { + $record['firstname'] = 'Firstname'.$i; + } + + if (!isset($record['lastname'])) { + $record['lastname'] = 'Lastname'.$i; + } + + if (!isset($record['idnumber'])) { + $record['idnumber'] = ''; + } + + if (!isset($record['username'])) { + $record['username'] = 'username'.$i; + } + + if (!isset($record['password'])) { + $record['password'] = 'lala'; + } + + if (!isset($record['email'])) { + $record['email'] = $record['username'].'@example.com'; + } + + if (!isset($record['confirmed'])) { + $record['confirmed'] = 1; + } + + if (!isset($record['mnethostid'])) { + $record['mnethostid'] = $CFG->mnet_localhost_id; + } + + if (!isset($record['lang'])) { + $record['lang'] = 'en'; + } + + if (!isset($record['maildisplay'])) { + $record['maildisplay'] = 1; + } + + if (!isset($record['deleted'])) { + $record['deleted'] = 0; + } + + $record['timecreated'] = time(); + $record['timemodified'] = $record['timecreated']; + $record['lastip'] = '0.0.0.0'; + + $record['password'] = hash_internal_user_password($record['password']); + + if ($record['deleted']) { + $delname = $record['email'].'.'.time(); + while ($DB->record_exists('user', array('username'=>$delname))) { + $delname++; + } + $record['idnumber'] = ''; + $record['email'] = md5($record['username']); + $record['username'] = $delname; + } + + $userid = $DB->insert_record('user', $record); + if (!$record['deleted']) { + context_user::instance($userid); + } + + return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST); + } + + /** + * Create a test course category + * @param array|stdClass $record + * @param array $options + * @return stdClass + */ + function create_category($record=null, array $options=null) { + global $DB, $CFG; + require_once("$CFG->dirroot/course/lib.php"); + + $this->categorycount++; + $i = $this->categorycount; + + $record = (array)$record; + + if (!isset($record['name'])) { + $record['name'] = 'Course category '.$i; + } + + if (!isset($record['idnumber'])) { + $record['idnumber'] = ''; + } + + if (!isset($record['description'])) { + $record['description'] = 'Test course category '.$i; + } + + if (!isset($record['descriptionformat'])) { + $record['description'] = FORMAT_MOODLE; + } + + if (!isset($record['parent'])) { + $record['descriptionformat'] = 0; + } + + if ($record['parent'] == 0) { + $parent = new stdClass(); + $parent->path = ''; + $parent->depth = 0; + } else { + $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST); + } + $record['depth'] = $parent->depth+1; + + $record['sortorder'] = 0; + $record['timemodified'] = time(); + $record['timecreated'] = $record['timemodified']; + + $catid = $DB->insert_record('course_categories', $record); + $path = $parent->path . '/' . $catid; + $DB->set_field('course_categories', 'path', $path, array('id'=>$catid)); + context_coursecat::instance($catid); + + fix_course_sortorder(); + + return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST); + } + + /** + * Create a test course + * @param array|stdClass $record + * @param array $options + * @return stdClass + */ + function create_course($record=null, array $options=null) { + global $DB, $CFG; + require_once("$CFG->dirroot/course/lib.php"); + + $this->coursecount++; + $i = $this->coursecount; + + $record = (array)$record; + + if (!isset($record['fullname'])) { + $record['fullname'] = 'Test course '.$i; + } + + if (!isset($record['shortname'])) { + $record['shortname'] = 'tc_'.$i; + } + + if (!isset($record['idnumber'])) { + $record['idnumber'] = ''; + } + + if (!isset($record['format'])) { + $record['format'] = 'topics'; + } + + if (!isset($record['newsitems'])) { + $record['newsitems'] = 0; + } + + if (!isset($record['numsections'])) { + $record['numsections'] = 5; + } + + if (!isset($record['description'])) { + $record['description'] = 'Test course '.$i; + } + + if (!isset($record['descriptionformat'])) { + $record['description'] = FORMAT_MOODLE; + } + + if (!isset($record['category'])) { + $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0"); + } + + $course = create_course((object)$record); + context_course::instance($course->id); + + return $course; + } + + /** + * Create a test block + * @param string $blockname + * @param array|stdClass $record + * @param array $options + * @return stdClass + */ + public function create_block($blockname, $record=null, array $options=null) { + global $DB; + + $this->blockcount++; + $i = $this->blockcount; + + $record = (array)$record; + + $record['blockname'] = $blockname; + + //TODO: use block callbacks + + if (!isset($record['parentcontextid'])) { + $record['parentcontextid'] = context_system::instance()->id; + } + + if (!isset($record['showinsubcontexts'])) { + $record['showinsubcontexts'] = 1; + } + + if (!isset($record['pagetypepattern'])) { + $record['pagetypepattern'] = ''; + } + + if (!isset($record['subpagepattern'])) { + $record['subpagepattern'] = ''; + } + + if (!isset($record['defaultweight'])) { + $record['defaultweight'] = ''; + } + + $biid = $DB->insert_record('block_instances', $record); + context_block::instance($biid); + + return $DB->get_record('block_instances', array('id'=>$biid), '*', MUST_EXIST); + } + + /** + * Create a test module + * @param string $modulename + * @param array|stdClass $record + * @param array $options + * @return stdClass + */ + public function create_module($modulename, $record=null, array $options=null) { + global $DB, $CFG; + require_once("$CFG->dirroot/course/lib.php"); + + $this->modulecount++; + $i = $this->modulecount; + + $record = (array)$record; + $options = (array)$options; + + if (!isset($record['name'])) { + $record['name'] = get_string('pluginname', $modulename).' '.$i; + } + + if (!isset($record['intro'])) { + $record['intro'] = 'Test module '.$i; + } + + if (!isset($record['introformat'])) { + $record['introformat'] = FORMAT_MOODLE; + } + + if (!isset($options['section'])) { + $options['section'] = 1; + } + + //TODO: use module callbacks + + if ($modulename === 'page') { + if (!isset($record['content'])) { + $record['content'] = 'Test page content'; + } + if (!isset($record['contentformat'])) { + $record['contentformat'] = FORMAT_MOODLE; + } + + } else { + error('TODO: only mod_page is supported in data generator for now'); + } + + $id = $DB->insert_record($modulename, $record); + + $cm = new stdClass(); + $cm->course = $record['course']; + $cm->module = $DB->get_field('modules', 'id', array('name'=>$modulename)); + $cm->section = $options['section']; + $cm->instance = $id; + $cm->id = $DB->insert_record('course_modules', $cm); + + $cm->coursemodule = $cm->id; + add_mod_to_section($cm); + + context_module::instance($cm->id); + + $instance = $DB->get_record($modulename, array('id'=>$id), '*', MUST_EXIST); + $instance->cmid = $cm->id; + + return $instance; + } +} diff --git a/lib/phpunit/lib.php b/lib/phpunit/lib.php index 6c01f37484e0d..4c2c0ed8959d6 100644 --- a/lib/phpunit/lib.php +++ b/lib/phpunit/lib.php @@ -48,6 +48,29 @@ class phpunit_util { */ protected static $globals = array(); + /** + * @var int last value of db writes counter, used for db resetting + */ + protected static $lastdbwrites = null; + + /** + * @var phpunit_data_generator + */ + protected static $generator = null; + + /** + * Get data generator + * @static + * @return phpunit_data_generator + */ + public static function get_data_generator() { + if (is_null(self::$generator)) { + require_once(__DIR__.'/generatorlib.php'); + self::$generator = new phpunit_data_generator(); + } + return self::$generator; + } + /** * Returns contents of all tables right after installation. * @static @@ -56,6 +79,11 @@ class phpunit_util { protected static function get_tabledata() { global $CFG; + if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) { + // not initialised yet + return array(); + } + if (!isset(self::$tabledata)) { $data = file_get_contents("$CFG->dataroot/phpunit/tabledata.ser"); self::$tabledata = unserialize($data); @@ -69,35 +97,57 @@ protected static function get_tabledata() { } /** - * Initialise CFG using data from fresh new install. + * Reset all database tables to default values. * @static + * @param bool $logchanges + * @param null|PHPUnit_Framework_TestCase $caller + * @return bool true if reset done, false if skipped */ - public static function initialise_cfg() { - global $CFG, $DB; - - if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) { - // most probably PHPUnit CLI installer - return; + public static function reset_database($logchanges = false, PHPUnit_Framework_TestCase $caller = null) { + global $DB; + + if ($logchanges) { + if (self::$lastdbwrites != $DB->perf_get_writes()) { + if ($caller) { + $where = ' in testcase: '.get_class($caller).'->'.$caller->getName(true); + } else { + $where = ''; + } + error_log('warning: unexpected database modification, resetting DB state'.$where); + } } - 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; + $tables = $DB->get_tables(false); + if (!$tables or empty($tables['config'])) { + // not installed yet 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; + $dbreset = false; + if (is_null(self::$lastdbwrites) or self::$lastdbwrites != $DB->perf_get_writes()) { + if ($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(); + $dbreset = true; } - $CFG->{$name} = $value; } + + self::$lastdbwrites = $DB->perf_get_writes(); + + return $dbreset; } /** @@ -105,47 +155,101 @@ public static function initialise_cfg() { * * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care! * + * @param bool $logchanges log changes in global state and database in error log + * @param PHPUnit_Framework_TestCase $caller caller object, used for logging only + * @return void * @static */ - public static function reset_all_data() { - global $DB, $CFG; + public static function reset_all_data($logchanges = false, PHPUnit_Framework_TestCase $caller = null) { + global $DB, $CFG, $USER; - $data = self::get_tabledata(); + $dbreset = self::reset_database($logchanges, $caller); - $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'); + if ($logchanges) { + if ($caller) { + $where = ' in testcase: '.get_class($caller).'->'.$caller->getName(true); + } else { + $where = ''; + } + + $oldcfg = self::get_global_backup('CFG'); + foreach($CFG as $k=>$v) { + if (!property_exists($oldcfg, $k)) { + error_log('warning: unexpected new $CFG->'.$k.' value'.$where); + } else if ($oldcfg->$k !== $CFG->$k) { + error_log('warning: unexpected change of $CFG->'.$k.' value'.$where); + } + unset($oldcfg->$k); + + } + if ($oldcfg) { + foreach($oldcfg as $k=>$v) { + error_log('warning: unexpected removal of $CFG->'.$k.$where); } - $DB->import_record($table, $record, false, true); } - if ($resetseq === true) { - $DB->get_manager()->reset_sequence($table, true); + + if ($USER->id != 0) { + error_log('warning: unexpected change of $USER'.$where); } } - $trans->allow_commit(); - purge_all_caches(); + // restore original config + $CFG = self::get_global_backup('CFG'); + // set fresh new user $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(); + + // reset all static caches + accesslib_clear_all_caches(true); + get_string_manager()->reset_caches(); + //TODO: add more resets here and probably refactor them to new core function + + // purge dataroot + $handle = opendir($CFG->dataroot); + $skip = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess'); + while (false !== ($item = readdir($handle))) { + if (in_array($item, $skip)) { + continue; + } + if (is_dir("$CFG->dataroot/$item")) { + remove_dir("$CFG->dataroot/$item", false); + } else { + unlink("$CFG->dataroot/$item"); + } + } + closedir($handle); + make_temp_directory(''); + make_cache_directory(''); + make_cache_directory('htmlpurifier'); + + // restore original config once more in case resetting of caches changes CFG + $CFG = self::get_global_backup('CFG'); + + // remember db writes + self::$lastdbwrites = $DB->perf_get_writes(); + + // inform data generator + self::get_data_generator()->reset(); + + // fix PHP settings + error_reporting($CFG->debug); } /** * Called during bootstrap only! * @static */ - public static function init_globals() { + public static function bootstrap_init() { global $CFG; + // backup the globals self::$globals['CFG'] = clone($CFG); + + // refresh data in all tables, clear caches, etc. + phpunit_util::reset_all_data(); } /** @@ -178,7 +282,7 @@ public static function is_test_site() { 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 + // but anyway presence of this file means the dataroot is for testing return false; } @@ -199,41 +303,41 @@ public static function is_test_site() { * Is this site initialised to run unit tests? * * @static - * @return bool + * @return int error code, 0 means ok */ - public static function is_testing_ready() { + public static function testing_ready_problem() { global $DB, $CFG; if (!self::is_test_site()) { - return false; + return 131; } $tables = $DB->get_tables(true); if (!$tables) { - return false; + return 132; } if (!get_config('core', 'phpunittest')) { - return false; + return 131; } if (!file_exists("$CFG->dataroot/phpunit/tabledata.ser")) { - return false; + return 131; } if (!file_exists("$CFG->dataroot/phpunit/versionshash.txt")) { - return false; + return 131; } $hash = phpunit_util::get_version_hash(); $oldhash = file_get_contents("$CFG->dataroot/phpunit/versionshash.txt"); if ($hash !== $oldhash) { - return false; + return 133; } - return true; + return 0; } /** @@ -296,8 +400,9 @@ public static function install_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')); + // install timezone info + $timezones = get_records_csv($CFG->libdir.'/timezone.txt', 'timezone'); + update_timezone_records($timezones); // add test db flag set_config('phpunittest', 'phpunittest'); @@ -319,7 +424,7 @@ public static function install_site() { } /** - * Culculate unique version hash for all available plugins and core. + * Calculate unique version hash for all available plugins and core. * @static * @return string sha1 hash */ @@ -372,11 +477,9 @@ public static function build_config_file() { global $CFG; $template = ' - @dir@ - - '; + '; $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist"); $suites = ''; @@ -427,6 +530,7 @@ class UnitTestCase extends PHPUnit_Framework_TestCase { * @deprecated since 2.3 * @param bool $expected * @param string $message + * @return void */ public function expectException($expected, $message = '') { // use phpdocs: @expectedException ExceptionClassName @@ -440,6 +544,7 @@ public function expectException($expected, $message = '') { * @deprecated since 2.3 * @param bool $expected * @param string $message + * @return void */ public static function expectError($expected = false, $message = '') { // not available in PHPUnit @@ -454,6 +559,7 @@ public static function expectError($expected = false, $message = '') { * @static * @param mixed $actual * @param string $messages + * @return void */ public static function assertTrue($actual, $messages = '') { parent::assertTrue((bool)$actual, $messages); @@ -464,6 +570,7 @@ public static function assertTrue($actual, $messages = '') { * @static * @param mixed $actual * @param string $messages + * @return void */ public static function assertFalse($actual, $messages = '') { parent::assertFalse((bool)$actual, $messages); @@ -475,6 +582,7 @@ public static function assertFalse($actual, $messages = '') { * @param mixed $expected * @param mixed $actual * @param string $message + * @return void */ public static function assertEqual($expected, $actual, $message = '') { parent::assertEquals($expected, $actual, $message); @@ -486,6 +594,7 @@ public static function assertEqual($expected, $actual, $message = '') { * @param mixed $expected * @param mixed $actual * @param string $message + * @return void */ public static function assertNotEqual($expected, $actual, $message = '') { parent::assertNotEquals($expected, $actual, $message); @@ -497,6 +606,7 @@ public static function assertNotEqual($expected, $actual, $message = '') { * @param mixed $expected * @param mixed $actual * @param string $message + * @return void */ public static function assertIdentical($expected, $actual, $message = '') { parent::assertSame($expected, $actual, $message); @@ -508,6 +618,7 @@ public static function assertIdentical($expected, $actual, $message = '') { * @param mixed $expected * @param mixed $actual * @param string $message + * @return void */ public static function assertNotIdentical($expected, $actual, $message = '') { parent::assertNotSame($expected, $actual, $message); @@ -519,6 +630,7 @@ public static function assertNotIdentical($expected, $actual, $message = '') { * @param mixed $actual * @param mixed $expected * @param string $message + * @return void */ public static function assertIsA($actual, $expected, $message = '') { parent::assertInstanceOf($expected, $actual, $message); @@ -529,7 +641,7 @@ public static function assertIsA($actual, $expected, $message = '') { /** * The simplest PHPUnit test case customised for Moodle * - * This test case does not modify database or any globals. + * It is intended for isolated tests that do not modify database or any globals. * * @package core * @category phpunit @@ -551,51 +663,238 @@ public function __construct($name = NULL, array $data = array(), $dataName = '') $this->setBackupGlobals(false); $this->setBackupStaticAttributes(false); $this->setRunTestInSeparateProcess(false); - $this->setInIsolation(false); } /** - * Runs the bare test sequence. + * Runs the bare test sequence and log any changes in global state or database. * @return void */ public function runBare() { - global $CFG, $USER, $DB; + parent::runBare(); + phpunit_util::reset_all_data(true, $this); + } +} - $dbwrites = $DB->perf_get_writes(); + +/** + * Advanced PHPUnit test case customised for Moodle. + * + * @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 advanced_testcase extends PHPUnit_Framework_TestCase { + /** @var bool automatically reset everything? */ + protected $resetAfterTest; + + /** + * 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); + } + + /** + * Runs the bare test sequence. + * @return void + */ + public function runBare() { + $this->resetAfterTest = null; 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)); + if ($this->resetAfterTest === true) { + self::resetAllData(); + } else if ($this->resetAfterTest === false) { + // keep all data untouched for other tests + } else { + // reset but log what changed + phpunit_util::reset_all_data(true, $this); + } + + $this->resetAfterTest = null; + } + + /** + * Reset everything after current test. + * @param bool $reset true means reset state back, false means keep all data for the next test, + * null means reset state and show warnings if anything changed + * @return void + */ + public function resetAfterTest($reset = true) { + $this->resetAfterTest = $reset; + } + + /** + * Cleanup after all tests are executed. + * + * Note: do not forget to call this if overridden... + * + * @static + * @return void + */ + public static function tearDownAfterClass() { + phpunit_util::reset_all_data(); + } + + /** + * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir. + * @static + * @return void + */ + public static function resetAllData() { + phpunit_util::reset_all_data(); + } + + /** + * Get data generator + * @static + * @return phpunit_data_generator + */ + public static function getDataGenerator() { + return phpunit_util::get_data_generator(); + } + + /** + * Recursively visit all the files in the source tree. Calls the callback + * function with the pathname of each file found. + * + * @param $path the folder to start searching from. + * @param $callback the function to call with the name of each file found. + * @param $fileregexp a regexp used to filter the search (optional). + * @param $exclude If true, pathnames that match the regexp will be ingored. If false, + * only files that match the regexp will be included. (default false). + * @param array $ignorefolders will not go into any of these folders (optional). + * @return void + */ + public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { + $files = scandir($path); + + foreach ($files as $file) { + $filepath = $path .'/'. $file; + if (strpos($file, '.') === 0) { + /// Don't check hidden files. + continue; + } else if (is_dir($filepath)) { + if (!in_array($filepath, $ignorefolders)) { + $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); + } + } else if ($exclude xor preg_match($fileregexp, $filepath)) { + $this->$callback($filepath); } - unset($oldcfg->$k); + } + } +} + +/** + * Test integration of PHPUnit and custom Moodle hacks. + * + * @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 database_driver_testcase extends PHPUnit_Framework_TestCase { + protected static $extradb = null; + protected $skipped = false; + + /** @var moodle_database */ + protected $tdb; + + /** + * 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); + } + + public static function setUpBeforeClass() { + global $CFG; + + if (!defined('PHPUNIT_TEST_DRIVER')) { + // use normal $DB + return; } - 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 (!isset($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER])) { + throw new exception('Can not find driver configuration options with index: '.PHPUNIT_TEST_DRIVER); + } + + $dblibrary = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']) ? 'native' : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dblibrary']; + $dbtype = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbtype']; + $dbhost = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbhost']; + $dbname = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbname']; + $dbuser = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbuser']; + $dbpass = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dbpass']; + $prefix = $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['prefix']; + $dboptions = empty($CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']) ? array() : $CFG->phpunit_extra_drivers[PHPUNIT_TEST_DRIVER]['dboptions']; + + $classname = "{$dbtype}_{$dblibrary}_moodle_database"; + require_once("$CFG->libdir/dml/$classname.php"); + $d = new $classname(); + if (!$d->driver_installed()) { + throw new exception('Database driver for '.$classname.' is not installed'); + } + + $d->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); + + self::$extradb = $d; + } + + public function getStatus() { + if ($this->skipped) { + // fake the status + return PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED; + } else { + return parent::getStatus(); } + } + + public function setUp() { + global $DB; - 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 (self::$extradb) { + $this->tdb = self::$extradb; + } else { + $this->tdb = $DB; } + } - 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(); + public function tearDown() { + // delete all test tables + $dbman = $this->tdb->get_manager(); + $tables = $this->tdb->get_tables(false); + foreach($tables as $tablename) { + if (strpos($tablename, 'test_table') === 0) { + $table = new xmldb_table($tablename); + $dbman->drop_table($table); + } } + } - //TODO: somehow find out if there are changes in dataroot + public static function tearDownAfterClass() { + if (self::$extradb) { + self::$extradb->dispose(); + self::$extradb = null; + } + phpunit_util::reset_all_data(); } -} \ No newline at end of file +} diff --git a/lib/phpunit/readme.md b/lib/phpunit/readme.md index b5fa015671f02..962b8ce2733ec 100644 --- a/lib/phpunit/readme.md +++ b/lib/phpunit/readme.md @@ -22,7 +22,7 @@ Test execution How to add more tests --------------------- 1. create `tests` directory in any plugin -2. add `*_test.php` files with custom class that extends `basic_testcase` +2. add `*_test.php` files with custom class that extends `basic_testcase` or `advanced_testcase` 3. manually add all core unit test locations to `phpunit.xml.dist` @@ -44,6 +44,9 @@ FAQs TODO ---- -* stage 2 - implement advanced_testcase - support for database modifications, object generators, automatic rollback of db, globals 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 +* add plugin callbacks to data generator +* convert remaining tests +* improve performance +* hide old SimpleTests and FUnctional DB tests in UI +* shell script that prepares everything for the first execution +* optionally support for execution of tests and cli/util.php from web UI (to be implemented via shell execution) diff --git a/lib/pluginlib.php b/lib/pluginlib.php index 06fa73e10dd9b..612f9ada2fd96 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -479,7 +479,7 @@ public static function standard_plugins_list($type) { 'tool' => array( 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator', - 'health', 'innodb', 'langimport', 'multilangupgrade', 'profiling', + 'health', 'innodb', 'langimport', 'multilangupgrade', 'phpunit', 'profiling', 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest', 'uploaduser', 'unsuproles', 'xmldb' ), diff --git a/lib/setup.php b/lib/setup.php index d5febeb2c615c..e2fa819eb10ba 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -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('PHPUNITTEST') and PHPUNITTEST) { + if (defined('PHPUNIT_TEST') and PHPUNIT_TEST) { echo('There is a missing "global $CFG;" at the beginning of the config.php file.'."\n"); exit(1); } else { @@ -134,8 +134,8 @@ } // PHPUnit tests need custom init -if (!defined('PHPUNITTEST')) { - define('PHPUNITTEST', false); +if (!defined('PHPUNIT_TEST')) { + define('PHPUNIT_TEST', false); } // Servers should define a default timezone in php.ini, but if they don't then make sure something is defined. @@ -398,11 +398,13 @@ $OUTPUT = new bootstrap_renderer(); // set handler for uncaught exceptions - equivalent to print_error() call -set_exception_handler('default_exception_handler'); -set_error_handler('default_error_handler', E_ALL | E_STRICT); +if (!PHPUNIT_TEST or PHPUNIT_UTIL) { + set_exception_handler('default_exception_handler'); + set_error_handler('default_error_handler', E_ALL | E_STRICT); +} // If there are any errors in the standard libraries we want to know! -error_reporting(E_ALL); +error_reporting(E_ALL | E_STRICT); // Just say no to link prefetching (Moz prefetching, Google Web Accelerator, others) // http://www.google.com/webmasters/faq.html#prefetchblock @@ -461,20 +463,21 @@ // Connect to the database setup_DB(); +// reset DB tables +if (PHPUNIT_TEST and !PHPUNIT_UTIL) { + phpunit_util::reset_database(); +} + // Disable errors for now - needed for installation when debug enabled in config.php if (isset($CFG->debug)) { $originalconfigdebug = $CFG->debug; unset($CFG->debug); } else { - $originalconfigdebug = -1; + $originalconfigdebug = null; } // Load up any configuration from the config table -if (PHPUNITTEST) { - phpunit_util::initialise_cfg(); -} else { - initialise_cfg(); -} +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)) { @@ -495,7 +498,7 @@ $originaldatabasedebug = $CFG->debug; unset($CFG->debug); } else { - $originaldatabasedebug = -1; + $originaldatabasedebug = null; } // enable circular reference collector in PHP 5.3, @@ -510,12 +513,12 @@ } // Set error reporting back to normal -if ($originaldatabasedebug == -1) { +if ($originaldatabasedebug === null) { $CFG->debug = DEBUG_MINIMAL; } else { $CFG->debug = $originaldatabasedebug; } -if ($originalconfigdebug !== -1) { +if ($originalconfigdebug !== null) { $CFG->debug = $originalconfigdebug; } unset($originalconfigdebug); @@ -823,7 +826,9 @@ function stripslashes_deep($value) { } } -if ((CLI_SCRIPT and !defined('WEB_CRON_EMULATED_CLI')) or PHPUNITTEST) { +if (PHPUNIT_TEST) { + // no ip blocking, these are CLI only +} else if (CLI_SCRIPT and !defined('WEB_CRON_EMULATED_CLI')) { // 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 diff --git a/lib/setuplib.php b/lib/setuplib.php index 55d8998f4771a..3085c68fe078f 100644 --- a/lib/setuplib.php +++ b/lib/setuplib.php @@ -141,6 +141,10 @@ function __construct($errorcode, $module='', $link='', $a=NULL, $debuginfo=null) $message = $module . '/' . $errorcode; } + if (PHPUNIT_TEST and $debuginfo) { + $message = "$message ($debuginfo)"; + } + parent::__construct($message, 0); } } diff --git a/lib/simpletest/testexternallib.php b/lib/simpletest/testexternallib.php index 7e1f14a387985..31f1581fe0a99 100644 --- a/lib/simpletest/testexternallib.php +++ b/lib/simpletest/testexternallib.php @@ -20,7 +20,7 @@ * Unit tests for /lib/externallib.php. * * @package webservices - * @copyright 2009 Pwetr Skoda + * @copyright 2009 Petr Skoda * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/lib/tests/accesslib_test.php b/lib/tests/accesslib_test.php new file mode 100644 index 0000000000000..0ceba73b07516 --- /dev/null +++ b/lib/tests/accesslib_test.php @@ -0,0 +1,990 @@ +. + +/** + * Full functional accesslib test + * + * It is implemented as one test case because it would take hours + * to prepare the fake test site for each test, at the same time + * we have to work around multiple problems in UnitTestCaseUsingDatabase. + * + * @package core + * @category phpunit + * @copyright 2011 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Context caching fixture + */ +class context_inspection extends context_helper { + public static function test_context_cache_size() { + return self::$cache_count; + } +} + + +/** + * Functional test for accesslib.php + * + * Note: execution may take many minutes especially on slower servers. + */ +class accesslib_testcase extends advanced_testcase { + + //TODO: add more tests for the remaining accesslib parts that were not touched by the refactoring in 2.2dev + + /** + * Verify comparison of context instances in phpunit asserts + * @return void + */ + public function test_context_comparisons() { + $frontpagecontext1 = context_course::instance(SITEID); + context_helper::reset_caches(); + $frontpagecontext2 = context_course::instance(SITEID); + $this->assertEquals($frontpagecontext1, $frontpagecontext2); + + $user1 = context_user::instance(1); + $user2 = context_user::instance(2); + $this->assertNotEquals($user1, $user2); + } + + /** + * A small functional test of accesslib functions and classes. + * @return void + */ + public function test_everything_in_accesslib() { + global $USER, $SITE, $CFG, $DB, $ACCESSLIB_PRIVATE; + + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + + // Fill the site with some real data + $testcategories = array(); + $testcourses = array(); + $testpages = array(); + $testblocks = array(); + $allroles = $DB->get_records_menu('role', array(), 'id', 'archetype, id'); + + $systemcontext = context_system::instance(); + $frontpagecontext = context_course::instance(SITEID); + + // Add block to system context + $bi = $generator->create_block('online_users'); + context_block::instance($bi->id); + $testblocks[] = $bi->id; + + // Some users + $testusers = array(); + for($i=0; $i<20; $i++) { + $user = $generator->create_user(); + $testusers[$i] = $user->id; + $usercontext = context_user::instance($user->id); + + // Add block to user profile + $bi = $generator->create_block('online_users', array('parentcontextid'=>$usercontext->id)); + $testblocks[] = $bi->id; + } + // Deleted user - should be ignored everywhere, can not have context + $generator->create_user(array('deleted'=>1)); + + // Add block to frontpage + $bi = $generator->create_block('online_users', array('parentcontextid'=>$frontpagecontext->id)); + $frontpageblockcontext = context_block::instance($bi->id); + $testblocks[] = $bi->id; + + // Add a resource to frontpage + $page = $generator->create_module('page', array('course'=>$SITE->id)); + $testpages[] = $page->id; + $frontpagepagecontext = context_module::instance($page->cmid); + + // Add block to frontpage resource + $bi = $generator->create_block('online_users', array('parentcontextid'=>$frontpagepagecontext->id)); + $frontpagepageblockcontext = context_block::instance($bi->id); + $testblocks[] = $bi->id; + + // Some nested course categories with courses + $manualenrol = enrol_get_plugin('manual'); + $parentcat = 0; + for($i=0; $i<5; $i++) { + $cat = $generator->create_category(array('parent'=>$parentcat)); + $testcategories[] = $cat->id; + $catcontext = context_coursecat::instance($cat->id); + $parentcat = $cat->id; + + if ($i >=4) { + continue; + } + + // Add resource to each category + $bi = $generator->create_block('online_users', array('parentcontextid'=>$catcontext->id)); + context_block::instance($bi->id); + + // Add a few courses to each category + for($j=0; $j<6; $j++) { + $course = $generator->create_course(array('category'=>$cat->id)); + $testcourses[] = $course->id; + $coursecontext = context_course::instance($course->id); + + if ($j >= 5) { + continue; + } + // Add manual enrol instance + $manualenrol->add_default_instance($DB->get_record('course', array('id'=>$course->id))); + + // Add block to each course + $bi = $generator->create_block('online_users', array('parentcontextid'=>$coursecontext->id)); + $testblocks[] = $bi->id; + + // Add a resource to each course + $page = $generator->create_module('page', array('course'=>$course->id)); + $testpages[] = $page->id; + $modcontext = context_module::instance($page->cmid); + + // Add block to each module + $bi = $generator->create_block('online_users', array('parentcontextid'=>$modcontext->id)); + $testblocks[] = $bi->id; + } + } + + // Make sure all contexts were created properly + $count = 1; //system + $count += $DB->count_records('user', array('deleted'=>0)); + $count += $DB->count_records('course_categories'); + $count += $DB->count_records('course'); + $count += $DB->count_records('course_modules'); + $count += $DB->count_records('block_instances'); + $this->assertEquals($DB->count_records('context'), $count); + $this->assertEquals($DB->count_records('context', array('depth'=>0)), 0); + $this->assertEquals($DB->count_records('context', array('path'=>NULL)), 0); + + + // ====== context_helper::get_level_name() ================================ + + $levels = context_helper::get_all_levels(); + foreach ($levels as $level=>$classname) { + $name = context_helper::get_level_name($level); + $this->assertFalse(empty($name)); + } + + + // ======= context::instance_by_id(), context_xxx::instance(); + + $context = context::instance_by_id($frontpagecontext->id); + $this->assertSame($context->contextlevel, CONTEXT_COURSE); + $this->assertFalse(context::instance_by_id(-1, IGNORE_MISSING)); + try { + context::instance_by_id(-1); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + $this->assertTrue(context_system::instance() instanceof context_system); + $this->assertTrue(context_coursecat::instance($testcategories[0]) instanceof context_coursecat); + $this->assertTrue(context_course::instance($testcourses[0]) instanceof context_course); + $this->assertTrue(context_module::instance($testpages[0]) instanceof context_module); + $this->assertTrue(context_block::instance($testblocks[0]) instanceof context_block); + + $this->assertFalse(context_coursecat::instance(-1, IGNORE_MISSING)); + $this->assertFalse(context_course::instance(-1, IGNORE_MISSING)); + $this->assertFalse(context_module::instance(-1, IGNORE_MISSING)); + $this->assertFalse(context_block::instance(-1, IGNORE_MISSING)); + try { + context_coursecat::instance(-1); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + try { + context_course::instance(-1); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + try { + context_module::instance(-1); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + try { + context_block::instance(-1); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + + + // ======= $context->get_url(), $context->get_context_name(), $context->get_capabilities() ========= + + $testcontexts = array(); + $testcontexts[CONTEXT_SYSTEM] = context_system::instance(); + $testcontexts[CONTEXT_COURSECAT] = context_coursecat::instance($testcategories[0]); + $testcontexts[CONTEXT_COURSE] = context_course::instance($testcourses[0]); + $testcontexts[CONTEXT_MODULE] = context_module::instance($testpages[0]); + $testcontexts[CONTEXT_BLOCK] = context_block::instance($testblocks[0]); + + foreach ($testcontexts as $context) { + $name = $context->get_context_name(true, true); + $this->assertFalse(empty($name)); + + $this->assertTrue($context->get_url() instanceof moodle_url); + + $caps = $context->get_capabilities(); + $this->assertTrue(is_array($caps)); + foreach ($caps as $cap) { + $cap = (array)$cap; + $this->assertSame(array_keys($cap), array('id', 'name', 'captype', 'contextlevel', 'component', 'riskbitmask')); + } + } + unset($testcontexts); + + // ===== $context->get_course_context() ========================================= + + $this->assertFalse($systemcontext->get_course_context(false)); + try { + $systemcontext->get_course_context(); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + $context = context_coursecat::instance($testcategories[0]); + $this->assertFalse($context->get_course_context(false)); + try { + $context->get_course_context(); + $this->fail('exception expected'); + } catch (Exception $e) { + $this->assertTrue(true); + } + $this->assertSame($frontpagecontext->get_course_context(true), $frontpagecontext); + $this->assertSame($frontpagepagecontext->get_course_context(true), $frontpagecontext); + $this->assertSame($frontpagepageblockcontext->get_course_context(true), $frontpagecontext); + + + // ======= $context->get_parent_context(), $context->get_parent_contexts(), $context->get_parent_context_ids() ======= + + $userid = reset($testusers); + $usercontext = context_user::instance($userid); + $this->assertSame($usercontext->get_parent_context(), $systemcontext); + $this->assertSame($usercontext->get_parent_contexts(), array($systemcontext->id=>$systemcontext)); + $this->assertSame($usercontext->get_parent_contexts(true), array($usercontext->id=>$usercontext, $systemcontext->id=>$systemcontext)); + + $this->assertSame($systemcontext->get_parent_contexts(), array()); + $this->assertSame($systemcontext->get_parent_contexts(true), array($systemcontext->id=>$systemcontext)); + $this->assertSame($systemcontext->get_parent_context_ids(), array()); + $this->assertSame($systemcontext->get_parent_context_ids(true), array($systemcontext->id)); + + $this->assertSame($frontpagecontext->get_parent_context(), $systemcontext); + $this->assertSame($frontpagecontext->get_parent_contexts(), array($systemcontext->id=>$systemcontext)); + $this->assertSame($frontpagecontext->get_parent_contexts(true), array($frontpagecontext->id=>$frontpagecontext, $systemcontext->id=>$systemcontext)); + $this->assertSame($frontpagecontext->get_parent_context_ids(), array($systemcontext->id)); + $this->assertEquals($frontpagecontext->get_parent_context_ids(true), array($frontpagecontext->id, $systemcontext->id)); + + $this->assertSame($systemcontext->get_parent_context(), false); + $frontpagecontext = context_course::instance($SITE->id); + $parent = $systemcontext; + foreach ($testcategories as $catid) { + $catcontext = context_coursecat::instance($catid); + $this->assertSame($catcontext->get_parent_context(), $parent); + $parent = $catcontext; + } + $this->assertSame($frontpagepagecontext->get_parent_context(), $frontpagecontext); + $this->assertSame($frontpageblockcontext->get_parent_context(), $frontpagecontext); + $this->assertSame($frontpagepageblockcontext->get_parent_context(), $frontpagepagecontext); + + + // ====== $context->get_child_contexts() ================================ + + $CFG->debug = 0; + $children = $systemcontext->get_child_contexts(); + $CFG->debug = DEBUG_DEVELOPER; + $this->assertEquals(count($children)+1, $DB->count_records('context')); + + $context = context_coursecat::instance($testcategories[3]); + $children = $context->get_child_contexts(); + $countcats = 0; + $countcourses = 0; + $countblocks = 0; + foreach ($children as $child) { + if ($child->contextlevel == CONTEXT_COURSECAT) { + $countcats++; + } + if ($child->contextlevel == CONTEXT_COURSE) { + $countcourses++; + } + if ($child->contextlevel == CONTEXT_BLOCK) { + $countblocks++; + } + } + $this->assertEquals(count($children), 8); + $this->assertEquals($countcats, 1); + $this->assertEquals($countcourses, 6); + $this->assertEquals($countblocks, 1); + + $context = context_course::instance($testcourses[2]); + $children = $context->get_child_contexts(); + $this->assertEquals(count($children), 7); // depends on number of default blocks + + $context = context_module::instance($testpages[3]); + $children = $context->get_child_contexts(); + $this->assertEquals(count($children), 1); + + $context = context_block::instance($testblocks[1]); + $children = $context->get_child_contexts(); + $this->assertEquals(count($children), 0); + + unset($children); + unset($countcats); + unset($countcourses); + unset($countblocks); + + + // ======= context_helper::reset_caches() ============================ + + context_helper::reset_caches(); + $this->assertEquals(context_inspection::test_context_cache_size(), 0); + context_course::instance($SITE->id); + $this->assertEquals(context_inspection::test_context_cache_size(), 1); + + + // ======= context preloading ======================================== + + context_helper::reset_caches(); + $sql = "SELECT ".context_helper::get_preload_record_columns_sql('c')." + FROM {context} c + WHERE c.contextlevel <> ".CONTEXT_SYSTEM; + $records = $DB->get_records_sql($sql); + $firstrecord = reset($records); + $columns = context_helper::get_preload_record_columns('c'); + $firstrecord = (array)$firstrecord; + $this->assertSame(array_keys($firstrecord), array_values($columns)); + context_helper::reset_caches(); + foreach ($records as $record) { + context_helper::preload_from_record($record); + $this->assertEquals($record, new stdClass()); + } + $this->assertEquals(context_inspection::test_context_cache_size(), count($records)); + unset($records); + unset($columns); + + context_helper::reset_caches(); + context_helper::preload_course($SITE->id); + $this->assertEquals(7, context_inspection::test_context_cache_size()); // depends on number of default blocks + + // ====== assign_capability(), unassign_capability() ==================== + + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertFalse($rc); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $allroles['teacher'], $frontpagecontext->id); + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertEquals($rc->permission, CAP_ALLOW); + assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $allroles['teacher'], $frontpagecontext->id); + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertEquals($rc->permission, CAP_ALLOW); + assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $allroles['teacher'], $frontpagecontext, true); + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertEquals($rc->permission, CAP_PREVENT); + + assign_capability('moodle/site:accessallgroups', CAP_INHERIT, $allroles['teacher'], $frontpagecontext); + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertFalse($rc); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $allroles['teacher'], $frontpagecontext); + unassign_capability('moodle/site:accessallgroups', $allroles['teacher'], $frontpagecontext, true); + $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups')); + $this->assertFalse($rc); + unassign_capability('moodle/site:accessallgroups', $allroles['teacher'], $frontpagecontext->id, true); + unset($rc); + + accesslib_clear_all_caches(false); // must be done after assign_capability() + + + // ======= role_assign(), role_unassign(), role_unassign_all() ============== + + $context = context_course::instance($testcourses[1]); + $this->assertEquals($DB->count_records('role_assignments', array('contextid'=>$context->id)), 0); + role_assign($allroles['teacher'], $testusers[1], $context->id); + role_assign($allroles['teacher'], $testusers[2], $context->id); + role_assign($allroles['manager'], $testusers[1], $context->id); + $this->assertEquals($DB->count_records('role_assignments', array('contextid'=>$context->id)), 3); + role_unassign($allroles['teacher'], $testusers[1], $context->id); + $this->assertEquals($DB->count_records('role_assignments', array('contextid'=>$context->id)), 2); + role_unassign_all(array('contextid'=>$context->id)); + $this->assertEquals($DB->count_records('role_assignments', array('contextid'=>$context->id)), 0); + unset($context); + + accesslib_clear_all_caches(false); // just in case + + + // ====== has_capability(), get_users_by_capability(), role_switch(), reload_all_capabilities() and friends ======================== + + $adminid = get_admin()->id; + $guestid = $CFG->siteguest; + + // Enrol some users into some courses + $course1 = $DB->get_record('course', array('id'=>$testcourses[22]), '*', MUST_EXIST); + $course2 = $DB->get_record('course', array('id'=>$testcourses[7]), '*', MUST_EXIST); + $cms = $DB->get_records('course_modules', array('course'=>$course1->id), 'id'); + $cm1 = reset($cms); + $blocks = $DB->get_records('block_instances', array('parentcontextid'=>context_module::instance($cm1->id)->id), 'id'); + $block1 = reset($blocks); + $instance1 = $DB->get_record('enrol', array('enrol'=>'manual', 'courseid'=>$course1->id)); + $instance2 = $DB->get_record('enrol', array('enrol'=>'manual', 'courseid'=>$course2->id)); + for($i=0; $i<9; $i++) { + $manualenrol->enrol_user($instance1, $testusers[$i], $allroles['student']); + } + $manualenrol->enrol_user($instance1, $testusers[8], $allroles['teacher']); + $manualenrol->enrol_user($instance1, $testusers[9], $allroles['editingteacher']); + + for($i=10; $i<15; $i++) { + $manualenrol->enrol_user($instance2, $testusers[$i], $allroles['student']); + } + $manualenrol->enrol_user($instance2, $testusers[15], $allroles['editingteacher']); + + // Add tons of role assignments - the more the better + role_assign($allroles['coursecreator'], $testusers[11], context_coursecat::instance($testcategories[2])); + role_assign($allroles['manager'], $testusers[12], context_coursecat::instance($testcategories[1])); + role_assign($allroles['student'], $testusers[9], context_module::instance($cm1->id)); + role_assign($allroles['teacher'], $testusers[8], context_module::instance($cm1->id)); + role_assign($allroles['guest'], $testusers[13], context_course::instance($course1->id)); + role_assign($allroles['teacher'], $testusers[7], context_block::instance($block1->id)); + role_assign($allroles['manager'], $testusers[9], context_block::instance($block1->id)); + role_assign($allroles['editingteacher'], $testusers[9], context_course::instance($course1->id)); + + role_assign($allroles['teacher'], $adminid, context_course::instance($course1->id)); + role_assign($allroles['editingteacher'], $adminid, context_block::instance($block1->id)); + + // Add tons of overrides - the more the better + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultuserroleid, $frontpageblockcontext, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpageblockcontext, true); + assign_capability('moodle/block:view', CAP_PROHIBIT, $allroles['guest'], $frontpageblockcontext, true); + assign_capability('block/online_users:viewlist', CAP_PREVENT, $allroles['user'], $frontpageblockcontext, true); + assign_capability('block/online_users:viewlist', CAP_PREVENT, $allroles['student'], $frontpageblockcontext, true); + + assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $CFG->defaultuserroleid, $frontpagepagecontext, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagepagecontext, true); + assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $frontpagepagecontext, true); + assign_capability('mod/page:view', CAP_ALLOW, $allroles['user'], $frontpagepagecontext, true); + assign_capability('moodle/page:view', CAP_ALLOW, $allroles['student'], $frontpagepagecontext, true); + + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultuserroleid, $frontpagecontext, true); + assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagecontext, true); + assign_capability('mod/page:view', CAP_ALLOW, $allroles['guest'], $frontpagecontext, true); + assign_capability('mod/page:view', CAP_PROHIBIT, $allroles['user'], $frontpagecontext, true); + + assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $systemcontext, true); + + accesslib_clear_all_caches(false); // must be done after assign_capability() + + // Extra tests for guests and not-logged-in users because they can not be verified by cross checking + // with get_users_by_capability() where they are ignored + $this->assertFalse(has_capability('moodle/block:view', $frontpageblockcontext, $guestid)); + $this->assertFalse(has_capability('mod/page:view', $frontpagepagecontext, $guestid)); + $this->assertTrue(has_capability('mod/page:view', $frontpagecontext, $guestid)); + $this->assertFalse(has_capability('mod/page:view', $systemcontext, $guestid)); + + $this->assertFalse(has_capability('moodle/block:view', $frontpageblockcontext, 0)); + $this->assertFalse(has_capability('mod/page:view', $frontpagepagecontext, 0)); + $this->assertTrue(has_capability('mod/page:view', $frontpagecontext, 0)); + $this->assertFalse(has_capability('mod/page:view', $systemcontext, 0)); + + $this->assertFalse(has_capability('moodle/course:create', $systemcontext, $testusers[11])); + $this->assertTrue(has_capability('moodle/course:create', context_coursecat::instance($testcategories[2]), $testusers[11])); + $this->assertFalse(has_capability('moodle/course:create', context_course::instance($testcourses[1]), $testusers[11])); + $this->assertTrue(has_capability('moodle/course:create', context_course::instance($testcourses[19]), $testusers[11])); + + $this->assertFalse(has_capability('moodle/course:update', context_course::instance($testcourses[1]), $testusers[9])); + $this->assertFalse(has_capability('moodle/course:update', context_course::instance($testcourses[19]), $testusers[9])); + $this->assertFalse(has_capability('moodle/course:update', $systemcontext, $testusers[9])); + + // Test the list of enrolled users + $coursecontext = context_course::instance($course1->id); + $enrolled = get_enrolled_users($coursecontext); + $this->assertEquals(count($enrolled), 10); + for($i=0; $i<10; $i++) { + $this->assertTrue(isset($enrolled[$testusers[$i]])); + } + $enrolled = get_enrolled_users($coursecontext, 'moodle/course:update'); + $this->assertEquals(count($enrolled), 1); + $this->assertTrue(isset($enrolled[$testusers[9]])); + unset($enrolled); + + // role switching + $userid = $testusers[9]; + $USER = $DB->get_record('user', array('id'=>$userid)); + load_all_capabilities(); + $coursecontext = context_course::instance($course1->id); + $this->assertTrue(has_capability('moodle/course:update', $coursecontext)); + $this->assertFalse(is_role_switched($course1->id)); + role_switch($allroles['student'], $coursecontext); + $this->assertTrue(is_role_switched($course1->id)); + $this->assertEquals($USER->access['rsw'][$coursecontext->path], $allroles['student']); + $this->assertFalse(has_capability('moodle/course:update', $coursecontext)); + reload_all_capabilities(); + $this->assertFalse(has_capability('moodle/course:update', $coursecontext)); + role_switch(0, $coursecontext); + $this->assertTrue(has_capability('moodle/course:update', $coursecontext)); + $userid = $adminid; + $USER = $DB->get_record('user', array('id'=>$userid)); + load_all_capabilities(); + $coursecontext = context_course::instance($course1->id); + $blockcontext = context_block::instance($block1->id); + $this->assertTrue(has_capability('moodle/course:update', $blockcontext)); + role_switch($allroles['student'], $coursecontext); + $this->assertEquals($USER->access['rsw'][$coursecontext->path], $allroles['student']); + $this->assertFalse(has_capability('moodle/course:update', $blockcontext)); + reload_all_capabilities(); + $this->assertFalse(has_capability('moodle/course:update', $blockcontext)); + load_all_capabilities(); + $this->assertTrue(has_capability('moodle/course:update', $blockcontext)); + + // temp course role for enrol + $DB->delete_records('cache_flags', array()); // this prevents problem with dirty contexts immediately resetting the temp role - this is a known problem... + $userid = $testusers[5]; + $roleid = $allroles['editingteacher']; + $USER = $DB->get_record('user', array('id'=>$userid)); + load_all_capabilities(); + $coursecontext = context_course::instance($course1->id); + $this->assertFalse(has_capability('moodle/course:update', $coursecontext)); + $this->assertFalse(isset($USER->access['ra'][$coursecontext->path][$roleid])); + load_temp_course_role($coursecontext, $roleid); + $this->assertEquals($USER->access['ra'][$coursecontext->path][$roleid], $roleid); + $this->assertTrue(has_capability('moodle/course:update', $coursecontext)); + remove_temp_course_roles($coursecontext); + $this->assertFalse(has_capability('moodle/course:update', $coursecontext, $userid)); + load_temp_course_role($coursecontext, $roleid); + reload_all_capabilities(); + $this->assertFalse(has_capability('moodle/course:update', $coursecontext, $userid)); + $USER = new stdClass(); + $USER->id = 0; + + // Now cross check has_capability() with get_users_by_capability(), each using different code paths, + // they have to be kept in sync, usually only one of them breaks, so we know when something is wrong, + // at the same time validate extra restrictions (guest read only no risks, admin exception, non existent and deleted users) + $contexts = $DB->get_records('context', array(), 'id'); + $contexts = array_values($contexts); + $capabilities = $DB->get_records('capabilities', array(), 'id'); + $capabilities = array_values($capabilities); + $roles = array($allroles['guest'], $allroles['user'], $allroles['teacher'], $allroles['editingteacher'], $allroles['coursecreator'], $allroles['manager']); + + if (PHPUNIT_LONGTEST) { + // Random time! + srand(666); + foreach($testusers as $userid) { // no guest or deleted + // each user gets 0-20 random roles + $rcount = rand(0, 20); + for($j=0; $j<$rcount; $j++) { + $roleid = $roles[rand(0, count($roles)-1)]; + $contextid = $contexts[rand(0, count($contexts)-1)]->id; + role_assign($roleid, $userid, $contextid); + } + } + $permissions = array(CAP_ALLOW, CAP_PREVENT, CAP_INHERIT, CAP_PREVENT); + for($j=0; $j<1000; $j++) { + $roleid = $roles[rand(0, count($roles)-1)]; + $contextid = $contexts[rand(0, count($contexts)-1)]->id; + $permission = $permissions[rand(0,count($permissions)-1)]; + $capname = $capabilities[rand(0, count($capabilities)-1)]->name; + assign_capability($capname, $permission, $roleid, $contextid, true); + } + unset($permissions); + unset($roles); + unset($contexts); + unset($users); + unset($capabilities); + + accesslib_clear_all_caches(false); // must be done after assign_capability() + + // Test time - let's set up some real user, just in case the logic for USER affects the others... + $USER = $DB->get_record('user', array('id'=>$testusers[3])); + load_all_capabilities(); + + $contexts = $DB->get_records('context', array(), 'id'); + $users = $DB->get_records('user', array(), 'id', 'id'); + $capabilities = $DB->get_records('capabilities', array(), 'id'); + $users[0] = null; // not-logged-in user + $users[-1] = null; // non-existent user + + } else { + // context testing takes a very long time... + $contexts = array(); + } + + foreach ($contexts as $crecord) { + $context = context::instance_by_id($crecord->id); + if ($coursecontext = $context->get_course_context(false)) { + $enrolled = get_enrolled_users($context); + } else { + $enrolled = array(); + } + foreach ($capabilities as $cap) { + $allowed = get_users_by_capability($context, $cap->name, 'u.id, u.username'); + if ($enrolled) { + $enrolledwithcap = get_enrolled_users($context, $cap->name); + } else { + $enrolledwithcap = array(); + } + foreach ($users as $userid=>$unused) { + if ($userid == 0 or isguestuser($userid)) { + if ($userid == 0) { + $CFG->forcelogin = true; + $this->assertFalse(has_capability($cap->name, $context, $userid)); + unset($CFG->forcelogin); + } + if (($cap->captype === 'write') or ($cap->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { + $this->assertFalse(has_capability($cap->name, $context, $userid)); + } + $this->assertFalse(isset($allowed[$userid])); + } else { + if (is_siteadmin($userid)) { + $this->assertTrue(has_capability($cap->name, $context, $userid, true)); + } + $hascap = has_capability($cap->name, $context, $userid, false); + $this->assertSame($hascap, isset($allowed[$userid]), "Capability result mismatch user:$userid, context:$context->id, $cap->name, hascap: ".(int)$hascap." "); + if (isset($enrolled[$userid])) { + $this->assertSame(isset($allowed[$userid]), isset($enrolledwithcap[$userid]), "Enrolment with capability result mismatch user:$userid, context:$context->id, $cap->name, hascap: ".(int)$hascap." "); + } + } + } + } + } + // Back to nobody + $USER = new stdClass(); + $USER->id = 0; + unset($contexts); + unset($users); + unset($capabilities); + + // Now let's do all the remaining tests that break our carefully prepared fake site + + + + // ======= $context->mark_dirty() ======================================= + + $DB->delete_records('cache_flags', array()); + accesslib_clear_all_caches(false); + $systemcontext->mark_dirty(); + $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2); + $this->assertTrue(isset($dirty[$systemcontext->path])); + $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$systemcontext->path])); + + + // ======= $context->reload_if_dirty(); ================================= + + $DB->delete_records('cache_flags', array()); + accesslib_clear_all_caches(false); + load_all_capabilities(); + $context = context_course::instance($testcourses[2]); + $page = $DB->get_record('page', array('course'=>$testcourses[2])); + $pagecontext = context_module::instance($page->id); + + $context->mark_dirty(); + $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$context->path])); + $USER->access['test'] = true; + $context->reload_if_dirty(); + $this->assertFalse(isset($USER->access['test'])); + + $context->mark_dirty(); + $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$context->path])); + $USER->access['test'] = true; + $pagecontext->reload_if_dirty(); + $this->assertFalse(isset($USER->access['test'])); + + + // ======= context_helper::build_all_paths() ============================ + + $oldcontexts = $DB->get_records('context', array(), 'id'); + $DB->set_field_select('context', 'path', NULL, "contextlevel <> ".CONTEXT_SYSTEM); + $DB->set_field_select('context', 'depth', 0, "contextlevel <> ".CONTEXT_SYSTEM); + context_helper::build_all_paths(); + $newcontexts = $DB->get_records('context', array(), 'id'); + $this->assertEquals($oldcontexts, $newcontexts); + unset($oldcontexts); + unset($newcontexts); + + + // ======= $context->reset_paths() ====================================== + + $context = context_course::instance($testcourses[2]); + $children = $context->get_child_contexts(); + $context->reset_paths(false); + $this->assertSame($DB->get_field('context', 'path', array('id'=>$context->id)), NULL); + $this->assertEquals($DB->get_field('context', 'depth', array('id'=>$context->id)), 0); + foreach ($children as $child) { + $this->assertSame($DB->get_field('context', 'path', array('id'=>$child->id)), NULL); + $this->assertEquals($DB->get_field('context', 'depth', array('id'=>$child->id)), 0); + } + $this->assertEquals(count($children)+1, $DB->count_records('context', array('depth'=>0))); + $this->assertEquals(count($children)+1, $DB->count_records('context', array('path'=>NULL))); + + $context = context_course::instance($testcourses[2]); + $context->reset_paths(true); + $context = context_course::instance($testcourses[2]); + $this->assertEquals($DB->get_field('context', 'path', array('id'=>$context->id)), $context->path); + $this->assertEquals($DB->get_field('context', 'depth', array('id'=>$context->id)), $context->depth); + $this->assertEquals(0, $DB->count_records('context', array('depth'=>0))); + $this->assertEquals(0, $DB->count_records('context', array('path'=>NULL))); + + + // ====== $context->update_moved(); ====================================== + + accesslib_clear_all_caches(false); + $DB->delete_records('cache_flags', array()); + $course = $DB->get_record('course', array('id'=>$testcourses[0])); + $context = context_course::instance($course->id); + $oldpath = $context->path; + $miscid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}"); + $categorycontext = context_coursecat::instance($miscid); + $course->category = $miscid; + $DB->update_record('course', $course); + $context->update_moved($categorycontext); + + $context = context_course::instance($course->id); + $this->assertEquals($context->get_parent_context(), $categorycontext); + $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2); + $this->assertTrue(isset($dirty[$oldpath])); + $this->assertTrue(isset($dirty[$context->path])); + + + // ====== $context->delete_content() ===================================== + + context_helper::reset_caches(); + $context = context_module::instance($testpages[3]); + $this->assertTrue($DB->record_exists('context', array('id'=>$context->id))); + $this->assertEquals(1, $DB->count_records('block_instances', array('parentcontextid'=>$context->id))); + $context->delete_content(); + $this->assertTrue($DB->record_exists('context', array('id'=>$context->id))); + $this->assertEquals(0, $DB->count_records('block_instances', array('parentcontextid'=>$context->id))); + + + // ====== $context->delete() ============================= + + context_helper::reset_caches(); + $context = context_module::instance($testpages[4]); + $this->assertTrue($DB->record_exists('context', array('id'=>$context->id))); + $this->assertEquals(1, $DB->count_records('block_instances', array('parentcontextid'=>$context->id))); + $bi = $DB->get_record('block_instances', array('parentcontextid'=>$context->id)); + $bicontext = context_block::instance($bi->id); + $DB->delete_records('cache_flags', array()); + $context->delete(); // should delete also linked blocks + $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2); + $this->assertTrue(isset($dirty[$context->path])); + $this->assertFalse($DB->record_exists('context', array('id'=>$context->id))); + $this->assertFalse($DB->record_exists('context', array('id'=>$bicontext->id))); + $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$testpages[4]))); + $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$bi->id))); + $this->assertEquals(0, $DB->count_records('block_instances', array('parentcontextid'=>$context->id))); + context_module::instance($testpages[4]); + + + // ====== context_helper::delete_instance() ============================= + + context_helper::reset_caches(); + $lastcourse = array_pop($testcourses); + $this->assertTrue($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$lastcourse))); + $coursecontext = context_course::instance($lastcourse); + $this->assertEquals(context_inspection::test_context_cache_size(), 1); + $this->assertFalse($coursecontext->instanceid == CONTEXT_COURSE); + $DB->delete_records('cache_flags', array()); + context_helper::delete_instance(CONTEXT_COURSE, $lastcourse); + $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2); + $this->assertTrue(isset($dirty[$coursecontext->path])); + $this->assertEquals(context_inspection::test_context_cache_size(), 0); + $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$lastcourse))); + context_course::instance($lastcourse); + + + // ======= context_helper::create_instances() ========================== + + $prevcount = $DB->count_records('context'); + $DB->delete_records('context', array('contextlevel'=>CONTEXT_BLOCK)); + context_helper::create_instances(null, true); + $this->assertSame($DB->count_records('context'), $prevcount); + $this->assertEquals($DB->count_records('context', array('depth'=>0)), 0); + $this->assertEquals($DB->count_records('context', array('path'=>NULL)), 0); + + $DB->delete_records('context', array('contextlevel'=>CONTEXT_BLOCK)); + $DB->delete_records('block_instances', array()); + $prevcount = $DB->count_records('context'); + $DB->delete_records_select('context', 'contextlevel <> '.CONTEXT_SYSTEM); + context_helper::create_instances(null, true); + $this->assertSame($DB->count_records('context'), $prevcount); + $this->assertEquals($DB->count_records('context', array('depth'=>0)), 0); + $this->assertEquals($DB->count_records('context', array('path'=>NULL)), 0); + + + // ======= context_helper::cleanup_instances() ========================== + + $lastcourse = $DB->get_field_sql("SELECT MAX(id) FROM {course}"); + $DB->delete_records('course', array('id'=>$lastcourse)); + $lastcategory = $DB->get_field_sql("SELECT MAX(id) FROM {course_categories}"); + $DB->delete_records('course_categories', array('id'=>$lastcategory)); + $lastuser = $DB->get_field_sql("SELECT MAX(id) FROM {user} WHERE deleted=0"); + $DB->delete_records('user', array('id'=>$lastuser)); + $DB->delete_records('block_instances', array('parentcontextid'=>$frontpagepagecontext->id)); + $DB->delete_records('course_modules', array('id'=>$frontpagepagecontext->instanceid)); + context_helper::cleanup_instances(); + $count = 1; //system + $count += $DB->count_records('user', array('deleted'=>0)); + $count += $DB->count_records('course_categories'); + $count += $DB->count_records('course'); + $count += $DB->count_records('course_modules'); + $count += $DB->count_records('block_instances'); + $this->assertEquals($DB->count_records('context'), $count); + + + // ======= context cache size restrictions ============================== + + $testusers= array(); + for ($i=0; $icreate_user(); + $testusers[$i] = $user->id; + } + context_helper::create_instances(null, true); + context_helper::reset_caches(); + for ($i=0; $iassertEquals(context_inspection::test_context_cache_size(), CONTEXT_CACHE_MAX_SIZE); + } else if ($i == CONTEXT_CACHE_MAX_SIZE) { + // once the limit is reached roughly 1/3 of records should be removed from cache + $this->assertEquals(context_inspection::test_context_cache_size(), (int)(CONTEXT_CACHE_MAX_SIZE * (2/3) +102)); + } + } + // We keep the first 100 cached + $prevsize = context_inspection::test_context_cache_size(); + for ($i=0; $i<100; $i++) { + context_user::instance($testusers[$i]); + $this->assertEquals(context_inspection::test_context_cache_size(), $prevsize); + } + context_user::instance($testusers[102]); + $this->assertEquals(context_inspection::test_context_cache_size(), $prevsize+1); + unset($testusers); + + + + // ================================================================= + // ======= basic test of legacy functions ========================== + // ================================================================= + // note: watch out, the fake site might be pretty borked already + + $this->assertSame(get_system_context(), context_system::instance()); + + foreach ($DB->get_records('context') as $contextid=>$record) { + $context = context::instance_by_id($contextid); + $this->assertSame(get_context_instance_by_id($contextid), $context); + $this->assertSame(get_context_instance($record->contextlevel, $record->instanceid), $context); + $this->assertSame(get_parent_contexts($context), $context->get_parent_context_ids()); + if ($context->id == SYSCONTEXTID) { + $this->assertSame(get_parent_contextid($context), false); + } else { + $this->assertSame(get_parent_contextid($context), $context->get_parent_context()->id); + } + } + + $CFG->debug = 0; + $children = get_child_contexts($systemcontext); + $CFG->debug = DEBUG_DEVELOPER; + $this->assertEquals(count($children), $DB->count_records('context')-1); + unset($children); + + $DB->delete_records('context', array('contextlevel'=>CONTEXT_BLOCK)); + create_contexts(); + $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_BLOCK))); + + $DB->set_field('context', 'depth', 0, array('contextlevel'=>CONTEXT_BLOCK)); + build_context_path(); + $this->assertFalse($DB->record_exists('context', array('depth'=>0))); + + $lastcourse = $DB->get_field_sql("SELECT MAX(id) FROM {course}"); + $DB->delete_records('course', array('id'=>$lastcourse)); + $lastcategory = $DB->get_field_sql("SELECT MAX(id) FROM {course_categories}"); + $DB->delete_records('course_categories', array('id'=>$lastcategory)); + $lastuser = $DB->get_field_sql("SELECT MAX(id) FROM {user} WHERE deleted=0"); + $DB->delete_records('user', array('id'=>$lastuser)); + $DB->delete_records('block_instances', array('parentcontextid'=>$frontpagepagecontext->id)); + $DB->delete_records('course_modules', array('id'=>$frontpagepagecontext->instanceid)); + cleanup_contexts(); + $count = 1; //system + $count += $DB->count_records('user', array('deleted'=>0)); + $count += $DB->count_records('course_categories'); + $count += $DB->count_records('course'); + $count += $DB->count_records('course_modules'); + $count += $DB->count_records('block_instances'); + $this->assertEquals($DB->count_records('context'), $count); + + context_helper::reset_caches(); + preload_course_contexts($SITE->id); + $this->assertEquals(context_inspection::test_context_cache_size(), 1); + + context_helper::reset_caches(); + list($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSECAT, 'ctx'); + $sql = "SELECT c.id $select FROM {course_categories} c $join"; + $records = $DB->get_records_sql($sql); + foreach ($records as $record) { + context_instance_preload($record); + $record = (array)$record; + $this->assertEquals(1, count($record)); // only id left + } + $this->assertEquals(count($records), context_inspection::test_context_cache_size()); + + accesslib_clear_all_caches(true); + $DB->delete_records('cache_flags', array()); + mark_context_dirty($systemcontext->path); + $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2); + $this->assertTrue(isset($dirty[$systemcontext->path])); + + accesslib_clear_all_caches(false); + $DB->delete_records('cache_flags', array()); + $course = $DB->get_record('course', array('id'=>$testcourses[2])); + $context = get_context_instance(CONTEXT_COURSE, $course->id); + $oldpath = $context->path; + $miscid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}"); + $categorycontext = context_coursecat::instance($miscid); + $course->category = $miscid; + $DB->update_record('course', $course); + context_moved($context, $categorycontext); + $context = get_context_instance(CONTEXT_COURSE, $course->id); + $this->assertEquals($context->get_parent_context(), $categorycontext); + + $this->assertTrue($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$testcourses[2]))); + delete_context(CONTEXT_COURSE, $testcourses[2]); + $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$testcourses[2]))); + + $name = get_contextlevel_name(CONTEXT_COURSE); + $this->assertFalse(empty($name)); + + $context = get_context_instance(CONTEXT_COURSE, $testcourses[2]); + $name = print_context_name($context); + $this->assertFalse(empty($name)); + + $url = get_context_url($coursecontext); + $this->assertFalse($url instanceof modole_url); + + $page = $DB->get_record('page', array('id'=>$testpages[7])); + $context = get_context_instance(CONTEXT_MODULE, $page->id); + $coursecontext = get_course_context($context); + $this->assertEquals($coursecontext->contextlevel, CONTEXT_COURSE); + $this->assertEquals(get_courseid_from_context($context), $page->course); + + $caps = fetch_context_capabilities($systemcontext); + $this->assertTrue(is_array($caps)); + unset($caps); + } +} + diff --git a/lib/tests/blocklib_test.php b/lib/tests/blocklib_test.php new file mode 100644 index 0000000000000..a7d3baab37477 --- /dev/null +++ b/lib/tests/blocklib_test.php @@ -0,0 +1,386 @@ +. + +/** + * Tests for the block_manager class in ../blocklib.php. + * + * @package core + * @category phpunit + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/pagelib.php'); +require_once($CFG->libdir . '/blocklib.php'); +require_once($CFG->dirroot . '/blocks/moodleblock.class.php'); + + +/** Test-specific subclass to make some protected things public. */ +class testable_block_manager extends block_manager { + + public function mark_loaded() { + $this->birecordsbyregion = array(); + } + public function get_loaded_blocks() { + return $this->birecordsbyregion; + } +} +class block_ablocktype extends block_base { + public function init() { + } +} + +/** + * Test functions that don't need to touch the database. + */ +class moodle_block_manager_testcase extends basic_testcase { + protected $testpage; + protected $blockmanager; + + public function setUp() { + parent::setUp(); + $this->testpage = new moodle_page(); + $this->testpage->set_context(get_context_instance(CONTEXT_SYSTEM)); + $this->blockmanager = new testable_block_manager($this->testpage); + } + + public function tearDown() { + $this->testpage = null; + $this->blockmanager = null; + parent::tearDown(); + } + + public function test_no_regions_initially() { + // Exercise SUT & Validate + $this->assertEquals(array(), $this->blockmanager->get_regions()); + } + + public function test_add_region() { + // Exercise SUT. + $this->blockmanager->add_region('a-region-name'); + // Validate + $this->assertEquals(array('a-region-name'), $this->blockmanager->get_regions()); + } + + public function test_add_regions() { + // Set up fixture. + $regions = array('a-region', 'another-region'); + // Exercise SUT. + $this->blockmanager->add_regions($regions); + // Validate + $this->assertEquals($regions, $this->blockmanager->get_regions(), '', 0, 10, true); + } + + public function test_add_region_twice() { + // Exercise SUT. + $this->blockmanager->add_region('a-region-name'); + $this->blockmanager->add_region('another-region'); + // Validate + $this->assertEquals(array('a-region-name', 'another-region'), $this->blockmanager->get_regions(), '', 0, 10, true); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_cannot_add_region_after_loaded() { + // Set up fixture. + $this->blockmanager->mark_loaded(); + // Exercise SUT. + $this->blockmanager->add_region('too-late'); + } + + public function test_set_default_region() { + // Set up fixture. + $this->blockmanager->add_region('a-region-name'); + // Exercise SUT. + $this->blockmanager->set_default_region('a-region-name'); + // Validate + $this->assertEquals('a-region-name', $this->blockmanager->get_default_region()); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_cannot_set_unknown_region_as_default() { + // Exercise SUT. + $this->blockmanager->set_default_region('a-region-name'); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_cannot_change_default_region_after_loaded() { + // Set up fixture. + $this->blockmanager->mark_loaded(); + // Exercise SUT. + $this->blockmanager->set_default_region('too-late'); + } + + public function test_matching_page_type_patterns() { + $this->assertEquals(array('site-index', 'site-index-*', 'site-*', '*'), + matching_page_type_patterns('site-index'), '', 0, 10, true); + + $this->assertEquals(array('mod-quiz-report-overview', 'mod-quiz-report-overview-*', 'mod-quiz-report-*', 'mod-quiz-*', 'mod-*', '*'), + matching_page_type_patterns('mod-quiz-report-overview'), '', 0, 10, true); + + $this->assertEquals(array('mod-forum-view', 'mod-*-view', 'mod-forum-view-*', 'mod-forum-*', 'mod-*', '*'), + matching_page_type_patterns('mod-forum-view'), '', 0, 10, true); + + $this->assertEquals(array('mod-forum-index', 'mod-*-index', 'mod-forum-index-*', 'mod-forum-*', 'mod-*', '*'), + matching_page_type_patterns('mod-forum-index'), '', 0, 10, true); + } +} + +/** + * Test methods that load and save data from block_instances and block_positions. + */ +class moodle_block_manager_test_saving_loading_testcase extends advanced_testcase { + + protected $isediting = null; + + protected function purge_blocks() { + global $DB; + $bis = $DB->get_records('block_instances'); + foreach($bis as $instance) { + blocks_delete_instance($instance); + } + $this->resetAfterTest(true); + } + + protected function get_a_page_and_block_manager($regions, $context, $pagetype, $subpage = '') { + $page = new moodle_page; + $page->set_context($context); + $page->set_pagetype($pagetype); + $page->set_subpage($subpage); + + $blockmanager = new testable_block_manager($page); + $blockmanager->add_regions($regions); + $blockmanager->set_default_region($regions[0]); + + return array($page, $blockmanager); + } + + protected function get_a_known_block_type() { + global $DB; + $block = new stdClass; + $block->name = 'ablocktype'; + $DB->insert_record('block', $block); + return $block->name; + } + + protected function assertContainsBlocksOfType($typearray, $blockarray) { + if (!$this->assertEquals(count($typearray), count($blockarray), "Blocks array contains the wrong number of elements %s.")) { + return; + } + $types = array_values($typearray); + $i = 0; + foreach ($blockarray as $block) { + $blocktype = $types[$i]; + $this->assertEquals($blocktype, $block->name(), "Block types do not match at postition $i %s."); + $i++; + } + } + + public function test_empty_initially() { + $this->purge_blocks(); + + // Set up fixture. + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array('a-region'), + get_context_instance(CONTEXT_SYSTEM), 'page-type'); + // Exercise SUT. + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_loaded_blocks(); + $this->assertEquals(array('a-region' => array()), $blocks); + } + + public function test_adding_and_retrieving_one_block() { + $this->purge_blocks(); + + // Set up fixture. + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + $context = get_context_instance(CONTEXT_SYSTEM); + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $context, 'page-type'); + + // Exercise SUT. + $blockmanager->add_block($blockname, $regionname, 0, false); + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array($blockname), $blocks); + } + + public function test_adding_and_retrieving_two_blocks() { + $this->purge_blocks(); + + // Set up fixture. + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + $context = get_context_instance(CONTEXT_SYSTEM); + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $context, 'page-type'); + + // Exercise SUT. + $blockmanager->add_block($blockname, $regionname, 0, false); + $blockmanager->add_block($blockname, $regionname, 1, false); + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array($blockname, $blockname), $blocks); + } + + public function test_block_not_included_in_different_context() { + global $DB; + $this->purge_blocks(); + + // Set up fixture. + $syscontext = get_context_instance(CONTEXT_SYSTEM); + $cat = new stdClass(); + $cat->name = 'testcategory'; + $cat->parent = 0; + $cat->depth = 1; + $cat->sortorder = 100; + $cat->timemodified = time(); + $catid = $DB->insert_record('course_categories', $cat); + $DB->set_field('course_categories', 'path', '/' . $catid, array('id' => $catid)); + $fakecontext = context_coursecat::instance($catid); + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + + list($addpage, $addbm) = $this->get_a_page_and_block_manager(array($regionname), $fakecontext, 'page-type'); + list($viewpage, $viewbm) = $this->get_a_page_and_block_manager(array($regionname), $syscontext, 'page-type'); + + $addbm->add_block($blockname, $regionname, 0, false); + + // Exercise SUT. + $viewbm->load_blocks(); + // Validate. + $blocks = $viewbm->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array(), $blocks); + } + + public function test_block_included_in_sub_context() { + $this->purge_blocks(); + + // Set up fixture. + $syscontext = get_context_instance(CONTEXT_SYSTEM); + $childcontext = context_coursecat::instance(1); + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + + list($addpage, $addbm) = $this->get_a_page_and_block_manager(array($regionname), $syscontext, 'page-type'); + list($viewpage, $viewbm) = $this->get_a_page_and_block_manager(array($regionname), $childcontext, 'page-type'); + + $addbm->add_block($blockname, $regionname, 0, true); + + // Exercise SUT. + $viewbm->load_blocks(); + // Validate. + $blocks = $viewbm->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array($blockname), $blocks); + } + + public function test_block_not_included_on_different_page_type() { + $this->purge_blocks(); + + // Set up fixture. + $syscontext = get_context_instance(CONTEXT_SYSTEM); + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + + list($addpage, $addbm) = $this->get_a_page_and_block_manager(array($regionname), $syscontext, 'page-type'); + list($viewpage, $viewbm) = $this->get_a_page_and_block_manager(array($regionname), $syscontext, 'other-page-type'); + + $addbm->add_block($blockname, $regionname, 0, true); + + // Exercise SUT. + $viewbm->load_blocks(); + // Validate. + $blocks = $viewbm->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array(), $blocks); + } + + public function test_block_not_included_on_different_sub_page() { + $this->purge_blocks(); + + // Set up fixture. + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + $syscontext = get_context_instance(CONTEXT_SYSTEM); + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $syscontext, 'page-type', 'sub-page'); + + $blockmanager->add_block($blockname, $regionname, 0, true, $page->pagetype, 'other-sub-page'); + + // Exercise SUT. + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array(), $blocks); + } + + public function test_block_included_with_explicit_sub_page() { + $this->purge_blocks(); + + // Set up fixture. + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + $syscontext = get_context_instance(CONTEXT_SYSTEM); + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $syscontext, 'page-type', 'sub-page'); + + $blockmanager->add_block($blockname, $regionname, 0, true, $page->pagetype, $page->subpage); + + // Exercise SUT. + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array($blockname), $blocks); + } + + public function test_block_included_with_page_type_pattern() { + $this->purge_blocks(); + + // Set up fixture. + $regionname = 'a-region'; + $blockname = $this->get_a_known_block_type(); + $syscontext = get_context_instance(CONTEXT_SYSTEM); + + list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname), + $syscontext, 'page-type', 'sub-page'); + + $blockmanager->add_block($blockname, $regionname, 0, true, 'page-*', $page->subpage); + + // Exercise SUT. + $blockmanager->load_blocks(); + // Validate. + $blocks = $blockmanager->get_blocks_for_region($regionname); + $this->assertContainsBlocksOfType(array($blockname), $blocks); + } +} + diff --git a/lib/tests/code_test.php b/lib/tests/code_test.php new file mode 100644 index 0000000000000..83ffbec7ef785 --- /dev/null +++ b/lib/tests/code_test.php @@ -0,0 +1,56 @@ +. + +/** + * Code quality unit tests that are fast enough to run each time. + * + * @package core + * @category phpunit + * @copyright © 2006 The Open University + * @author T.J.Hunt@open.ac.uk + * @license http://www.gnu.org/copyleft/gpl.html GNU Public License + */ + +defined('MOODLE_INTERNAL') || die(); + +class code_testcase extends advanced_testcase { + protected $badstrings; + protected $extensions_to_ignore = array('exe', 'gif', 'ico', 'jpg', 'png', 'ttf', 'log'); + protected $ignore_folders = array(); + + public function test_dnc() { + global $CFG; + $regexp = '/\.(' . implode('|', $this->extensions_to_ignore) . ')$/'; + $this->badstrings = array(); + $this->badstrings['DONOT' . 'COMMIT'] = 'DONOT' . 'COMMIT'; // If we put the literal string here, it fails the test! + $this->badstrings['trailing whitespace'] = "[\t ][\r\n]"; + foreach ($this->badstrings as $description => $ignored) { + $this->allok[$description] = true; + } + $this->recurseFolders($CFG->dirroot, 'search_file_for_dnc', $regexp, true); + $this->assertTrue(true); // executed only if no file failed the test + } + + protected function search_file_for_dnc($filepath) { + $content = file_get_contents($filepath); + foreach ($this->badstrings as $description => $badstring) { + if (stripos($content, $badstring) !== false) { + $this->fail("File $filepath contains $description."); + } + } + } +} + diff --git a/lib/tests/componentlib_test.php b/lib/tests/componentlib_test.php new file mode 100644 index 0000000000000..4ac2e76fad166 --- /dev/null +++ b/lib/tests/componentlib_test.php @@ -0,0 +1,182 @@ +. + +/** + * Unit tests for /lib/componentlib.class.php. + * + * @package core + * @category phpunit + * @copyright 2011 Tomasz Muras + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir.'/componentlib.class.php'); + +class componentlib_test extends advanced_testcase { + + public function test_component_installer() { + global $CFG; + + $ci = new component_installer('http://download.moodle.org', 'unittest', 'downloadtests.zip'); + $this->assertTrue($ci->check_requisites()); + + $destpath = $CFG->dataroot.'/downloadtests'; + + //carefully remove component files to enforce fresh installation + @unlink($destpath.'/'.'downloadtests.md5'); + @unlink($destpath.'/'.'test.html'); + @unlink($destpath.'/'.'test.jpg'); + @rmdir($destpath); + + $this->assertEquals(COMPONENT_NEEDUPDATE, $ci->need_upgrade()); + + $status = $ci->install(); + $this->assertEquals(COMPONENT_INSTALLED, $status); + $this->assertEquals('9e94f74b3efb1ff6cf075dc6b2abf15c', $ci->get_component_md5()); + + //it's already installed, so Moodle should detect it's up to date + $this->assertEquals(COMPONENT_UPTODATE, $ci->need_upgrade()); + $status = $ci->install(); + $this->assertEquals(COMPONENT_UPTODATE, $status); + + //check if correct files were downloaded + $this->assertEquals('2af180e813dc3f446a9bb7b6af87ce24', md5_file($destpath.'/'.'test.jpg')); + $this->assertEquals('47250a973d1b88d9445f94db4ef2c97a', md5_file($destpath.'/'.'test.html')); + } + + /** + * Test the public API of the {@link lang_installer} class + */ + public function test_lang_installer() { + + // test the manipulation with the download queue + $installer = new testable_lang_installer(); + $this->assertFalse($installer->protected_is_queued()); + $installer->protected_add_to_queue('cs'); + $installer->protected_add_to_queue(array('cs', 'sk')); + $this->assertTrue($installer->protected_is_queued()); + $this->assertTrue($installer->protected_is_queued('cs')); + $this->assertTrue($installer->protected_is_queued('sk')); + $this->assertFalse($installer->protected_is_queued('de_kids')); + $installer->set_queue('de_kids'); + $this->assertFalse($installer->protected_is_queued('cs')); + $this->assertFalse($installer->protected_is_queued('sk')); + $this->assertFalse($installer->protected_is_queued('de')); + $this->assertFalse($installer->protected_is_queued('de_du')); + $this->assertTrue($installer->protected_is_queued('de_kids')); + $installer->set_queue(array('cs', 'de_kids')); + $this->assertTrue($installer->protected_is_queued('cs')); + $this->assertFalse($installer->protected_is_queued('sk')); + $this->assertFalse($installer->protected_is_queued('de')); + $this->assertFalse($installer->protected_is_queued('de_du')); + $this->assertTrue($installer->protected_is_queued('de_kids')); + $installer->set_queue(array()); + $this->assertFalse($installer->protected_is_queued()); + unset($installer); + + // install a set of lang packs + $installer = new testable_lang_installer(array('cs', 'de_kids', 'xx')); + $result = $installer->run(); + $this->assertEquals($result['cs'], lang_installer::RESULT_UPTODATE); + $this->assertEquals($result['de_kids'], lang_installer::RESULT_INSTALLED); + $this->assertEquals($result['xx'], lang_installer::RESULT_DOWNLOADERROR); + // the following two were automatically added to the queue + $this->assertEquals($result['de_du'], lang_installer::RESULT_INSTALLED); + $this->assertEquals($result['de'], lang_installer::RESULT_UPTODATE); + + // exception throwing + $installer = new testable_lang_installer(array('yy')); + try { + $installer->run(); + $this->fail('lang_installer_exception exception expected'); + } catch (Exception $e) { + $this->assertEquals('lang_installer_exception', get_class($e)); + } + } +} + +/** + * Testable lang_installer subclass that does not actually install anything + * and provides access to the protected methods of the parent class + * + * @copyright 2011 David Mudrak + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class testable_lang_installer extends lang_installer { + + /** + * @see parent::is_queued() + */ + public function protected_is_queued($langcode = '') { + return $this->is_queued($langcode); + } + + /** + * @see parent::add_to_queue() + */ + public function protected_add_to_queue($langcodes) { + return $this->add_to_queue($langcodes); + } + + /** + * Simulate lang pack installation via component_installer + * + * Language packages 'de_du' and 'de_kids' reported as installed + * Language packages 'cs' and 'de' reported as up-to-date + * Language package 'xx' returns download error + * All other language packages will throw an unknown exception + * + * @see parent::install_language_pack() + */ + protected function install_language_pack($langcode) { + + switch ($langcode) { + case 'de_du': + case 'de_kids': + return self::RESULT_INSTALLED; + + case 'cs': + case 'de': + return self::RESULT_UPTODATE; + + case 'xx': + return self::RESULT_DOWNLOADERROR; + + default: + throw new lang_installer_exception('testing-unknown-exception', $langcode); + } + } + + /** + * Simulate detection of parent languge + * + * @see parent::get_parent_language() + */ + protected function get_parent_language($langcode) { + + switch ($langcode) { + case 'de_kids': + return 'de_du'; + case 'de_du': + return 'de'; + default: + return ''; + } + } +} diff --git a/lib/tests/cssslib_test.php b/lib/tests/cssslib_test.php new file mode 100644 index 0000000000000..bff1c94dea96c --- /dev/null +++ b/lib/tests/cssslib_test.php @@ -0,0 +1,708 @@ +. + +/** + * This file contains the unittests for the css optimiser in csslib.php + * + * @package core_css + * @category phpunit + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/csslib.php'); + + +/** + * CSS optimiser test class + * + * @package core_css + * @category css + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class css_optimiser_test extends advanced_testcase { + + /** + * Sets up the test class + */ + public function setUp() { + global $CFG; + parent::setUp(); + // We need to disable these if they are enabled to that we can predict + // the output. + $CFG->cssoptimiserstats = false; + $CFG->cssoptimiserpretty = false; + + $this->resetAfterTest(true); + } + + /** + * Test the process method + */ + public function test_process() { + $optimiser = new css_optimiser; + + $this->check_background($optimiser); + $this->check_borders($optimiser); + $this->check_colors($optimiser); + $this->check_margins($optimiser); + $this->check_padding($optimiser); + $this->check_widths($optimiser); + + $this->try_broken_css_found_in_moodle($optimiser); + $this->try_invalid_css_handling($optimiser); + $this->try_bulk_processing($optimiser); + $this->try_break_things($optimiser); + } + + /** + * Background colour tests + * @param css_optimiser $optimiser + */ + protected function check_background(css_optimiser $optimiser) { + + $cssin = '.test {background-color: #123456;}'; + $cssout = '.test{background:#123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {background-image: url(\'test.png\');}'; + $cssout = '.test{background-image:url(\'test.png\');}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {background: #123456 url(\'test.png\') no-repeat top left;}'; + $cssout = '.test{background:#123456 url(\'test.png\') no-repeat top left;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {background: url(\'test.png\') no-repeat top left;}.test{background-position: bottom right}.test {background-color:#123456;}'; + $cssout = '.test{background:#123456 url(\'test.png\') no-repeat bottom right;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {background: url( \'test.png\' )}.test{background: bottom right}.test {background:#123456;}'; + $cssout = '.test{background-image:url(\'test.png\');background-position:bottom right;background-color:#123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {background-color: #123456;background-repeat: repeat-x; background-position: 100% 0%;}'; + $cssout = '.test{background-color:#123456;background-repeat:repeat-x;background-position:100% 0%;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;} + .tree_item.branch.navigation_node {background-image:none;padding-left:0;}'; + $cssout = '.tree_item.branch{background:url([[pix:t/expanded]]) no-repeat 0 10%;} .tree_item.branch.navigation_node{background-image:none;padding-left:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0% 5%;background-repeat: no-repeat;} + .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}'; + $cssout = '.block_tree .tree_item.emptybranch{background:url([[pix:t/collapsed_empty]]) no-repeat 0% 5%;} .block_tree .collapsed .tree_item.branch{background-image:url([[pix:t/collapsed]]);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Border tests + * @param css_optimiser $optimiser + */ + protected function check_borders(css_optimiser $optimiser) { + $cssin = '.test {border: 1px solid #654321} .test {border-bottom-color: #123456}'; + $cssout = '.test{border:1px solid;border-color:#654321 #654321 #123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {border:1px solid red;}'; + $cssout = '.one{border:1px solid #FF0000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {border:1px solid;} .one {border:2px dotted #DDD;}'; + $cssout = '.one{border:2px dotted #DDDDDD;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {border:2px dotted #DDD;}.one {border:1px solid;} '; + $cssout = '.one{border:1px solid #DDDDDD;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one, .two {border:1px solid red;}'; + $cssout = ".one, .two{border:1px solid #FF0000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one, .two {border:0px;}'; + $cssout = ".one, .two{border-width:0;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one, .two {border-top: 5px solid white;}'; + $cssout = ".one, .two{border-top:5px solid #FFFFFF;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {border:1px solid red;} .two {border:1px solid red;}'; + $cssout = ".one, .two{border:1px solid #FF0000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {border:1px solid red;width:20px;} .two {border:1px solid red;height:20px;}'; + $cssout = ".one{width:20px;border:1px solid #FF0000;} .two{height:20px;border:1px solid #FF0000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border: 1px solid #123456;} .test {border-color: #654321}'; + $cssout = '.test{border:1px solid #654321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border-width: 1px; border-style: solid; border-color: #123456;}'; + $cssout = '.test{border:1px solid #123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border:1px solid #123456;border-top:2px dotted #654321;}'; + $cssout = '.test{border:1px solid #123456;border-top:2px dotted #654321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border:1px solid #123456;border-left:2px dotted #654321;}'; + $cssout = '.test{border:1px solid #123456;border-left:2px dotted #654321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border-left:2px dotted #654321;border:1px solid #123456;}'; + $cssout = '.test{border:1px solid #123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border:1px solid;border-top-color:#123456;}'; + $cssout = '.test{border:1px solid;border-top-color:#123456;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border:1px solid;border-top-color:#111; border-bottom-color: #222;border-left-color: #333;}'; + $cssout = '.test{border:1px solid;border-top-color:#111;border-bottom-color:#222;border-left-color:#333;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.test {border:1px solid;border-top-color:#111; border-bottom-color: #222;border-left-color: #333;border-right-color:#444;}'; + $cssout = '.test{border:1px solid;border-color:#111 #444 #222 #333;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.generaltable .cell {border-color:#EEEEEE;} .generaltable .cell {border-width: 1px;border-style: solid;}'; + $cssout = '.generaltable .cell{border:1px solid #EEEEEE;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '#page-admin-roles-override .rolecap {border:none;border-bottom:1px solid #CECECE;}'; + $cssout = '#page-admin-roles-override .rolecap{border-top:0;border-right:0;border-bottom:1px solid #CECECE;border-left:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Test colour styles + * @param css_optimiser $optimiser + */ + protected function check_colors(css_optimiser $optimiser) { + $css = '.css{}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = '.css{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = '#some{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div.css{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div#some{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div[type=blah]{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div.css[type=blah]{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div#some[type=blah]{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = '#some.css[type=blah]{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = '#some .css[type=blah]{color:#123456;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $cssin = '.one {color:red;} .two {color:#F00;}'; + $cssout = ".one, .two{color:#F00;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:#123;color:#321;}'; + $cssout = '.one{color:#321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:#123; color : #321 ;}'; + $cssout = '.one{color:#321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:#123;} .one {color:#321;}'; + $cssout = '.one{color:#321;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:#123 !important;color:#321;}'; + $cssout = '.one{color:#123 !important;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:#123 !important;} .one {color:#321;}'; + $cssout = '.one{color:#123 !important;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:rgb(255, 128, 1)}'; + $cssout = '.one{color:rgb(255, 128, 1);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:rgba(255, 128, 1, 0.5)}'; + $cssout = '.one{color:rgba(255, 128, 1, 0.5);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:hsl(120, 65%, 75%)}'; + $cssout = '.one{color:hsl(120, 65%, 75%);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:hsla(120,65%,75%,0.5)}'; + $cssout = '.one{color:hsla(120,65%,75%,0.5);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Try some invalid colours to make sure we don't mangle them. + $css = 'div#some{color:#1;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div#some{color:#12;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div#some{color:#1234;}'; + $this->assertEquals($css, $optimiser->process($css)); + + $css = 'div#some{color:#12345;}'; + $this->assertEquals($css, $optimiser->process($css)); + } + + protected function check_widths(css_optimiser $optimiser) { + $cssin = '.css {width:0}'; + $cssout = '.css{width:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.css {width:0px}'; + $cssout = '.css{width:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.css {width:0em}'; + $cssout = '.css{width:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.css {width:0pt}'; + $cssout = '.css{width:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.css {width:0mm}'; + $cssout = '.css{width:0;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.css {width:100px}'; + $cssout = '.css{width:100px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Test margin styles + * @param css_optimiser $optimiser + */ + protected function check_margins(css_optimiser $optimiser) { + $cssin = '.one {margin: 1px 2px 3px 4px}'; + $cssout = '.one{margin:1px 2px 3px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin-top:1px; margin-left:4px; margin-right:2px; margin-bottom: 3px;}'; + $cssout = '.one{margin:1px 2px 3px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin-top:1px; margin-left:4px;}'; + $cssout = '.one{margin-top:1px;margin-left:4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin:1px; margin-left:4px;}'; + $cssout = '.one{margin:1px 1px 1px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin:1px; margin-bottom:4px;}'; + $cssout = '.one{margin:1px 1px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one, .two, .one.two, .one .two {margin:0;} .one.two {margin:0 7px;}'; + $cssout = '.one, .two, .one .two{margin:0;} .one.two{margin:0 7px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Test padding styles + * + * @param css_optimiser $optimiser + */ + protected function check_padding(css_optimiser $optimiser) { + $cssin = '.one {margin: 1px 2px 3px 4px}'; + $cssout = '.one{margin:1px 2px 3px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin-top:1px; margin-left:4px; margin-right:2px; margin-bottom: 3px;}'; + $cssout = '.one{margin:1px 2px 3px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin-top:1px; margin-left:4px;}'; + $cssout = '.one{margin-top:1px;margin-left:4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin:1px; margin-left:4px;}'; + $cssout = '.one{margin:1px 1px 1px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin:1px; margin-bottom:4px;}'; + $cssout = '.one{margin:1px 1px 4px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {margin:0 !important;}'; + $cssout = '.one{margin:0 !important;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {padding:0 !important;}'; + $cssout = '.one{padding:0 !important;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one, .two, .one.two, .one .two {margin:0;} .one.two {margin:0 7px;}'; + $cssout = '.one, .two, .one .two{margin:0;} .one.two{margin:0 7px;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Test some totally invalid CSS optimisation + * + * @param css_optimiser $optimiser + */ + protected function try_invalid_css_handling(css_optimiser $optimiser) { + + $cssin = array( + '.one{}', + '.one {:}', + '.one {;}', + '.one {;;;;;}', + '.one {:;}', + '.one {:;:;:;:::;;;}', + '.one {!important}', + '.one {:!important}', + '.one {:!important;}', + '.one {;!important}' + ); + $cssout = '.one{}'; + foreach ($cssin as $css) { + $this->assertEquals($cssout, $optimiser->process($css)); + } + + $cssin = array( + '.one{background-color:red;}', + '.one {background-color:red;} .one {background-color:}', + '.one {background-color:red;} .one {background-color;}', + '.one {background-color:red;} .one {background-color}', + '.one {background-color:red;} .one {background-color:;}', + '.one {background-color:red;} .one {:blue;}', + '.one {background-color:red;} .one {:#00F}', + ); + $cssout = '.one{background:#F00;}'; + foreach ($cssin as $css) { + $this->assertEquals($cssout, $optimiser->process($css)); + } + + $cssin = '..one {background-color:color:red}'; + $cssout = '..one{background-color:color:red;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '#.one {background-color:color:red}'; + $cssout = '#.one{background-color:color:red;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '##one {background-color:color:red}'; + $cssout = '##one{background-color:color:red;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {background-color:color:red}'; + $cssout = '.one{background-color:color:red;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {background-color:red;color;border-color:blue}'; + $cssout = '.one{background:#F00;border-color:#00F;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '{background-color:#123456;color:red;}{color:green;}'; + $cssout = "{color:#008000;background:#123456;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + $cssin = '.one {color:red;} {color:green;} .one {background-color:blue;}'; + $cssout = ".one{color:#F00;background:#00F;} {color:#008000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * Try to break some things + * @param css_optimiser $optimiser + */ + protected function try_break_things(css_optimiser $optimiser) { + // Wildcard test + $cssin = '* {color: black;}'; + $cssout = '*{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Wildcard test + $cssin = '.one * {color: black;}'; + $cssout = '.one *{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Wildcard test + $cssin = '* .one * {color: black;}'; + $cssout = '* .one *{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Wildcard test + $cssin = '*,* {color: black;}'; + $cssout = '*{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Wildcard test + $cssin = '*, * .one {color: black;}'; + $cssout = "*,\n* .one{color:#000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Wildcard test + $cssin = '*, *.one {color: black;}'; + $cssout = "*,\n*.one{color:#000;}"; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Psedo test + $cssin = '.one:before {color: black;}'; + $cssout = '.one:before{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Psedo test + $cssin = '.one:after {color: black;}'; + $cssout = '.one:after{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Psedo test + $cssin = '.one:onclick {color: black;}'; + $cssout = '.one:onclick{color:#000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Test complex CSS rules that don't really exist but mimic other CSS rules + $cssin = '.one {master-of-destruction: explode(\' \', "What madness";}'; + $cssout = '.one{master-of-destruction:explode(\' \', "What madness";}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Test some complex IE css... I couldn't even think of a more complext solution + // than the CSS they came up with. + $cssin = 'a { opacity: 0.5; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); }'; + $cssout = 'a{opacity:0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } + + /** + * A bulk processing test + * @param css_optimiser $optimiser + */ + protected function try_bulk_processing(css_optimiser $optimiser) { + global $CFG; + $cssin = <<cssoptimiserpretty = 1; + $this->assertEquals($optimiser->process($cssin), $cssout); + } + + /** + * Test CSS colour matching + */ + public function test_css_is_colour() { + // First lets test hex colours + $this->assertTrue(css_is_colour('#123456')); + $this->assertTrue(css_is_colour('#123')); + $this->assertTrue(css_is_colour('#ABCDEF')); + $this->assertTrue(css_is_colour('#ABC')); + $this->assertTrue(css_is_colour('#abcdef')); + $this->assertTrue(css_is_colour('#abc')); + $this->assertTrue(css_is_colour('#aBcDeF')); + $this->assertTrue(css_is_colour('#aBc')); + $this->assertTrue(css_is_colour('#1a2Bc3')); + $this->assertTrue(css_is_colour('#1Ac')); + + // Note the following two colour's arn't really colours but browsers process + // them still. + $this->assertTrue(css_is_colour('#A')); + $this->assertTrue(css_is_colour('#12')); + // Having four or five characters however are not valid colours and + // browsers don't parse them. They need to fail so that broken CSS + // stays broken after optimisation. + $this->assertFalse(css_is_colour('#1234')); + $this->assertFalse(css_is_colour('#12345')); + + $this->assertFalse(css_is_colour('#BCDEFG')); + $this->assertFalse(css_is_colour('#')); + $this->assertFalse(css_is_colour('#0000000')); + $this->assertFalse(css_is_colour('#132-245')); + $this->assertFalse(css_is_colour('#13 23 43')); + $this->assertFalse(css_is_colour('123456')); + + // Next lets test real browser mapped colours + $this->assertTrue(css_is_colour('black')); + $this->assertTrue(css_is_colour('blue')); + $this->assertTrue(css_is_colour('BLACK')); + $this->assertTrue(css_is_colour('Black')); + $this->assertTrue(css_is_colour('bLACK')); + $this->assertTrue(css_is_colour('mediumaquamarine')); + $this->assertTrue(css_is_colour('mediumAquamarine')); + $this->assertFalse(css_is_colour('monkey')); + $this->assertFalse(css_is_colour('')); + $this->assertFalse(css_is_colour('not a colour')); + + // Next lets test rgb(a) colours + $this->assertTrue(css_is_colour('rgb(255,255,255)')); + $this->assertTrue(css_is_colour('rgb(0, 0, 0)')); + $this->assertTrue(css_is_colour('RGB (255, 255 , 255)')); + $this->assertTrue(css_is_colour('rgba(0,0,0,0)')); + $this->assertTrue(css_is_colour('RGBA(255,255,255,1)')); + $this->assertTrue(css_is_colour('rgbA(255,255,255,0.5)')); + $this->assertFalse(css_is_colour('rgb(-255,-255,-255)')); + $this->assertFalse(css_is_colour('rgb(256,-256,256)')); + + // Now lets test HSL colours + $this->assertTrue(css_is_colour('hsl(0,0%,100%)')); + $this->assertTrue(css_is_colour('hsl(180, 0%, 10%)')); + $this->assertTrue(css_is_colour('hsl (360, 100% , 95%)')); + + // Finally test the special values + $this->assertTrue(css_is_colour('inherit')); + } + + /** + * Test the css_is_width function + */ + public function test_css_is_width() { + + $this->assertTrue(css_is_width('0')); + $this->assertTrue(css_is_width('0px')); + $this->assertTrue(css_is_width('0em')); + $this->assertTrue(css_is_width('199px')); + $this->assertTrue(css_is_width('199em')); + $this->assertTrue(css_is_width('199%')); + $this->assertTrue(css_is_width('-1')); + $this->assertTrue(css_is_width('-1px')); + $this->assertTrue(css_is_width('auto')); + $this->assertTrue(css_is_width('inherit')); + + $this->assertFalse(css_is_width('-')); + $this->assertFalse(css_is_width('bananas')); + $this->assertFalse(css_is_width('')); + $this->assertFalse(css_is_width('top')); + } + + /** + * This function tests some of the broken crazy CSS we have in Moodle. + * For each of these things the value needs to be corrected if we can be 100% + * certain what is going wrong, Or it needs to be left as is. + * + * @param css_optimiser $optimiser + */ + public function try_broken_css_found_in_moodle(css_optimiser $optimiser) { + // Notice how things are out of order here but that they get corrected + $cssin = '.test {background:url([[pix:theme|pageheaderbgred]]) top center no-repeat}'; + $cssout = '.test{background:url([[pix:theme|pageheaderbgred]]) no-repeat top center;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Cursor hand isn't valid + $cssin = '.test {cursor: hand;}'; + $cssout = '.test{cursor:hand;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Zoom property isn't valid + $cssin = '.test {zoom: 1;}'; + $cssout = '.test{zoom:1;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Left isn't a valid position property + $cssin = '.test {position: left;}'; + $cssout = '.test{position:left;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // The dark red color isn't a valid HTML color but has a standardised + // translation of #8B0000 + $cssin = '.test {color: darkred;}'; + $cssout = '.test{color:#8B0000;}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // You can't use argb colours as border colors + $cssin = '.test {border-bottom: 1px solid rgba(0,0,0,0.25);}'; + $cssout = '.test{border-bottom:1px solid rgba(0,0,0,0.25);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + + // Opacity with annoying IE equivilants.... + $cssin = '.test {opacity: 0.5; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50);}'; + $cssout = '.test{opacity:0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);}'; + $this->assertEquals($cssout, $optimiser->process($cssin)); + } +} diff --git a/lib/tests/eventslib_test.php b/lib/tests/eventslib_test.php new file mode 100644 index 0000000000000..77458a3532fb3 --- /dev/null +++ b/lib/tests/eventslib_test.php @@ -0,0 +1,233 @@ +. + +/** + * Tests events subsystems + * + * @package core + * @subpackage event + * @copyright 2007 onwards Martin Dougiamas (http://dougiamas.com) + * @author Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +// test handler function +function sample_function_handler($eventdata) { + static $called = 0; + static $ignorefail = false; + + if ($eventdata == 'status') { + return $called; + + } else if ($eventdata == 'reset') { + $called = 0; + $ignorefail = false; + return; + + } else if ($eventdata == 'fail') { + if ($ignorefail) { + $called++; + return true; + } else { + return false; + } + + } else if ($eventdata == 'ignorefail') { + $ignorefail = true; + return; + + } else if ($eventdata == 'ok') { + $called++; + return true; + } + + print_error('invalideventdata', '', '', $eventdata); +} + +// test handler class with static method +class sample_handler_class { + static function static_method($eventdata) { + static $called = 0; + static $ignorefail = false; + + if ($eventdata == 'status') { + return $called; + + } else if ($eventdata == 'reset') { + $called = 0; + $ignorefail = false; + return; + + } else if ($eventdata == 'fail') { + if ($ignorefail) { + $called++; + return true; + } else { + return false; + } + + } else if ($eventdata == 'ignorefail') { + $ignorefail = true; + return; + + } else if ($eventdata == 'ok') { + $called++; + return true; + } + + print_error('invalideventdata', '', '', $eventdata); + } +} + +class eventslib_test extends advanced_testcase { + + /** + * Create temporary entries in the database for these tests. + * These tests have to work no matter the data currently in the database + * (meaning they should run on a brand new site). This means several items of + * data have to be artificially inseminated (:-) in the DB. + * @return void + */ + public function setUp() { + parent::setUp(); + // Set global category settings to -1 (not force) + sample_function_handler('reset'); + sample_handler_class::static_method('reset'); + events_update_definition('unittest'); + + $this->resetAfterTest(true); + } + + /** + * Delete temporary entries from the database + * @return void + */ + public function tearDown() { + events_uninstall('unittest'); + parent::tearDown(); + } + + /** + * Tests the installation of event handlers from file + * @return void + */ + public function test_events_update_definition__install() { + global $CFG, $DB; + + $dbcount = $DB->count_records('events_handlers', array('component'=>'unittest')); + $handlers = array(); + require(__DIR__.'/fixtures/events.php'); + $filecount = count($handlers); + $this->assertEquals($dbcount, $filecount, 'Equal number of handlers in file and db: %s'); + } + + /** + * Tests the uninstallation of event handlers from file + * @return void + */ + public function test_events_update_definition__uninstall() { + global $DB; + + events_uninstall('unittest'); + $this->assertEquals(0, $DB->count_records('events_handlers', array('component'=>'unittest')), 'All handlers should be uninstalled: %s'); + } + + /** + * Tests the update of event handlers from file + * @return void + */ + public function test_events_update_definition__update() { + global $DB; + // first modify directly existing handler + $handler = $DB->get_record('events_handlers', array('component'=>'unittest', 'eventname'=>'test_instant')); + + $original = $handler->handlerfunction; + + // change handler in db + $DB->set_field('events_handlers', 'handlerfunction', serialize('some_other_function_handler'), array('id'=>$handler->id)); + + // update the definition, it should revert the handler back + events_update_definition('unittest'); + $handler = $DB->get_record('events_handlers', array('component'=>'unittest', 'eventname'=>'test_instant')); + $this->assertEquals($handler->handlerfunction, $original, 'update should sync db with file definition: %s'); + } + + /** + * tests events_trigger_is_registered funtion() + * @return void + */ + public function test_events_is_registered() { + $this->assertTrue(events_is_registered('test_instant', 'unittest')); + } + + /** + * tests events_trigger funtion() + * @return void + */ + public function test_events_trigger__instant() { + $this->assertEquals(0, events_trigger('test_instant', 'ok')); + $this->assertEquals(0, events_trigger('test_instant', 'ok')); + $this->assertEquals(2, sample_function_handler('status')); + } + + /** + * tests events_trigger funtion() + * @return void + */ + public function test_events_trigger__cron() { + $this->assertEquals(0, events_trigger('test_cron', 'ok')); + $this->assertEquals(0, sample_handler_class::static_method('status')); + events_cron('test_cron'); + $this->assertEquals(1, sample_handler_class::static_method('status')); + } + + /** + * tests events_pending_count() + * @return void + */ + public function test_events_pending_count() { + events_trigger('test_cron', 'ok'); + events_trigger('test_cron', 'ok'); + events_cron('test_cron'); + $this->assertEquals(0, events_pending_count('test_cron'), 'all messages should be already dequeued: %s'); + } + + /** + * tests events_trigger funtion() when instant handler fails + * @return void + */ + public function test_events_trigger__failed_instant() { + $this->assertEquals(1, events_trigger('test_instant', 'fail'), 'fail first event: %s'); + $this->assertEquals(1, events_trigger('test_instant', 'ok'), 'this one should fail too: %s'); + $this->assertEquals(0, events_cron('test_instant'), 'all events should stay in queue: %s'); + $this->assertEquals(2, events_pending_count('test_instant'), 'two events should in queue: %s'); + $this->assertEquals(0, sample_function_handler('status'), 'verify no event dispatched yet: %s'); + sample_function_handler('ignorefail'); //ignore "fail" eventdata from now on + $this->assertEquals(1, events_trigger('test_instant', 'ok'), 'this one should go to queue directly: %s'); + $this->assertEquals(3, events_pending_count('test_instant'), 'three events should in queue: %s'); + $this->assertEquals(0, sample_function_handler('status'), 'verify previous event was not dispatched: %s'); + $this->assertEquals(3, events_cron('test_instant'), 'all events should be dispatched: %s'); + $this->assertEquals(3, sample_function_handler('status'), 'verify three events were dispatched: %s'); + $this->assertEquals(0, events_pending_count('test_instant'), 'no events should in queue: %s'); + $this->assertEquals(0, events_trigger('test_instant', 'ok'), 'this event should be dispatched immediately: %s'); + $this->assertEquals(4, sample_function_handler('status'), 'verify event was dispatched: %s'); + $this->assertEquals(0, events_pending_count('test_instant'), 'no events should in queue: %s'); + } +} + + diff --git a/lib/tests/externallib_test.php b/lib/tests/externallib_test.php new file mode 100644 index 0000000000000..43ea1b775e35a --- /dev/null +++ b/lib/tests/externallib_test.php @@ -0,0 +1,77 @@ +. + +/** + * Unit tests for /lib/externallib.php. + * + * @package core + * @subpackage phpunit + * @copyright 2009 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/externallib.php'); + + +class externallib_test extends UnitTestCase { + public function test_validate_params() { + $params = array('text'=>'aaa', 'someid'=>'6',); + $description = new external_function_parameters(array('someid' => new external_value(PARAM_INT, 'Some int value'), + 'text' => new external_value(PARAM_ALPHA, 'Some text value'))); + $result = external_api::validate_parameters($description, $params); + $this->assertEquals(count($result), 2); + reset($result); + $this->assertTrue(key($result) === 'someid'); + $this->assertTrue($result['someid'] === 6); + $this->assertTrue($result['text'] === 'aaa'); + + + $params = array('someids'=>array('1', 2, 'a'=>'3'), 'scalar'=>666); + $description = new external_function_parameters(array('someids' => new external_multiple_structure(new external_value(PARAM_INT, 'Some ID')), + 'scalar' => new external_value(PARAM_ALPHANUM, 'Some text value'))); + $result = external_api::validate_parameters($description, $params); + $this->assertEquals(count($result), 2); + reset($result); + $this->assertTrue(key($result) === 'someids'); + $this->assertTrue($result['someids'] == array(0=>1, 1=>2, 2=>3)); + $this->assertTrue($result['scalar'] === '666'); + + + $params = array('text'=>'aaa'); + $description = new external_function_parameters(array('someid' => new external_value(PARAM_INT, 'Some int value', false), + 'text' => new external_value(PARAM_ALPHA, 'Some text value'))); + $result = external_api::validate_parameters($description, $params); + $this->assertEquals(count($result), 2); + reset($result); + $this->assertTrue(key($result) === 'someid'); + $this->assertTrue($result['someid'] === null); + $this->assertTrue($result['text'] === 'aaa'); + + + $params = array('text'=>'aaa'); + $description = new external_function_parameters(array('someid' => new external_value(PARAM_INT, 'Some int value', false, 6), + 'text' => new external_value(PARAM_ALPHA, 'Some text value'))); + $result = external_api::validate_parameters($description, $params); + $this->assertEquals(count($result), 2); + reset($result); + $this->assertTrue(key($result) === 'someid'); + $this->assertTrue($result['someid'] === 6); + $this->assertTrue($result['text'] === 'aaa'); + } +} diff --git a/lib/tests/filelib_test.php b/lib/tests/filelib_test.php new file mode 100644 index 0000000000000..92c82d7d656e8 --- /dev/null +++ b/lib/tests/filelib_test.php @@ -0,0 +1,88 @@ +. + + +/** + * Unit tests for /lib/filelib.php. + * + * @package core_files + * @category phpunit + * @copyright 2009 Jerome Mouneyrac + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/filelib.php'); + +class filelib_test extends basic_testcase { + public function test_format_postdata_for_curlcall() { + + //POST params with just simple types + $postdatatoconvert =array( 'userid' => 1, 'roleid' => 22, 'name' => 'john'); + $expectedresult = "userid=1&roleid=22&name=john"; + $postdata = format_postdata_for_curlcall($postdatatoconvert); + $this->assertEquals($postdata, $expectedresult); + + //POST params with a string containing & character + $postdatatoconvert =array( 'name' => 'john&emilie', 'roleid' => 22); + $expectedresult = "name=john%26emilie&roleid=22"; //urlencode: '%26' => '&' + $postdata = format_postdata_for_curlcall($postdatatoconvert); + $this->assertEquals($postdata, $expectedresult); + + //POST params with an empty value + $postdatatoconvert =array( 'name' => null, 'roleid' => 22); + $expectedresult = "name=&roleid=22"; + $postdata = format_postdata_for_curlcall($postdatatoconvert); + $this->assertEquals($postdata, $expectedresult); + + //POST params with complex types + $postdatatoconvert =array( 'users' => array( + array( + 'id' => 2, + 'customfields' => array( + array + ( + 'type' => 'Color', + 'value' => 'violet' + ) + ) + ) + ) + ); + $expectedresult = "users[0][id]=2&users[0][customfields][0][type]=Color&users[0][customfields][0][value]=violet"; + $postdata = format_postdata_for_curlcall($postdatatoconvert); + $this->assertEquals($postdata, $expectedresult); + + //POST params with other complex types + $postdatatoconvert = array ('members' => + array( + array('groupid' => 1, 'userid' => 1) + , array('groupid' => 1, 'userid' => 2) + ) + ); + $expectedresult = "members[0][groupid]=1&members[0][userid]=1&members[1][groupid]=1&members[1][userid]=2"; + $postdata = format_postdata_for_curlcall($postdatatoconvert); + $this->assertEquals($postdata, $expectedresult); + } + + public function test_download_file_content() { + $testhtml = "http://download.moodle.org/unittest/test.html"; + $contents = download_file_content($testhtml); + $this->assertEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents)); + } +} diff --git a/lib/tests/filter_test.php b/lib/tests/filter_test.php new file mode 100644 index 0000000000000..30f1ee2688b3a --- /dev/null +++ b/lib/tests/filter_test.php @@ -0,0 +1,767 @@ +. + +/** + * Tests for the parts of ../filterlib.php that involve loading the configuration + * from, and saving the configuration to, the database. + * + * @package core_filter + * @category phpunit + * @copyright 2009 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/filterlib.php'); + +/** + * Test functions that affect filter_active table with contextid = $syscontextid. + */ +class filter_active_global_test extends advanced_testcase { + + public function setUp() { + global $DB; + parent::setUp(); + $DB->delete_records('filter_active', array()); + $this->resetAfterTest(false); + } + + private function assert_only_one_filter_globally($filter, $state) { + global $DB; + $recs = $DB->get_records('filter_active'); + $this->assertEquals(1, count($recs), 'More than one record returned %s.'); + $rec = reset($recs); + unset($rec->id); + $expectedrec = new stdClass(); + $expectedrec->filter = $filter; + $expectedrec->contextid = context_system::instance()->id; + $expectedrec->active = $state; + $expectedrec->sortorder = 1; + $this->assertEquals($expectedrec, $rec); + } + + private function assert_global_sort_order($filters) { + global $DB; + + $sortedfilters = $DB->get_records_menu('filter_active', + array('contextid' => context_system::instance()->id), 'sortorder', 'sortorder,filter'); + $testarray = array(); + $index = 1; + foreach($filters as $filter) { + $testarray[$index++] = $filter; + } + $this->assertEquals($testarray, $sortedfilters); + } + + public function test_set_filter_globally_on() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/name', TEXTFILTER_ON, 1); + // Validate. + $this->assert_only_one_filter_globally('filter/name', TEXTFILTER_ON); + } + + public function test_set_filter_globally_off() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/name', TEXTFILTER_OFF, 1); + // Validate. + $this->assert_only_one_filter_globally('filter/name', TEXTFILTER_OFF); + } + + public function test_set_filter_globally_disabled() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/name', TEXTFILTER_DISABLED, 1); + // Validate. + $this->assert_only_one_filter_globally('filter/name', TEXTFILTER_DISABLED); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_global_config_exception_on_invalid_state() { + filter_set_global_state('filter/name', 0, 1); + } + + public function test_set_no_sortorder_clash() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/one', TEXTFILTER_DISABLED, 1); + filter_set_global_state('filter/two', TEXTFILTER_DISABLED, 1); + // Validate - should have pushed other filters down. + $this->assert_global_sort_order(array('filter/two', 'filter/one')); + } + + public function test_auto_sort_order_disabled() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/one', TEXTFILTER_DISABLED); + filter_set_global_state('filter/two', TEXTFILTER_DISABLED); + // Validate. + $this->assert_global_sort_order(array('filter/one', 'filter/two')); + } + + public function test_auto_sort_order_enabled() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/one', TEXTFILTER_ON); + filter_set_global_state('filter/two', TEXTFILTER_OFF); + // Validate. + $this->assert_global_sort_order(array('filter/one', 'filter/two')); + } + + public function test_auto_sort_order_mixed() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/0', TEXTFILTER_DISABLED); + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_DISABLED); + filter_set_global_state('filter/3', TEXTFILTER_OFF); + // Validate. + $this->assert_global_sort_order(array('filter/1', 'filter/3', 'filter/0', 'filter/2')); + } + + public function test_update_existing_dont_duplicate() { + // Setup fixture. + // Exercise SUT. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_global_state('filter/name', TEXTFILTER_OFF); + // Validate. + $this->assert_only_one_filter_globally('filter/name', TEXTFILTER_OFF); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_sort_order_not_too_low() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + // Exercise SUT. + filter_set_global_state('filter/2', TEXTFILTER_ON, 0); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_sort_order_not_too_high() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + // Exercise SUT. + filter_set_global_state('filter/2', TEXTFILTER_ON, 3); + } + + public function test_update_reorder_down() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_ON); + filter_set_global_state('filter/3', TEXTFILTER_ON); + // Exercise SUT. + filter_set_global_state('filter/2', TEXTFILTER_ON, 1); + // Validate. + $this->assert_global_sort_order(array('filter/2', 'filter/1', 'filter/3')); + } + + public function test_update_reorder_up() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_ON); + filter_set_global_state('filter/3', TEXTFILTER_ON); + filter_set_global_state('filter/4', TEXTFILTER_ON); + // Exercise SUT. + filter_set_global_state('filter/2', TEXTFILTER_ON, 3); + // Validate. + $this->assert_global_sort_order(array('filter/1', 'filter/3', 'filter/2', 'filter/4')); + } + + public function test_auto_sort_order_change_to_enabled() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_DISABLED); + filter_set_global_state('filter/3', TEXTFILTER_DISABLED); + // Exercise SUT. + filter_set_global_state('filter/3', TEXTFILTER_ON); + // Validate. + $this->assert_global_sort_order(array('filter/1', 'filter/3', 'filter/2')); + } + + public function test_auto_sort_order_change_to_disabled() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_ON); + filter_set_global_state('filter/3', TEXTFILTER_DISABLED); + // Exercise SUT. + filter_set_global_state('filter/1', TEXTFILTER_DISABLED); + // Validate. + $this->assert_global_sort_order(array('filter/2', 'filter/1', 'filter/3')); + } + + public function test_filter_get_global_states() { + // Setup fixture. + filter_set_global_state('filter/1', TEXTFILTER_ON); + filter_set_global_state('filter/2', TEXTFILTER_OFF); + filter_set_global_state('filter/3', TEXTFILTER_DISABLED); + // Exercise SUT. + $filters = filter_get_global_states(); + // Validate. + $this->assertEquals(array( + 'filter/1' => (object) array('filter' => 'filter/1', 'active' => TEXTFILTER_ON, 'sortorder' => 1), + 'filter/2' => (object) array('filter' => 'filter/2', 'active' => TEXTFILTER_OFF, 'sortorder' => 2), + 'filter/3' => (object) array('filter' => 'filter/3', 'active' => TEXTFILTER_DISABLED, 'sortorder' => 3) + ), $filters); + } +} + + +/** + * Test functions that affect filter_active table with contextid = $syscontextid. + */ +class filter_active_local_test extends advanced_testcase { + + public function setUp() { + global $DB; + parent::setUp(); + $DB->delete_records('filter_active', array()); + $this->resetAfterTest(false); + } + + private function assert_only_one_local_setting($filter, $contextid, $state) { + global $DB; + $recs = $DB->get_records('filter_active'); + $this->assertEquals(1, count($recs), 'More than one record returned %s.'); + $rec = reset($recs); + unset($rec->id); + unset($rec->sortorder); + $expectedrec = new stdClass(); + $expectedrec->filter = $filter; + $expectedrec->contextid = $contextid; + $expectedrec->active = $state; + $this->assertEquals($expectedrec, $rec); + } + + private function assert_no_local_setting() { + global $DB; + $this->assertEquals(0, $DB->count_records('filter_active')); + } + + public function test_local_on() { + // Exercise SUT. + filter_set_local_state('filter/name', 123, TEXTFILTER_ON); + // Validate. + $this->assert_only_one_local_setting('filter/name', 123, TEXTFILTER_ON); + } + + public function test_local_off() { + // Exercise SUT. + filter_set_local_state('filter/name', 123, TEXTFILTER_OFF); + // Validate. + $this->assert_only_one_local_setting('filter/name', 123, TEXTFILTER_OFF); + } + + public function test_local_inherit() { + // Exercise SUT. + filter_set_local_state('filter/name', 123, TEXTFILTER_INHERIT); + // Validate. + $this->assert_no_local_setting(); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_local_invalid_state_throws_exception() { + // Exercise SUT. + filter_set_local_state('filter/name', 123, -9999); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_throws_exception_when_setting_global() { + // Exercise SUT. + filter_set_local_state('filter/name', get_context_instance(CONTEXT_SYSTEM)->id, TEXTFILTER_INHERIT); + } + + public function test_local_inherit_deletes_existing() { + // Setup fixture. + filter_set_local_state('filter/name', 123, TEXTFILTER_INHERIT); + // Exercise SUT. + filter_set_local_state('filter/name', 123, TEXTFILTER_INHERIT); + // Validate. + $this->assert_no_local_setting(); + } +} + + +/** + * Test functions that use just the filter_config table. + */ +class filter_config_test extends advanced_testcase { + + public function setUp() { + global $DB; + parent::setUp(); + $DB->delete_records('filter_config', array()); + $this->resetAfterTest(false); + } + + private function assert_only_one_config($filter, $context, $name, $value) { + global $DB; + $recs = $DB->get_records('filter_config'); + $this->assertEquals(1, count($recs), 'More than one record returned %s.'); + $rec = reset($recs); + unset($rec->id); + $expectedrec = new stdClass(); + $expectedrec->filter = $filter; + $expectedrec->contextid = $context; + $expectedrec->name = $name; + $expectedrec->value = $value; + $this->assertEquals($expectedrec, $rec); + } + + public function test_set_new_config() { + // Exercise SUT. + filter_set_local_config('filter/name', 123, 'settingname', 'An arbitrary value'); + // Validate. + $this->assert_only_one_config('filter/name', 123, 'settingname', 'An arbitrary value'); + } + + public function test_update_existing_config() { + // Setup fixture. + filter_set_local_config('filter/name', 123, 'settingname', 'An arbitrary value'); + // Exercise SUT. + filter_set_local_config('filter/name', 123, 'settingname', 'A changed value'); + // Validate. + $this->assert_only_one_config('filter/name', 123, 'settingname', 'A changed value'); + } + + public function test_filter_get_local_config() { + // Setup fixture. + filter_set_local_config('filter/name', 123, 'setting1', 'An arbitrary value'); + filter_set_local_config('filter/name', 123, 'setting2', 'Another arbitrary value'); + filter_set_local_config('filter/name', 122, 'settingname', 'Value from another context'); + filter_set_local_config('filter/other', 123, 'settingname', 'Someone else\'s value'); + // Exercise SUT. + $config = filter_get_local_config('filter/name', 123); + // Validate. + $this->assertEquals(array('setting1' => 'An arbitrary value', 'setting2' => 'Another arbitrary value'), $config); + } +} + + +class filter_get_active_available_in_context_test extends advanced_testcase { + private static $syscontext; + private static $childcontext; + private static $childcontext2; + + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + + $course = self::getDataGenerator()->create_course(array('category'=>1)); + + self::$childcontext = context_coursecat::instance(1); + self::$childcontext2 = context_course::instance($course->id); + self::$syscontext = context_system::instance(); + } + + public function setUp() { + global $DB; + parent::setUp(); + + $DB->delete_records('filter_active', array()); + $DB->delete_records('filter_config', array()); + $this->resetAfterTest(false); + } + + private function assert_filter_list($expectedfilters, $filters) { + $this->assertEquals($expectedfilters, array_keys($filters), '', 0, 10, true); + } + + public function test_globally_on_is_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$syscontext); + // Validate. + $this->assert_filter_list(array('filter/name'), $filters); + // Check no config returned correctly. + $this->assertEquals(array(), $filters['filter/name']); + } + + public function test_globally_off_not_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_OFF); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext2); + // Validate. + $this->assert_filter_list(array(), $filters); + } + + public function test_globally_off_overridden() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_OFF); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_ON); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext2); + // Validate. + $this->assert_filter_list(array('filter/name'), $filters); + } + + public function test_globally_on_overridden() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_OFF); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext2); + // Validate. + $this->assert_filter_list(array(), $filters); + } + + public function test_globally_disabled_not_overridden() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_DISABLED); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_ON); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$syscontext); + // Validate. + $this->assert_filter_list(array(), $filters); + } + + public function test_single_config_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_config('filter/name', self::$childcontext->id, 'settingname', 'A value'); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext); + // Validate. + $this->assertEquals(array('settingname' => 'A value'), $filters['filter/name']); + } + + public function test_multi_config_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_config('filter/name', self::$childcontext->id, 'settingname', 'A value'); + filter_set_local_config('filter/name', self::$childcontext->id, 'anothersettingname', 'Another value'); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext); + // Validate. + $this->assertEquals(array('settingname' => 'A value', 'anothersettingname' => 'Another value'), $filters['filter/name']); + } + + public function test_config_from_other_context_not_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_config('filter/name', self::$childcontext->id, 'settingname', 'A value'); + filter_set_local_config('filter/name', self::$childcontext2->id, 'anothersettingname', 'Another value'); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext2); + // Validate. + $this->assertEquals(array('anothersettingname' => 'Another value'), $filters['filter/name']); + } + + public function test_config_from_other_filter_not_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_config('filter/name', self::$childcontext->id, 'settingname', 'A value'); + filter_set_local_config('filter/other', self::$childcontext->id, 'anothersettingname', 'Another value'); + // Exercise SUT. + $filters = filter_get_active_in_context(self::$childcontext); + // Validate. + $this->assertEquals(array('settingname' => 'A value'), $filters['filter/name']); + } + + protected function assert_one_available_filter($filter, $localstate, $inheritedstate, $filters) { + $this->assertEquals(1, count($filters), 'More than one record returned %s.'); + $rec = $filters[$filter]; + unset($rec->id); + $expectedrec = new stdClass(); + $expectedrec->filter = $filter; + $expectedrec->localstate = $localstate; + $expectedrec->inheritedstate = $inheritedstate; + $this->assertEquals($expectedrec, $rec); + } + + public function test_available_in_context_localoverride() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_OFF); + // Exercise SUT. + $filters = filter_get_available_in_context(self::$childcontext); + // Validate. + $this->assert_one_available_filter('filter/name', TEXTFILTER_OFF, TEXTFILTER_ON, $filters); + } + + public function test_available_in_context_nolocaloverride() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_OFF); + // Exercise SUT. + $filters = filter_get_available_in_context(self::$childcontext2); + // Validate. + $this->assert_one_available_filter('filter/name', TEXTFILTER_INHERIT, TEXTFILTER_OFF, $filters); + } + + public function test_available_in_context_disabled_not_returned() { + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_DISABLED); + filter_set_local_state('filter/name', self::$childcontext->id, TEXTFILTER_ON); + // Exercise SUT. + $filters = filter_get_available_in_context(self::$childcontext); + // Validate. + $this->assertEquals(array(), $filters); + } + + /** + * @expectedException coding_exception + * @return void + */ + public function test_available_in_context_exception_with_syscontext() { + // Exercise SUT. + filter_get_available_in_context(self::$syscontext); + } +} + + +class filter_preload_activities_test extends advanced_testcase { + private static $syscontext; + private static $catcontext; + private static $coursecontext; + private static $course; + private static $activity1context; + private static $activity2context; + + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + + self::$syscontext = context_system::instance(); + self::$catcontext = context_coursecat::instance(1); + self::$course = self::getDataGenerator()->create_course(array('category'=>1)); + self::$coursecontext = context_course::instance(self::$course->id); + $page1 = self::getDataGenerator()->create_module('page', array('course'=>self::$course->id)); + self::$activity1context = context_module::instance($page1->cmid); + $page2 = self::getDataGenerator()->create_module('page', array('course'=>self::$course->id)); + self::$activity2context = context_module::instance($page2->cmid); + } + + public function setUp() { + global $DB; + parent::setUp(); + + $DB->delete_records('filter_active', array()); + $DB->delete_records('filter_config', array()); + $this->resetAfterTest(false); + } + + private function assert_matches($modinfo) { + global $FILTERLIB_PRIVATE, $DB; + + // Use preload cache... + $FILTERLIB_PRIVATE = new stdClass(); + filter_preload_activities($modinfo); + + // Get data and check no queries are made + $before = $DB->perf_get_reads(); + $plfilters1 = filter_get_active_in_context(self::$activity1context); + $plfilters2 = filter_get_active_in_context(self::$activity2context); + $after = $DB->perf_get_reads(); + $this->assertEquals($before, $after); + + // Repeat without cache and check it makes queries now + $FILTERLIB_PRIVATE = new stdClass; + $before = $DB->perf_get_reads(); + $filters1 = filter_get_active_in_context(self::$activity1context); + $filters2 = filter_get_active_in_context(self::$activity2context); + $after = $DB->perf_get_reads(); + $this->assertTrue($after > $before); + + // Check they match + $this->assertEquals($plfilters1, $filters1); + $this->assertEquals($plfilters2, $filters2); + } + + public function test_preload() { + // Get course and modinfo + $modinfo = new course_modinfo(self::$course, 2); + + // Note: All the tests in this function check that the result from the + // preloaded cache is the same as the result from calling the standard + // function without preloading. + + // Initially, check with no filters enabled + $this->assert_matches($modinfo); + + // Enable filter globally, check + filter_set_global_state('filter/name', TEXTFILTER_ON); + $this->assert_matches($modinfo); + + // Disable for activity 2 + filter_set_local_state('filter/name', self::$activity2context->id, TEXTFILTER_OFF); + $this->assert_matches($modinfo); + + // Disable at category + filter_set_local_state('filter/name', self::$catcontext->id, TEXTFILTER_OFF); + $this->assert_matches($modinfo); + + // Enable for activity 1 + filter_set_local_state('filter/name', self::$activity1context->id, TEXTFILTER_ON); + $this->assert_matches($modinfo); + + // Disable globally + filter_set_global_state('filter/name', TEXTFILTER_DISABLED); + $this->assert_matches($modinfo); + + // Add another 2 filters + filter_set_global_state('filter/frog', TEXTFILTER_ON); + filter_set_global_state('filter/zombie', TEXTFILTER_ON); + $this->assert_matches($modinfo); + + // Disable random one of these in each context + filter_set_local_state('filter/zombie', self::$activity1context->id, TEXTFILTER_OFF); + filter_set_local_state('filter/frog', self::$activity2context->id, TEXTFILTER_OFF); + $this->assert_matches($modinfo); + + // Now do some filter options + filter_set_local_config('filter/name', self::$activity1context->id, 'a', 'x'); + filter_set_local_config('filter/zombie', self::$activity1context->id, 'a', 'y'); + filter_set_local_config('filter/frog', self::$activity1context->id, 'a', 'z'); + // These last two don't do anything as they are not at final level but I + // thought it would be good to have that verified in test + filter_set_local_config('filter/frog', self::$coursecontext->id, 'q', 'x'); + filter_set_local_config('filter/frog', self::$catcontext->id, 'q', 'z'); + $this->assert_matches($modinfo); + } +} + + +class filter_delete_config_test extends advanced_testcase { + public function setUp() { + global $DB; + parent::setUp(); + + $DB->delete_records('filter_active', array()); + $DB->delete_records('filter_config', array()); + $this->resetAfterTest(false); + } + + public function test_filter_delete_all_for_filter() { + global $DB; + + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_global_state('filter/other', TEXTFILTER_ON); + filter_set_local_config('filter/name', context_system::instance()->id, 'settingname', 'A value'); + filter_set_local_config('filter/other', context_system::instance()->id, 'settingname', 'Other value'); + set_config('configname', 'A config value', 'filter_name'); + set_config('configname', 'Other config value', 'filter_other'); + // Exercise SUT. + filter_delete_all_for_filter('filter/name'); + // Validate. + $this->assertEquals(1, $DB->count_records('filter_active')); + $this->assertTrue($DB->record_exists('filter_active', array('filter' => 'filter/other'))); + $this->assertEquals(1, $DB->count_records('filter_config')); + $this->assertTrue($DB->record_exists('filter_config', array('filter' => 'filter/other'))); + $expectedconfig = new stdClass; + $expectedconfig->configname = 'Other config value'; + $this->assertEquals($expectedconfig, get_config('filter_other')); + $this->assertEquals(get_config('filter_name'), new stdClass()); + } + + public function test_filter_delete_all_for_context() { + global $DB; + + // Setup fixture. + filter_set_global_state('filter/name', TEXTFILTER_ON); + filter_set_local_state('filter/name', 123, TEXTFILTER_OFF); + filter_set_local_config('filter/name', 123, 'settingname', 'A value'); + filter_set_local_config('filter/other', 123, 'settingname', 'Other value'); + filter_set_local_config('filter/other', 122, 'settingname', 'Other value'); + // Exercise SUT. + filter_delete_all_for_context(123); + // Validate. + $this->assertEquals(1, $DB->count_records('filter_active')); + $this->assertTrue($DB->record_exists('filter_active', array('contextid' => context_system::instance()->id))); + $this->assertEquals(1, $DB->count_records('filter_config')); + $this->assertTrue($DB->record_exists('filter_config', array('filter' => 'filter/other'))); + } +} + +class filter_filter_set_applies_to_strings extends advanced_testcase { + protected $origcfgstringfilters; + protected $origcfgfilterall; + + public function setUp() { + global $DB, $CFG; + parent::setUp(); + + $DB->delete_records('filter_active', array()); + $DB->delete_records('filter_config', array()); + $this->resetAfterTest(false); + + // Store original $CFG; + $this->origcfgstringfilters = $CFG->stringfilters; + $this->origcfgfilterall = $CFG->filterall; + } + + public function tearDown() { + global $CFG; + $CFG->stringfilters = $this->origcfgstringfilters; + $CFG->filterall = $this->origcfgfilterall; + + parent::tearDown(); + } + + public function test_set() { + global $CFG; + // Setup fixture. + $CFG->filterall = 0; + $CFG->stringfilters = ''; + // Exercise SUT. + filter_set_applies_to_strings('filter/name', true); + // Validate. + $this->assertEquals('filter/name', $CFG->stringfilters); + $this->assertEquals(1, $CFG->filterall); + } + + public function test_unset_to_empty() { + global $CFG; + // Setup fixture. + $CFG->filterall = 1; + $CFG->stringfilters = 'filter/name'; + // Exercise SUT. + filter_set_applies_to_strings('filter/name', false); + // Validate. + $this->assertEquals('', $CFG->stringfilters); + $this->assertEquals('', $CFG->filterall); + } + + public function test_unset_multi() { + global $CFG; + // Setup fixture. + $CFG->filterall = 1; + $CFG->stringfilters = 'filter/name,filter/other'; + // Exercise SUT. + filter_set_applies_to_strings('filter/name', false); + // Validate. + $this->assertEquals('filter/other', $CFG->stringfilters); + $this->assertEquals(1, $CFG->filterall); + } +} diff --git a/lib/tests/fixtures/events.php b/lib/tests/fixtures/events.php new file mode 100644 index 0000000000000..03ded8c965d34 --- /dev/null +++ b/lib/tests/fixtures/events.php @@ -0,0 +1,42 @@ +. + +/** + * Test event handler definition used only from unit tests. + * + * @package core + * @subpackage event + * @copyright 2007 onwards Martin Dougiamas (http://dougiamas.com) + * @author Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$handlers = array ( + 'test_instant' => array ( + 'handlerfile' => '/lib/tests/eventslib_test.php', + 'handlerfunction' => 'sample_function_handler', + 'schedule' => 'instant', + 'internal' => 1, + ), + + 'test_cron' => array ( + 'handlerfile' => '/lib/tests/eventslib_test.php', + 'handlerfunction' => array('sample_handler_class', 'static_method'), + 'schedule' => 'cron', + 'internal' => 1, + ) +); + diff --git a/lib/tests/formslib_test.php b/lib/tests/formslib_test.php new file mode 100644 index 0000000000000..b4bebf32870d4 --- /dev/null +++ b/lib/tests/formslib_test.php @@ -0,0 +1,220 @@ +. + +/** + * Unit tests for /lib/formslib.php. + * + * @package core_form + * @category phpunit + * @copyright 2011 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/formslib.php'); +require_once($CFG->libdir . '/form/radio.php'); +require_once($CFG->libdir . '/form/select.php'); +require_once($CFG->libdir . '/form/text.php'); + + +class formslib_testcase extends basic_testcase { + + public function test_require_rule() { + global $CFG; + + $strictformsrequired = null; + if (isset($CFG->strictformsrequired)) { + $strictformsrequired = $CFG->strictformsrequired; + } + + $rule = new MoodleQuickForm_Rule_Required(); + + // First run the tests with strictformsrequired off + $CFG->strictformsrequired = false; + // Passes + $this->assertTrue($rule->validate('Something')); + $this->assertTrue($rule->validate("Something\nmore")); + $this->assertTrue($rule->validate("\nmore")); + $this->assertTrue($rule->validate(" more ")); + $this->assertTrue($rule->validate("0")); + $this->assertTrue($rule->validate(0)); + $this->assertTrue($rule->validate(true)); + $this->assertTrue($rule->validate(' ')); + $this->assertTrue($rule->validate(' ')); + $this->assertTrue($rule->validate("\t")); + $this->assertTrue($rule->validate("\n")); + $this->assertTrue($rule->validate("\r")); + $this->assertTrue($rule->validate("\r\n")); + $this->assertTrue($rule->validate(" \t \n \r ")); + $this->assertTrue($rule->validate('

')); + $this->assertTrue($rule->validate('

')); + $this->assertTrue($rule->validate('

x

')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate(' ')); + // Fails + $this->assertFalse($rule->validate('')); + $this->assertFalse($rule->validate(false)); + $this->assertFalse($rule->validate(null)); + + // Now run the same tests with it on to make sure things work as expected + $CFG->strictformsrequired = true; + // Passes + $this->assertTrue($rule->validate('Something')); + $this->assertTrue($rule->validate("Something\nmore")); + $this->assertTrue($rule->validate("\nmore")); + $this->assertTrue($rule->validate(" more ")); + $this->assertTrue($rule->validate("0")); + $this->assertTrue($rule->validate(0)); + $this->assertTrue($rule->validate(true)); + $this->assertTrue($rule->validate('

x

')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('smile')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + $this->assertTrue($rule->validate('
')); + // Fails + $this->assertFalse($rule->validate(' ')); + $this->assertFalse($rule->validate(' ')); + $this->assertFalse($rule->validate("\t")); + $this->assertFalse($rule->validate("\n")); + $this->assertFalse($rule->validate("\r")); + $this->assertFalse($rule->validate("\r\n")); + $this->assertFalse($rule->validate(" \t \n \r ")); + $this->assertFalse($rule->validate('

')); + $this->assertFalse($rule->validate('

')); + $this->assertFalse($rule->validate('
')); + $this->assertFalse($rule->validate('
')); + $this->assertFalse($rule->validate('
')); + $this->assertFalse($rule->validate(' ')); + $this->assertFalse($rule->validate('')); + $this->assertFalse($rule->validate(false)); + $this->assertFalse($rule->validate(null)); + + if (isset($strictformsrequired)) { + $CFG->strictformsrequired = $strictformsrequired; + } + } + + public function test_generate_id_select() { + $el = new MoodleQuickForm_select('choose_one', 'Choose one', + array(1 => 'One', '2' => 'Two')); + $el->_generateId(); + $this->assertEquals('id_choose_one', $el->getAttribute('id')); + } + + public function test_generate_id_like_repeat() { + $el = new MoodleQuickForm_text('text[7]', 'Type something'); + $el->_generateId(); + $this->assertEquals('id_text_7', $el->getAttribute('id')); + } + + public function test_can_manually_set_id() { + $el = new MoodleQuickForm_text('elementname', 'Type something', + array('id' => 'customelementid')); + $el->_generateId(); + $this->assertEquals('customelementid', $el->getAttribute('id')); + } + + public function test_generate_id_radio() { + $el = new MoodleQuickForm_radio('radio', 'Label', 'Choice label', 'choice_value'); + $el->_generateId(); + $this->assertEquals('id_radio_choice_value', $el->getAttribute('id')); + } + + public function test_radio_can_manually_set_id() { + $el = new MoodleQuickForm_radio('radio2', 'Label', 'Choice label', 'choice_value', + array('id' => 'customelementid2')); + $el->_generateId(); + $this->assertEquals('customelementid2', $el->getAttribute('id')); + } + + public function test_generate_id_radio_like_repeat() { + $el = new MoodleQuickForm_radio('repeatradio[2]', 'Label', 'Choice label', 'val'); + $el->_generateId(); + $this->assertEquals('id_repeatradio_2_val', $el->getAttribute('id')); + } + + public function test_rendering() { + $form = new formslib_test_form(); + ob_start(); + $form->display(); + $html = ob_get_clean(); + + $this->assertTag(array('tag'=>'select', 'id'=>'id_choose_one', + 'attributes'=>array('name'=>'choose_one')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_text_0', + 'attributes'=>array('type'=>'text', 'name'=>'text[0]')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_text_1', + 'attributes'=>array('type'=>'text', 'name'=>'text[1]')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_radio_choice_value', + 'attributes'=>array('type'=>'radio', 'name'=>'radio', 'value'=>'choice_value')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'customelementid2', + 'attributes'=>array('type'=>'radio', 'name'=>'radio2')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_repeatradio_0_2', + 'attributes'=>array('type'=>'radio', 'name'=>'repeatradio[0]', 'value'=>'2')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_repeatradio_2_1', + 'attributes'=>array('type'=>'radio', 'name'=>'repeatradio[2]', 'value'=>'1')), $html); + + $this->assertTag(array('tag'=>'input', 'id'=>'id_repeatradio_2_2', + 'attributes'=>array('type'=>'radio', 'name'=>'repeatradio[2]', 'value'=>'2')), $html); + } +} + + +/** + * Test form to be used by {@link formslib_test::test_rendering()}. + */ +class formslib_test_form extends moodleform { + public function definition() { + $this->_form->addElement('select', 'choose_one', 'Choose one', + array(1 => 'One', '2' => 'Two')); + + $repeatels = array( + $this->_form->createElement('text', 'text', 'Type something') + ); + $this->repeat_elements($repeatels, 2, array(), 'numtexts', 'addtexts'); + + $this->_form->addElement('radio', 'radio', 'Label', 'Choice label', 'choice_value'); + + $this->_form->addElement('radio', 'radio2', 'Label', 'Choice label', 'choice_value', + array('id' => 'customelementid2')); + + $repeatels = array( + $this->_form->createElement('radio', 'repeatradio', 'Choose {no}', 'One', 1), + $this->_form->createElement('radio', 'repeatradio', 'Choose {no}', 'Two', 2), + ); + $this->repeat_elements($repeatels, 3, array(), 'numradios', 'addradios'); + } +} \ No newline at end of file diff --git a/lib/tests/html2text_test.php b/lib/tests/html2text_test.php new file mode 100644 index 0000000000000..c4f54ff21f85e --- /dev/null +++ b/lib/tests/html2text_test.php @@ -0,0 +1,146 @@ +. + +/** + * Tests our html2text hacks + * + * Note: includes original tests from testweblib.php + * + * @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 + */ + + +defined('MOODLE_INTERNAL') || die(); + + +class html2text_testcase extends basic_testcase { + + /** + * ALT as image replacements + */ + public function test_images() { + $this->assertEquals('[edit]', html_to_text('edit')); + + $text = 'xxsome gifxx'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, 'xx[some gif]xx'); + } + + /** + * No magic quotes messing + */ + public function test_no_strip_slashes() { + $this->assertEquals('[\edit]', html_to_text('\edit')); + + $text = '\\magic\\quotes\\are\\\\horrible'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, $text); + } + + /** + * Textlib integration + */ + public function test_textlib() { + $text = 'Žluťoučký koníček'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, 'ŽLUŤOUČKÝ KONÍČEK'); + } + + /** + * Protect 0 + */ + public function test_zero() { + $text = '0'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, $text); + + $this->assertSame('0', html_to_text('0')); + } + + // ======= Standard html2text conversion features ======= + + /** + * Various invalid HTML typed by users that ignore html strict + **/ + public function test_invalid_html() { + $text = 'Gin & Tonic'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, $text); + + $text = 'Gin > Tonic'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, $text); + + $text = 'Gin < Tonic'; + $result = html_to_text($text, null, false, false); + $this->assertSame($result, $text); + } + + /** + * Basic text formatting. + */ + public function test_simple() { + $this->assertEquals("_Hello_ WORLD!", html_to_text('

Hello world!

')); + $this->assertEquals("All the WORLD’S a stage.\n\n-- William Shakespeare", html_to_text('

All the world’s a stage.

-- William Shakespeare

')); + $this->assertEquals("HELLO WORLD!\n\n", html_to_text('

Hello world!

')); + $this->assertEquals("Hello\nworld!", html_to_text('Hello
world!')); + } + + /** + * Test line wrapping + */ + public function test_text_nowrap() { + $long = "Here is a long string, more than 75 characters long, since by default html_to_text wraps text at 75 chars."; + $wrapped = "Here is a long string, more than 75 characters long, since by default\nhtml_to_text wraps text at 75 chars."; + $this->assertEquals($long, html_to_text($long, 0)); + $this->assertEquals($wrapped, html_to_text($long)); + } + + /** + * Whitespace removal + */ + public function test_trailing_whitespace() { + $this->assertEquals('With trailing whitespace and some more text', html_to_text("With trailing whitespace \nand some more text", 0)); + } + + /** + * PRE parsing + */ + public function test_html_to_text_pre_parsing_problem() { + $strorig = 'Consider the following function:
void FillMeUp(char* in_string) {'.
+            '
int i = 0;
while (in_string[i] != \'\0\') {
in_string[i] = \'X\';
i++;
}
'. + '}
What would happen if a non-terminated string were input to this function?

'; + + $strconv = 'Consider the following function: + +void FillMeUp(char* in_string) { + int i = 0; + while (in_string[i] != \'\0\') { + in_string[i] = \'X\'; + i++; + } +} +What would happen if a non-terminated string were input to this function? + +'; + + $this->assertSame($strconv, html_to_text($strorig)); + } +} + diff --git a/lib/tests/htmlwriter_test.php b/lib/tests/htmlwriter_test.php new file mode 100644 index 0000000000000..eaa82db4f6ecc --- /dev/null +++ b/lib/tests/htmlwriter_test.php @@ -0,0 +1,92 @@ +. + + +/** + * Unit tests for the html_writer class. + * + * @package core + * @category phpunit + * @copyright 2010 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/outputcomponents.php'); + + +/** + * Unit tests for the html_writer class. + * + * @copyright 2010 Tim Hunt + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class html_writer_testcase extends basic_testcase { + + public function test_start_tag() { + $this->assertEquals('
', html_writer::start_tag('div')); + } + + public function test_start_tag_with_attr() { + $this->assertEquals('
', + html_writer::start_tag('div', array('class' => 'frog'))); + } + + public function test_start_tag_with_attrs() { + $this->assertEquals('
', + html_writer::start_tag('div', array('class' => 'frog', 'id' => 'mydiv'))); + } + + public function test_end_tag() { + $this->assertEquals('
', html_writer::end_tag('div')); + } + + public function test_empty_tag() { + $this->assertEquals('
', html_writer::empty_tag('br')); + } + + public function test_empty_tag_with_attrs() { + $this->assertEquals('', + html_writer::empty_tag('input', array('type' => 'submit', 'value' => 'frog'))); + } + + public function test_nonempty_tag_with_content() { + $this->assertEquals('
Hello world!
', + html_writer::nonempty_tag('div', 'Hello world!')); + } + + public function test_nonempty_tag_empty() { + $this->assertEquals('', + html_writer::nonempty_tag('div', '')); + } + + public function test_nonempty_tag_null() { + $this->assertEquals('', + html_writer::nonempty_tag('div', null)); + } + + public function test_nonempty_tag_zero() { + $this->assertEquals('
0
', + html_writer::nonempty_tag('div', 0, array('class' => 'score'))); + } + + public function test_nonempty_tag_zero_string() { + $this->assertEquals('
0
', + html_writer::nonempty_tag('div', '0', array('class' => 'score'))); + } +} diff --git a/lib/tests/mathslib_test.php b/lib/tests/mathslib_test.php new file mode 100644 index 0000000000000..4c56eed2196f9 --- /dev/null +++ b/lib/tests/mathslib_test.php @@ -0,0 +1,218 @@ +. + +/** + * Unit tests of mathslib wrapper and underlying EvalMath library. + * + * @package core + * @category phpunit + * @copyright 2007 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/mathslib.php'); + + +class mathsslib_testcase extends basic_testcase { + + /** + * Tests the basic formula evaluation + */ + public function test__basic() { + $formula = new calc_formula('=1+2'); + $res = $formula->evaluate(); + $this->assertEquals($res, 3, '3+1 is: %s'); + } + + /** + * Tests the formula params + */ + public function test__params() { + $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 60, '10+20+30 is: %s'); + } + + /** + * Tests the changed params + */ + public function test__changing_params() { + $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 60, '10+20+30 is: %s'); + $formula->set_params(array('a'=>1, 'b'=>2, 'c'=>3)); + $res = $formula->evaluate(); + $this->assertEquals($res, 6, 'changed params 1+2+3 is: %s'); + } + + /** + * Tests the spreadsheet emulation function in formula + */ + public function test__calc_function() { + $formula = new calc_formula('=sum(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 60, 'sum(a, b, c) is: %s'); + } + + public function test_other_functions() { + $formula = new calc_formula('=average(1,2,3)'); + $this->assertEquals($formula->evaluate(), 2); + + $formula = new calc_formula('=mod(10,3)'); + $this->assertEquals($formula->evaluate(), 1); + + $formula = new calc_formula('=power(2,3)'); + $this->assertEquals($formula->evaluate(), 8); + } + + /** + * Tests the min and max functions + */ + public function test__minmax_function() { + $formula = new calc_formula('=min(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 10, 'minimum is: %s'); + $formula = new calc_formula('=max(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 30, 'maximum is: %s'); + } + + /** + * Tests special chars + */ + public function test__specialchars() { + $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1'=>10, 'gi2'=>20, 'gi11'=>30)); + $res = $formula->evaluate(); + $this->assertEquals($res, 60, 'sum is: %s'); + } + + /** + * Tests some slightly more complex expressions + */ + public function test__more_complex_expressions() { + $formula = new calc_formula('=pi() + a', array('a'=>10)); + $res = $formula->evaluate(); + $this->assertEquals($res, pi()+10); + $formula = new calc_formula('=pi()^a', array('a'=>10)); + $res = $formula->evaluate(); + $this->assertEquals($res, pow(pi(), 10)); + $formula = new calc_formula('=-8*(5/2)^2*(1-sqrt(4))-8'); + $res = $formula->evaluate(); + $this->assertEquals($res, -8*pow((5/2), 2)*(1-sqrt(4))-8); + } + + /** + * Tests some slightly more complex expressions + */ + public function test__error_handling() { + $formula = new calc_formula('=pi( + a', array('a'=>10)); + $res = $formula->evaluate(); + $this->assertEquals($res, false); + $this->assertEquals($formula->get_error(), + get_string('unexpectedoperator', 'mathslib', '+')); + + $formula = new calc_formula('=pi('); + $res = $formula->evaluate(); + $this->assertEquals($res, false); + $this->assertEquals($formula->get_error(), + get_string('expectingaclosingbracket', 'mathslib')); + + $formula = new calc_formula('=pi()^'); + $res = $formula->evaluate(); + $this->assertEquals($res, false); + $this->assertEquals($formula->get_error(), + get_string('operatorlacksoperand', 'mathslib', '^')); + + } + + public function test_rounding_function() { + $formula = new calc_formula('=round(2.5)'); + $this->assertEquals($formula->evaluate(), 3); + + $formula = new calc_formula('=round(1.5)'); + $this->assertEquals($formula->evaluate(), 2); + + $formula = new calc_formula('=round(-1.49)'); + $this->assertEquals($formula->evaluate(), -1); + + $formula = new calc_formula('=round(-2.49)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=round(-1.5)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=round(-2.5)'); + $this->assertEquals($formula->evaluate(), -3); + + $formula = new calc_formula('=ceil(2.5)'); + $this->assertEquals($formula->evaluate(), 3); + + $formula = new calc_formula('=ceil(1.5)'); + $this->assertEquals($formula->evaluate(), 2); + + $formula = new calc_formula('=ceil(-1.49)'); + $this->assertEquals($formula->evaluate(), -1); + + $formula = new calc_formula('=ceil(-2.49)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=ceil(-1.5)'); + $this->assertEquals($formula->evaluate(), -1); + + $formula = new calc_formula('=ceil(-2.5)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=floor(2.5)'); + $this->assertEquals($formula->evaluate(), 2); + + $formula = new calc_formula('=floor(1.5)'); + $this->assertEquals($formula->evaluate(), 1); + + $formula = new calc_formula('=floor(-1.49)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=floor(-2.49)'); + $this->assertEquals($formula->evaluate(), -3); + + $formula = new calc_formula('=floor(-1.5)'); + $this->assertEquals($formula->evaluate(), -2); + + $formula = new calc_formula('=floor(-2.5)'); + $this->assertEquals($formula->evaluate(), -3); + + } + + public function test_scientific_notation() { + $formula = new calc_formula('=10e10'); + $this->assertEquals($formula->evaluate(), 1e11, '', 1e11*1e-15); + + $formula = new calc_formula('=10e-10'); + $this->assertEquals($formula->evaluate(), 1e-9, '', 1e11*1e-15); + + $formula = new calc_formula('=10e+10'); + $this->assertEquals($formula->evaluate(), 1e11, '', 1e11*1e-15); + + $formula = new calc_formula('=10e10*5'); + $this->assertEquals($formula->evaluate(), 5e11, '', 1e11*1e-15); + + $formula = new calc_formula('=10e10^2'); + $this->assertEquals($formula->evaluate(), 1e22, '', 1e22*1e-15); + + } +} diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php new file mode 100644 index 0000000000000..af6125e752945 --- /dev/null +++ b/lib/tests/moodlelib_test.php @@ -0,0 +1,1823 @@ +. + +/** + * Unit tests for (some of) ../moodlelib.php. + * + * @package core + * @category phpunit + * @copyright © 2006 The Open University + * @author T.J.Hunt@open.ac.uk + * @author nicolas@moodle.com + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/moodlelib.php'); + + +class moodlelib_test extends advanced_testcase { + + public static $includecoverage = array('lib/moodlelib.php'); + + var $user_agents = array( + 'MSIE' => array( + '5.0' => array('Windows 98' => 'Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)'), + '5.5' => array('Windows 2000' => 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)'), + '6.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'), + '7.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.0.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'), + '8.0' => array('Windows Vista' => 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)'), + '9.0' => array('Windows 7' => 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'), + + ), + 'Firefox' => array( + '1.0.6' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6'), + '1.5' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.8) Gecko/20051107 Firefox/1.5'), + '1.5.0.1' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1'), + '2.0' => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1', + 'Ubuntu Linux AMD64' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1) Gecko/20060601 Firefox/2.0 (Ubuntu-edgy)'), + '3.0.6' => array('SUSE' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.0.6) Gecko/2009012700 SUSE/3.0.6-1.4 Firefox/3.0.6'), + ), + 'Safari' => array( + '312' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312'), + '412' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412 (KHTML, like Gecko) Safari/412') + ), + 'Safari iOS' => array( + '528' => array('iPhone' => 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_1_2 like Mac OS X; cs-cz) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7D11 Safari/528.16'), + '533' => array('iPad' => 'Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5'), + ), + 'WebKit Android' => array( + '525' => array('G1 Phone' => 'Mozilla/5.0 (Linux; U; Android 1.1; en-gb; dream) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2 – G1 Phone'), + '530' => array('Nexus' => 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17 –Nexus'), + ), + 'Chrome' => array( + '8' => array('Mac OS X' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10'), + ), + 'Opera' => array( + '8.51' => array('Windows XP' => 'Opera/8.51 (Windows NT 5.1; U; en)'), + '9.0' => array('Windows XP' => 'Opera/9.0 (Windows NT 5.1; U; en)', + 'Debian Linux' => 'Opera/9.01 (X11; Linux i686; U; en)') + ) + ); + + function test_cleanremoteaddr() { + //IPv4 + $this->assertEquals(cleanremoteaddr('1023.121.234.1'), null); + $this->assertEquals(cleanremoteaddr('123.121.234.01 '), '123.121.234.1'); + + //IPv6 + $this->assertEquals(cleanremoteaddr('0:0:0:0:0:0:0:0:0'), null); + $this->assertEquals(cleanremoteaddr('0:0:0:0:0:0:0:abh'), null); + $this->assertEquals(cleanremoteaddr('0:0:0:::0:0:1'), null); + $this->assertEquals(cleanremoteaddr('0:0:0:0:0:0:0:0', true), '::'); + $this->assertEquals(cleanremoteaddr('0:0:0:0:0:0:1:1', true), '::1:1'); + $this->assertEquals(cleanremoteaddr('abcd:00ef:0:0:0:0:0:0', true), 'abcd:ef::'); + $this->assertEquals(cleanremoteaddr('1:0:0:0:0:0:0:1', true), '1::1'); + $this->assertEquals(cleanremoteaddr('::10:1', false), '0:0:0:0:0:0:10:1'); + $this->assertEquals(cleanremoteaddr('01:1::', false), '1:1:0:0:0:0:0:0'); + $this->assertEquals(cleanremoteaddr('10::10', false), '10:0:0:0:0:0:0:10'); + $this->assertEquals(cleanremoteaddr('::ffff:192.168.1.1', true), '::ffff:c0a8:11'); + } + + function test_address_in_subnet() { + /// 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn (number of bits in net mask) + $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.1/32')); + $this->assertFalse(address_in_subnet('123.121.23.1', '123.121.23.0/32')); + $this->assertTrue(address_in_subnet('10.10.10.100', '123.121.23.45/0')); + $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/24')); + $this->assertFalse(address_in_subnet('123.121.34.1', '123.121.234.0/24')); + $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/30')); + $this->assertFalse(address_in_subnet('123.121.23.8', '123.121.23.0/30')); + $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128')); + $this->assertFalse(address_in_subnet('bab:baba::baba', 'bab:baba::cece/128')); + $this->assertTrue(address_in_subnet('baba:baba::baba', 'cece:cece::cece/0')); + $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128')); + $this->assertTrue(address_in_subnet('baba:baba::00ba', 'baba:baba::/120')); + $this->assertFalse(address_in_subnet('baba:baba::aba', 'baba:baba::/120')); + $this->assertTrue(address_in_subnet('baba::baba:00ba', 'baba::baba:0/112')); + $this->assertFalse(address_in_subnet('baba::aba:00ba', 'baba::baba:0/112')); + $this->assertFalse(address_in_subnet('aba::baba:0000', 'baba::baba:0/112')); + + // fixed input + $this->assertTrue(address_in_subnet('123.121.23.1 ', ' 123.121.23.0 / 24')); + $this->assertTrue(address_in_subnet('::ffff:10.1.1.1', ' 0:0:0:000:0:ffff:a1:10 / 126')); + + // incorrect input + $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/-2')); + $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/64')); + $this->assertFalse(address_in_subnet('123.121.234.x', '123.121.234.1/24')); + $this->assertFalse(address_in_subnet('123.121.234.0', '123.121.234.xx/24')); + $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/xx0')); + $this->assertFalse(address_in_subnet('::1', '::aa:0/xx0')); + $this->assertFalse(address_in_subnet('::1', '::aa:0/-5')); + $this->assertFalse(address_in_subnet('::1', '::aa:0/130')); + $this->assertFalse(address_in_subnet('x:1', '::aa:0/130')); + $this->assertFalse(address_in_subnet('::1', '::ax:0/130')); + + + /// 2: xxx.xxx.xxx.xxx-yyy or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group) + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12-14')); + $this->assertTrue(address_in_subnet('123.121.234.13', '123.121.234.12-14')); + $this->assertTrue(address_in_subnet('123.121.234.14', '123.121.234.12-14')); + $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.12-14')); + $this->assertFalse(address_in_subnet('123.121.234.20', '123.121.234.12-14')); + $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.234.12-14')); + $this->assertFalse(address_in_subnet('123.12.234.12', '123.121.234.12-14')); + $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba-babe')); + $this->assertTrue(address_in_subnet('baba:baba::babc', 'baba:baba::baba-babe')); + $this->assertTrue(address_in_subnet('baba:baba::babe', 'baba:baba::baba-babe')); + $this->assertFalse(address_in_subnet('bab:baba::bab0', 'bab:baba::baba-babe')); + $this->assertFalse(address_in_subnet('bab:baba::babf', 'bab:baba::baba-babe')); + $this->assertFalse(address_in_subnet('bab:baba::bfbe', 'bab:baba::baba-babe')); + $this->assertFalse(address_in_subnet('bfb:baba::babe', 'bab:baba::baba-babe')); + + // fixed input + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12 - 14 ')); + $this->assertTrue(address_in_subnet('bab:baba::babe', 'bab:baba::baba - babe ')); + + // incorrect input + $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-234.14')); + $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-256')); + $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12--256')); + + + /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx. (incomplete address, a bit non-technical ;-) + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12')); + $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.23.13')); + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.')); + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234')); + $this->assertTrue(address_in_subnet('123.121.234.12', '123.121')); + $this->assertTrue(address_in_subnet('123.121.234.12', '123')); + $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234.')); + $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234')); + $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba::bab')); + $this->assertFalse(address_in_subnet('baba:baba::ba', 'baba:baba::bc')); + $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba')); + $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:')); + $this->assertFalse(address_in_subnet('bab:baba::bab', 'baba:')); + + + /// multiple subnets + $this->assertTrue(address_in_subnet('123.121.234.12', '::1/64, 124., 123.121.234.10-30')); + $this->assertTrue(address_in_subnet('124.121.234.12', '::1/64, 124., 123.121.234.10-30')); + $this->assertTrue(address_in_subnet('::2', '::1/64, 124., 123.121.234.10-30')); + $this->assertFalse(address_in_subnet('12.121.234.12', '::1/64, 124., 123.121.234.10-30')); + + + /// other incorrect input + $this->assertFalse(address_in_subnet('123.123.123.123', '')); + } + + /** + * Modifies $_SERVER['HTTP_USER_AGENT'] manually to check if check_browser_version + * works as expected. + */ + function test_check_browser_version() + { + global $CFG; + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['412']['Mac OS X']; + $this->assertTrue(check_browser_version('Safari')); + $this->assertTrue(check_browser_version('WebKit')); + $this->assertTrue(check_browser_version('Safari', '312')); + $this->assertFalse(check_browser_version('Safari', '500')); + $this->assertFalse(check_browser_version('Chrome')); + $this->assertFalse(check_browser_version('Safari iOS')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari iOS']['528']['iPhone']; + $this->assertTrue(check_browser_version('Safari iOS')); + $this->assertTrue(check_browser_version('WebKit')); + $this->assertTrue(check_browser_version('Safari iOS', '527')); + $this->assertFalse(check_browser_version('Safari iOS', 590)); + $this->assertFalse(check_browser_version('Safari', '312')); + $this->assertFalse(check_browser_version('Safari', '500')); + $this->assertFalse(check_browser_version('Chrome')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['WebKit Android']['530']['Nexus']; + $this->assertTrue(check_browser_version('WebKit')); + $this->assertTrue(check_browser_version('WebKit Android', '527')); + $this->assertFalse(check_browser_version('WebKit Android', 590)); + $this->assertFalse(check_browser_version('Safari')); + $this->assertFalse(check_browser_version('Chrome')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Chrome']['8']['Mac OS X']; + $this->assertTrue(check_browser_version('Chrome')); + $this->assertTrue(check_browser_version('WebKit')); + $this->assertTrue(check_browser_version('Chrome', 8)); + $this->assertFalse(check_browser_version('Chrome', 10)); + $this->assertFalse(check_browser_version('Safari', '1')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['9.0']['Windows XP']; + $this->assertTrue(check_browser_version('Opera')); + $this->assertTrue(check_browser_version('Opera', '8.0')); + $this->assertFalse(check_browser_version('Opera', '10.0')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['6.0']['Windows XP SP2']; + $this->assertTrue(check_browser_version('MSIE')); + $this->assertTrue(check_browser_version('MSIE', '5.0')); + $this->assertFalse(check_browser_version('MSIE', '7.0')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['5.0']['Windows 98']; + $this->assertFalse(check_browser_version('MSIE')); + $this->assertTrue(check_browser_version('MSIE', 0)); + $this->assertTrue(check_browser_version('MSIE', '5.0')); + $this->assertFalse(check_browser_version('MSIE', '7.0')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['9.0']['Windows 7']; + $this->assertTrue(check_browser_version('MSIE')); + $this->assertTrue(check_browser_version('MSIE', 0)); + $this->assertTrue(check_browser_version('MSIE', '5.0')); + $this->assertTrue(check_browser_version('MSIE', '9.0')); + $this->assertFalse(check_browser_version('MSIE', '10')); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP']; + $this->assertTrue(check_browser_version('Firefox')); + $this->assertTrue(check_browser_version('Firefox', '1.5')); + $this->assertFalse(check_browser_version('Firefox', '3.0')); + } + + function test_get_browser_version_classes() { + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari']['412']['Mac OS X']; + $this->assertEquals(array('safari'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Chrome']['8']['Mac OS X']; + $this->assertEquals(array('safari'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Safari iOS']['528']['iPhone']; + $this->assertEquals(array('safari', 'ios'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['WebKit Android']['530']['Nexus']; + $this->assertEquals(array('safari', 'android'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Chrome']['8']['Mac OS X']; + $this->assertEquals(array('safari'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Opera']['9.0']['Windows XP']; + $this->assertEquals(array('opera'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['6.0']['Windows XP SP2']; + $this->assertEquals(array('ie', 'ie6'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['7.0']['Windows XP SP2']; + $this->assertEquals(array('ie', 'ie7'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['8.0']['Windows Vista']; + $this->assertEquals(array('ie', 'ie8'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP']; + $this->assertEquals(array('gecko', 'gecko18'), get_browser_version_classes()); + + $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['3.0.6']['SUSE']; + $this->assertEquals(array('gecko', 'gecko19'), get_browser_version_classes()); + } + + function test_get_device_type() { + // IE8 (common pattern ~1.5% of IE7/8 users have embedded IE6 agent)) + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; BT Openworld BB; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; Hotbar 10.2.197.0; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)'; + $this->assertEquals('default', get_device_type()); + // Genuine IE6 + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/4.0 (compatible; MSIE 6.0; AOL 9.0; Windows NT 5.1; SV1; FunWebProducts; .NET CLR 1.0.3705; Media Center PC 2.8)'; + $this->assertEquals('legacy', get_device_type()); + } + + function test_fix_utf8() { + // make sure valid data including other types is not changed + $this->assertSame(null, fix_utf8(null)); + $this->assertSame(1, fix_utf8(1)); + $this->assertSame(1.1, fix_utf8(1.1)); + $this->assertSame(true, fix_utf8(true)); + $this->assertSame('', fix_utf8('')); + $array = array('do', 're', 'mi'); + $this->assertSame($array, fix_utf8($array)); + $object = new stdClass(); + $object->a = 'aa'; + $object->b = 'bb'; + $this->assertEquals($object, fix_utf8($object)); + + // valid utf8 string + $this->assertSame("žlutý koníček přeskočil potůček \n\t\r\0", fix_utf8("žlutý koníček přeskočil potůček \n\t\r\0")); + + // invalid utf8 string + $this->assertSame('aaabbb', fix_utf8('aaa'.chr(130).'bbb')); + } + + function test_optional_param() { + global $CFG; + + $_POST['username'] = 'post_user'; + $_GET['username'] = 'get_user'; + $this->assertSame(optional_param('username', 'default_user', PARAM_RAW), $_POST['username']); + + unset($_POST['username']); + $this->assertSame(optional_param('username', 'default_user', PARAM_RAW), $_GET['username']); + + unset($_GET['username']); + $this->assertSame(optional_param('username', 'default_user', PARAM_RAW), 'default_user'); + + // make sure exception is triggered when some params are missing, hide error notices here - new in 2.2 + $_POST['username'] = 'post_user'; + try { + optional_param('username', 'default_user', null); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + @optional_param('username', 'default_user'); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + @optional_param('username'); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + optional_param('', 'default_user', PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3 + $debugging = isset($CFG->debug) ? $CFG->debug : null; + $debugdisplay = isset($CFG->debugdisplay) ? $CFG->debugdisplay : null; + $CFG->debug = DEBUG_DEVELOPER; + $CFG->debugdisplay = true; + + ob_start(); + $this->assertSame(optional_param('username', 'default_user', PARAM_RAW), $_POST['username']); + $d = ob_end_clean(); + $this->assertTrue($d !== ''); + + if ($debugging !== null) { + $CFG->debug = $debugging; + } else { + unset($CFG->debug); + } + if ($debugdisplay !== null) { + $CFG->debugdisplay = $debugdisplay; + } else { + unset($CFG->debugdisplay); + } + } + + function test_optional_param_array() { + global $CFG; + + $_POST['username'] = array('a'=>'post_user'); + $_GET['username'] = array('a'=>'get_user'); + $this->assertSame(optional_param_array('username', array('a'=>'default_user'), PARAM_RAW), $_POST['username']); + + unset($_POST['username']); + $this->assertSame(optional_param_array('username', array('a'=>'default_user'), PARAM_RAW), $_GET['username']); + + unset($_GET['username']); + $this->assertSame(optional_param_array('username', array('a'=>'default_user'), PARAM_RAW), array('a'=>'default_user')); + + // make sure exception is triggered when some params are missing, hide error notices here - new in 2.2 + $_POST['username'] = array('a'=>'post_user'); + try { + optional_param_array('username', array('a'=>'default_user'), null); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + @optional_param_array('username', array('a'=>'default_user')); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + @optional_param_array('username'); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + optional_param_array('', array('a'=>'default_user'), PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // do not allow nested arrays + try { + $_POST['username'] = array('a'=>array('b'=>'post_user')); + optional_param_array('username', array('a'=>'default_user'), PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // do not allow non-arrays + $debugging = isset($CFG->debug) ? $CFG->debug : null; + $debugdisplay = isset($CFG->debugdisplay) ? $CFG->debugdisplay : null; + $CFG->debug = DEBUG_DEVELOPER; + $CFG->debugdisplay = true; + + ob_start(); + $_POST['username'] = 'post_user'; + $this->assertSame(optional_param_array('username', array('a'=>'default_user'), PARAM_RAW), array('a'=>'default_user')); + $d = ob_end_clean(); + $this->assertTrue($d !== ''); + + // make sure array keys are sanitised + ob_start(); + $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user'); + $this->assertSame(optional_param_array('username', array(), PARAM_RAW), array('a1_-'=>'post_user')); + $d = ob_end_clean(); + $this->assertTrue($d !== ''); + + if ($debugging !== null) { + $CFG->debug = $debugging; + } else { + unset($CFG->debug); + } + if ($debugdisplay !== null) { + $CFG->debugdisplay = $debugdisplay; + } else { + unset($CFG->debugdisplay); + } + } + + function test_required_param() { + global $CFG; + + $_POST['username'] = 'post_user'; + $_GET['username'] = 'get_user'; + $this->assertSame(required_param('username', PARAM_RAW), 'post_user'); + + unset($_POST['username']); + $this->assertSame(required_param('username', PARAM_RAW), 'get_user'); + + unset($_GET['username']); + try { + $this->assertSame(required_param('username', PARAM_RAW), 'default_user'); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // make sure exception is triggered when some params are missing, hide error notices here - new in 2.2 + $_POST['username'] = 'post_user'; + try { + @required_param('username'); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + required_param('username', ''); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + required_param('', PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // make sure warning is displayed if array submitted - TODO: throw exception in Moodle 2.3 + $debugging = isset($CFG->debug) ? $CFG->debug : null; + $debugdisplay = isset($CFG->debugdisplay) ? $CFG->debugdisplay : null; + $CFG->debug = DEBUG_DEVELOPER; + $CFG->debugdisplay = true; + + ob_start(); + $this->assertSame(required_param('username', PARAM_RAW), $_POST['username']); + $d = ob_end_clean(); + $this->assertTrue($d !== ''); + + if ($debugging !== null) { + $CFG->debug = $debugging; + } else { + unset($CFG->debug); + } + if ($debugdisplay !== null) { + $CFG->debugdisplay = $debugdisplay; + } else { + unset($CFG->debugdisplay); + } + } + + function test_required_param_array() { + global $CFG; + + $_POST['username'] = array('a'=>'post_user'); + $_GET['username'] = array('a'=>'get_user'); + $this->assertSame(required_param_array('username', PARAM_RAW), $_POST['username']); + + unset($_POST['username']); + $this->assertSame(required_param_array('username', PARAM_RAW), $_GET['username']); + + // make sure exception is triggered when some params are missing, hide error notices here - new in 2.2 + $_POST['username'] = array('a'=>'post_user'); + try { + required_param_array('username', null); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + @required_param_array('username'); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + required_param_array('', PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // do not allow nested arrays + try { + $_POST['username'] = array('a'=>array('b'=>'post_user')); + required_param_array('username', PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // do not allow non-arrays + try { + $_POST['username'] = 'post_user'; + required_param_array('username', PARAM_RAW); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + // do not allow non-arrays + $debugging = isset($CFG->debug) ? $CFG->debug : null; + $debugdisplay = isset($CFG->debugdisplay) ? $CFG->debugdisplay : null; + $CFG->debug = DEBUG_DEVELOPER; + $CFG->debugdisplay = true; + + // make sure array keys are sanitised + ob_start(); + $_POST['username'] = array('abc123_;-/*-+ '=>'arrggh', 'a1_-'=>'post_user'); + $this->assertSame(required_param_array('username', PARAM_RAW), array('a1_-'=>'post_user')); + $d = ob_end_clean(); + $this->assertTrue($d !== ''); + + if ($debugging !== null) { + $CFG->debug = $debugging; + } else { + unset($CFG->debug); + } + if ($debugdisplay !== null) { + $CFG->debugdisplay = $debugdisplay; + } else { + unset($CFG->debugdisplay); + } + } + + function test_clean_param() { + // forbid objects and arrays + try { + clean_param(array('x', 'y'), PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + try { + $param = new stdClass(); + $param->id = 1; + clean_param($param, PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // require correct type + try { + clean_param('x', 'xxxxxx'); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + try { + @clean_param('x'); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + } + + function test_clean_param_array() { + $this->assertSame(clean_param_array(null, PARAM_RAW), array()); + $this->assertSame(clean_param_array(array('a', 'b'), PARAM_RAW), array('a', 'b')); + $this->assertSame(clean_param_array(array('a', array('b')), PARAM_RAW, true), array('a', array('b'))); + + // require correct type + try { + clean_param_array(array('x'), 'xxxxxx'); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + try { + @clean_param_array(array('x')); + $this->fail('moodle_exception expected'); + } catch (moodle_exception $ex) { + $this->assertTrue(true); + } + + try { + clean_param_array(array('x', array('y')), PARAM_RAW); + $this->fail('coding_exception expected'); + } catch (coding_exception $ex) { + $this->assertTrue(true); + } + + // test recursive + } + + function test_clean_param_raw() { + $this->assertEquals(clean_param('#()*#,9789\'".,<42897>assertEquals(clean_param(" Frog toad \r\n ", PARAM_RAW_TRIMMED), 'Frog toad'); + } + + function test_clean_param_clean() { + // PARAM_CLEAN is an ugly hack, do not use in new code (skodak) + // instead use more specific type, or submit sothing that can be verified properly + $this->assertEquals(clean_param('xx