Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/admin/tool/unittest/simpletestlib.php
Go to the documentation of this file.
00001 <?php
00002 // This file is part of Moodle - http://moodle.org/
00003 //
00004 // Moodle is free software: you can redistribute it and/or modify
00005 // it under the terms of the GNU General Public License as published by
00006 // the Free Software Foundation, either version 3 of the License, or
00007 // (at your option) any later version.
00008 //
00009 // Moodle is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 // GNU General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00016 
00033 defined('MOODLE_INTERNAL') || die();
00034 
00038 require_once($CFG->libdir . '/simpletestlib/simpletest.php');
00039 require_once($CFG->libdir . '/simpletestlib/unit_tester.php');
00040 require_once($CFG->libdir . '/simpletestlib/expectation.php');
00041 require_once($CFG->libdir . '/simpletestlib/reporter.php');
00042 require_once($CFG->libdir . '/simpletestlib/web_tester.php');
00043 require_once($CFG->libdir . '/simpletestlib/mock_objects.php');
00044 
00056 function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
00057     $files = scandir($path);
00058 
00059     foreach ($files as $file) {
00060         $filepath = $path .'/'. $file;
00061         if (strpos($file, '.') === 0) {
00063             continue;
00064         } else if (is_dir($filepath)) {
00065             if (!in_array($filepath, $ignorefolders)) {
00066                 recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
00067             }
00068         } else if ($exclude xor preg_match($fileregexp, $filepath)) {
00069             call_user_func($callback, $filepath);
00070         }
00071     }
00072 }
00073 
00082 class IgnoreWhitespaceExpectation extends SimpleExpectation {
00083     var $expect;
00084 
00085     function IgnoreWhitespaceExpectation($content, $message = '%s') {
00086         $this->SimpleExpectation($message);
00087         $this->expect=$this->normalise($content);
00088     }
00089 
00090     function test($ip) {
00091         return $this->normalise($ip)==$this->expect;
00092     }
00093 
00094     function normalise($text) {
00095         return preg_replace('/\s+/m',' ',trim($text));
00096     }
00097 
00098     function testMessage($ip) {
00099         return "Input string [$ip] doesn't match the required value.";
00100     }
00101 }
00102 
00111 class ArraysHaveSameValuesExpectation extends SimpleExpectation {
00112     var $expect;
00113 
00114     function ArraysHaveSameValuesExpectation($expected, $message = '%s') {
00115         $this->SimpleExpectation($message);
00116         if (!is_array($expected)) {
00117             trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' .
00118                     'with an expected value that is not an array.');
00119         }
00120         $this->expect = $this->normalise($expected);
00121     }
00122 
00123     function test($actual) {
00124         return $this->normalise($actual) == $this->expect;
00125     }
00126 
00127     function normalise($array) {
00128         sort($array);
00129         return $array;
00130     }
00131 
00132     function testMessage($actual) {
00133         return 'Array [' . implode(', ', $actual) .
00134                 '] does not contain the expected list of values [' . implode(', ', $this->expect) . '].';
00135     }
00136 }
00137 
00138 
00149 class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
00150     var $expect;
00151 
00152     function CheckSpecifiedFieldsExpectation($expected, $message = '%s') {
00153         $this->SimpleExpectation($message);
00154         if (!is_object($expected)) {
00155             trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' .
00156                     'with an expected value that is not an object.');
00157         }
00158         $this->expect = $expected;
00159     }
00160 
00161     function test($actual) {
00162         foreach ($this->expect as $key => $value) {
00163             if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
00164                 // OK
00165             } else if (is_null($value) && is_null($actual->$key)) {
00166                 // OK
00167             } else {
00168                 return false;
00169             }
00170         }
00171         return true;
00172     }
00173 
00174     function testMessage($actual) {
00175         $mismatches = array();
00176         foreach ($this->expect as $key => $value) {
00177             if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
00178                 // OK
00179             } else if (is_null($value) && is_null($actual->$key)) {
00180                 // OK
00181             } else if (!isset($actual->$key)) {
00182                 $mismatches[] = $key . ' (expected [' . $value . '] but was missing.';
00183             } else {
00184                 $mismatches[] = $key . ' (expected [' . $value . '] got [' . $actual->$key . '].';
00185             }
00186         }
00187         return 'Actual object does not have all the same fields with the same values as the expected object (' .
00188                 implode(', ', $mismatches) . ').';
00189     }
00190 }
00191 
00192 abstract class XMLStructureExpectation extends SimpleExpectation {
00198     protected function load_xml($html) {
00199         $prevsetting = libxml_use_internal_errors(true);
00200         $parser = new DOMDocument();
00201         if (!$parser->loadXML('<html>' . $html . '</html>')) {
00202             $parser = new DOMDocument();
00203         }
00204         libxml_clear_errors();
00205         libxml_use_internal_errors($prevsetting);
00206         return $parser;
00207     }
00208 
00209     function testMessage($html) {
00210         $parsererrors = $this->load_xml($html);
00211         if (is_array($parsererrors)) {
00212             foreach ($parsererrors as $key => $message) {
00213                 $parsererrors[$key] = $message->message;
00214             }
00215             return 'Could not parse XML [' . $html . '] errors were [' .
00216                     implode('], [', $parsererrors) . ']';
00217         }
00218         return $this->customMessage($html);
00219     }
00220 }
00227 class ContainsTagWithAttribute extends XMLStructureExpectation {
00228     protected $tag;
00229     protected $attribute;
00230     protected $value;
00231 
00232     function __construct($tag, $attribute, $value, $message = '%s') {
00233         parent::__construct($message);
00234         $this->tag = $tag;
00235         $this->attribute = $attribute;
00236         $this->value = $value;
00237     }
00238 
00239     function test($html) {
00240         $parser = $this->load_xml($html);
00241         if (is_array($parser)) {
00242             return false;
00243         }
00244         $list = $parser->getElementsByTagName($this->tag);
00245 
00246         foreach ($list as $node) {
00247             if ($node->attributes->getNamedItem($this->attribute)->nodeValue === (string) $this->value) {
00248                 return true;
00249             }
00250         }
00251         return false;
00252     }
00253 
00254     function customMessage($html) {
00255         return 'Content [' . $html . '] does not contain the tag [' .
00256                 $this->tag . '] with attribute [' . $this->attribute . '="' . $this->value . '"].';
00257     }
00258 }
00259 
00268 class ContainsTagWithAttributes extends XMLStructureExpectation {
00272     protected $tag;
00276     protected $expectedvalues = array();
00280     protected $forbiddenvalues = array();
00284     protected $failurereason = 'nomatch';
00285 
00286     function __construct($tag, $expectedvalues, $forbiddenvalues=array(), $message = '%s') {
00287         parent::__construct($message);
00288         $this->tag = $tag;
00289         $this->expectedvalues = $expectedvalues;
00290         $this->forbiddenvalues = $forbiddenvalues;
00291     }
00292 
00293     function test($html) {
00294         $parser = $this->load_xml($html);
00295         if (is_array($parser)) {
00296             return false;
00297         }
00298 
00299         $list = $parser->getElementsByTagName($this->tag);
00300         $foundamatch = false;
00301 
00302         // Iterating through inputs
00303         foreach ($list as $node) {
00304             if (empty($node->attributes) || !is_a($node->attributes, 'DOMNamedNodeMap')) {
00305                 continue;
00306             }
00307 
00308             // For the current expected attribute under consideration, check that values match
00309             $allattributesmatch = true;
00310 
00311             foreach ($this->expectedvalues as $expectedattribute => $expectedvalue) {
00312                 if ($node->getAttribute($expectedattribute) === '' && $expectedvalue !== '') {
00313                     $this->failurereason = 'nomatch';
00314                     continue 2; // Skip this tag, it doesn't have all the expected attributes
00315                 }
00316                 if ($node->getAttribute($expectedattribute) !== (string) $expectedvalue) {
00317                     $allattributesmatch = false;
00318                     $this->failurereason = 'nomatch';
00319                 }
00320             }
00321 
00322             if ($allattributesmatch) {
00323                 $foundamatch = true;
00324 
00325                 // Now make sure this node doesn't have any of the forbidden attributes either
00326                 $nodeattrlist = $node->attributes;
00327 
00328                 foreach ($nodeattrlist as $domattrname => $domattr) {
00329                     if (array_key_exists($domattrname, $this->forbiddenvalues) && $node->getAttribute($domattrname) === (string) $this->forbiddenvalues[$domattrname]) {
00330                         $this->failurereason = "forbiddenmatch:$domattrname:" . $node->getAttribute($domattrname);
00331                         $foundamatch = false;
00332                     }
00333                 }
00334             }
00335         }
00336 
00337         return $foundamatch;
00338     }
00339 
00340     function customMessage($html) {
00341         $output = 'Content [' . $html . '] ';
00342 
00343         if (preg_match('/forbiddenmatch:(.*):(.*)/', $this->failurereason, $matches)) {
00344             $output .= "contains the tag $this->tag with the forbidden attribute=>value pair: [$matches[1]=>$matches[2]]";
00345         } else if ($this->failurereason == 'nomatch') {
00346             $output .= 'does not contain the tag [' . $this->tag . '] with attributes [';
00347             foreach ($this->expectedvalues as $var => $val) {
00348                 $output .= "$var=\"$val\" ";
00349             }
00350             $output = rtrim($output);
00351             $output .= '].';
00352         }
00353 
00354         return $output;
00355     }
00356 }
00357 
00366 class ContainsSelectExpectation extends XMLStructureExpectation {
00370     protected $name;
00374     protected $choices;
00378     protected $selected;
00382     protected $enabled;
00383 
00384     function __construct($name, $choices, $selected = null, $enabled = null, $message = '%s') {
00385         parent::__construct($message);
00386         $this->name = $name;
00387         $this->choices = $choices;
00388         $this->selected = $selected;
00389         $this->enabled = $enabled;
00390     }
00391 
00392     function test($html) {
00393         $parser = $this->load_xml($html);
00394         if (is_array($parser)) {
00395             return false;
00396         }
00397 
00398         $list = $parser->getElementsByTagName('select');
00399 
00400         // Iterating through inputs
00401         foreach ($list as $node) {
00402             if (empty($node->attributes) || !is_a($node->attributes, 'DOMNamedNodeMap')) {
00403                 continue;
00404             }
00405 
00406             if ($node->getAttribute('name') != $this->name) {
00407                 continue;
00408             }
00409 
00410             if ($this->enabled === true && $node->getAttribute('disabled')) {
00411                 continue;
00412             } else if ($this->enabled === false && $node->getAttribute('disabled') != 'disabled') {
00413                 continue;
00414             }
00415 
00416             $options = $node->getElementsByTagName('option');
00417             reset($this->choices);
00418             foreach ($options as $option) {
00419                 if ($option->getAttribute('value') != key($this->choices)) {
00420                     continue 2;
00421                 }
00422                 if ($option->firstChild->wholeText != current($this->choices)) {
00423                     continue 2;
00424                 }
00425                 if ($option->getAttribute('value') === $this->selected &&
00426                         !$option->hasAttribute('selected')) {
00427                     continue 2;
00428                 }
00429                 next($this->choices);
00430             }
00431             if (current($this->choices) !== false) {
00432                 // The HTML did not contain all the choices.
00433                 return false;
00434             }
00435             return true;
00436         }
00437         return false;
00438     }
00439 
00440     function customMessage($html) {
00441         if ($this->enabled === true) {
00442             $state = 'an enabled';
00443         } else if ($this->enabled === false) {
00444             $state = 'a disabled';
00445         } else {
00446             $state = 'a';
00447         }
00448         $output = 'Content [' . $html . '] does not contain ' . $state .
00449                 ' <select> with name ' . $this->name . ' and choices ' .
00450                 implode(', ', $this->choices);
00451         if ($this->selected) {
00452             $output .= ' with ' . $this->selected . ' selected).';
00453         }
00454 
00455         return $output;
00456     }
00457 }
00458 
00466 class DoesNotContainTagWithAttributes extends ContainsTagWithAttributes {
00467     function __construct($tag, $expectedvalues, $message = '%s') {
00468         parent::__construct($tag, $expectedvalues, array(), $message);
00469     }
00470     function test($html) {
00471         return !parent::test($html);
00472     }
00473     function customMessage($html) {
00474         $output = 'Content [' . $html . '] ';
00475 
00476         $output .= 'contains the tag [' . $this->tag . '] with attributes [';
00477         foreach ($this->expectedvalues as $var => $val) {
00478             $output .= "$var=\"$val\" ";
00479         }
00480         $output = rtrim($output);
00481         $output .= '].';
00482 
00483         return $output;
00484     }
00485 }
00486 
00493 class ContainsTagWithContents extends XMLStructureExpectation {
00494     protected $tag;
00495     protected $content;
00496 
00497     function __construct($tag, $content, $message = '%s') {
00498         parent::__construct($message);
00499         $this->tag = $tag;
00500         $this->content = $content;
00501     }
00502 
00503     function test($html) {
00504         $parser = $this->load_xml($html);
00505         $list = $parser->getElementsByTagName($this->tag);
00506 
00507         foreach ($list as $node) {
00508             if ($node->textContent == $this->content) {
00509                 return true;
00510             }
00511         }
00512 
00513         return false;
00514     }
00515 
00516     function testMessage($html) {
00517         return 'Content [' . $html . '] does not contain the tag [' .
00518                 $this->tag . '] with contents [' . $this->content . '].';
00519     }
00520 }
00521 
00528 class ContainsEmptyTag extends XMLStructureExpectation {
00529     protected $tag;
00530 
00531     function __construct($tag, $message = '%s') {
00532         parent::__construct($message);
00533         $this->tag = $tag;
00534     }
00535 
00536     function test($html) {
00537         $parser = $this->load_xml($html);
00538         $list = $parser->getElementsByTagName($this->tag);
00539 
00540         foreach ($list as $node) {
00541             if (!$node->hasAttributes() && !$node->hasChildNodes()) {
00542                 return true;
00543             }
00544         }
00545 
00546         return false;
00547     }
00548 
00549     function testMessage($html) {
00550         return 'Content ['.$html.'] does not contain the empty tag ['.$this->tag.'].';
00551     }
00552 }
00553 
00554 
00566 class test_recordset extends moodle_recordset {
00567     protected $records;
00568 
00574     public function __construct(array $table) {
00575         $columns = array_shift($table);
00576         $this->records = array();
00577         foreach ($table as $row) {
00578             if (count($row) != count($columns)) {
00579                 throw new coding_exception("Row contains the wrong number of fields.");
00580             }
00581             $rec = array();
00582             foreach ($columns as $i => $name) {
00583                 $rec[$name] = $row[$i];
00584             }
00585             $this->records[] = $rec;
00586         }
00587         reset($this->records);
00588     }
00589 
00590     public function __destruct() {
00591         $this->close();
00592     }
00593 
00594     public function current() {
00595         return (object) current($this->records);
00596     }
00597 
00598     public function key() {
00599         if (is_null(key($this->records))) {
00600             return false;
00601         }
00602         $current = current($this->records);
00603         return reset($current);
00604     }
00605 
00606     public function next() {
00607         next($this->records);
00608     }
00609 
00610     public function valid() {
00611         return !is_null(key($this->records));
00612     }
00613 
00614     public function close() {
00615         $this->records = null;
00616     }
00617 }
00618 
00619 
00636 class UnitTestCaseUsingDatabase extends UnitTestCase {
00637     private $realdb;
00638     protected $testdb;
00639     private $realuserid = null;
00640     private $tables = array();
00641 
00642     private $realcfg;
00643     protected $testcfg;
00644 
00645     public function __construct($label = false) {
00646         global $DB, $CFG;
00647 
00648         // Complain if we get this far and $CFG->unittestprefix is not set.
00649         if (empty($CFG->unittestprefix)) {
00650             throw new coding_exception('You cannot use UnitTestCaseUsingDatabase unless you set $CFG->unittestprefix.');
00651         }
00652 
00653         // Only do this after the above text.
00654         parent::UnitTestCase($label);
00655 
00656         // Create the test DB instance.
00657         $this->realdb = $DB;
00658         $this->testdb = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
00659         $this->testdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->unittestprefix);
00660 
00661         // Set up test config
00662         $this->testcfg = (object)array(
00663                 'testcfg' => true, // Marker that this is a test config
00664                 'libdir' => $CFG->libdir, // Must use real one so require_once works
00665                 'dirroot' => $CFG->dirroot, // Must use real one
00666                 'dataroot' => $CFG->dataroot, // Use real one for now (maybe this should change?)
00667                 'ostype' => $CFG->ostype, // Real one
00668                 'wwwroot' => 'http://www.example.org', // Use fixed url
00669                 'siteadmins' => '0', // No admins
00670                 'siteguest' => '0' // No guest
00671         );
00672         $this->realcfg = $CFG;
00673     }
00674 
00678     protected function switch_to_test_db() {
00679         global $DB;
00680         if ($DB === $this->testdb) {
00681             debugging('switch_to_test_db called when the test DB was already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00682         }
00683         $DB = $this->testdb;
00684     }
00685 
00689     protected function revert_to_real_db() {
00690         global $DB;
00691         if ($DB !== $this->testdb) {
00692             debugging('revert_to_real_db called when the test DB was not already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00693         }
00694         $DB = $this->realdb;
00695     }
00696 
00700     protected function switch_to_test_cfg() {
00701         global $CFG;
00702         if (isset($CFG->testcfg)) {
00703             debugging('switch_to_test_cfg called when the test CFG was already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00704         }
00705         $CFG = $this->testcfg;
00706     }
00707 
00711     protected function revert_to_real_cfg() {
00712         global $CFG;
00713         if (!isset($CFG->testcfg)) {
00714             debugging('revert_to_real_cfg called when the test CFG was not already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00715         }
00716         $CFG = $this->realcfg;
00717     }
00718 
00725     protected function switch_global_user_id($userid) {
00726         global $USER;
00727         if (!is_null($this->realuserid)) {
00728             debugging('switch_global_user_id called when $USER->id was already switched to a different value. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00729         } else {
00730             $this->realuserid = $USER->id;
00731         }
00732         $USER->id = $userid;
00733     }
00734 
00738     protected function revert_global_user_id() {
00739         global $USER;
00740         if (is_null($this->realuserid)) {
00741             debugging('revert_global_user_id called without switch_global_user_id having been called first. This suggest you are doing something wrong and dangerous. Please review your code immediately.', DEBUG_DEVELOPER);
00742         } else {
00743             $USER->id = $this->realuserid;
00744             $this->realuserid = null;
00745         }
00746     }
00747 
00769     protected function create_system_context_record() {
00770         global $DB;
00771 
00772         // If, for any reason, the record exists, do nothing
00773         if ($DB->record_exists('context', array('contextlevel'=>CONTEXT_SYSTEM))) {
00774             return;
00775         }
00776 
00777         $record = new stdClass();
00778         $record->contextlevel = CONTEXT_SYSTEM;
00779         $record->instanceid   = 0;
00780         $record->depth        = 1;
00781         $record->path         = null;
00782         if (defined('SYSCONTEXTID')) {
00783             $record->id = SYSCONTEXTID;
00784             $DB->import_record('context', $record);
00785             $DB->get_manager()->reset_sequence('context');
00786         } else {
00787             $record->id = $DB->insert_record('context', $record);
00788         }
00789         // fix path
00790         $record->path  = '/'.$record->id;
00791         $DB->set_field('context', 'path', $record->path, array('id' => $record->id));
00792     }
00793 
00798     private function automatic_clean_up() {
00799         global $DB, $CFG;
00800         $cleanmore = false;
00801 
00802         // Drop any test tables that were created.
00803         foreach ($this->tables as $tablename => $notused) {
00804             $this->drop_test_table($tablename);
00805         }
00806 
00807         // Switch back to the real DB if necessary.
00808         if ($DB !== $this->realdb) {
00809             $this->revert_to_real_db();
00810             $cleanmore = true;
00811         }
00812 
00813         // Switch back to the real CFG if necessary.
00814         if (isset($CFG->testcfg)) {
00815             $this->revert_to_real_cfg();
00816             $cleanmore = true;
00817         }
00818 
00819         // revert_global_user_id if necessary.
00820         if (!is_null($this->realuserid)) {
00821             $this->revert_global_user_id();
00822             $cleanmore = true;
00823         }
00824 
00825         if ($cleanmore) {
00826             accesslib_clear_all_caches_for_unit_testing();
00827             $course = 'reset';
00828             get_fast_modinfo($course);
00829         }
00830     }
00831 
00832     public function tearDown() {
00833         $this->automatic_clean_up();
00834         parent::tearDown();
00835     }
00836 
00837     public function __destruct() {
00838         // Should not be necessary thanks to tearDown, but no harm in belt and braces.
00839         $this->automatic_clean_up();
00840     }
00841 
00850     protected function create_test_table($tablename, $installxmlfile) {
00851         global $CFG;
00852         $dbman = $this->testdb->get_manager();
00853         if (isset($this->tables[$tablename])) {
00854             debugging('You are attempting to create test table ' . $tablename . ' again. It already exists. Please review your code immediately.', DEBUG_DEVELOPER);
00855             return;
00856         }
00857         if ($dbman->table_exists($tablename)) {
00858             debugging('This table ' . $tablename . ' already exists from a previous execution. If the error persists you will need to review your code to ensure it is being created only once.', DEBUG_DEVELOPER);
00859             $dbman->drop_table(new xmldb_table($tablename));
00860         }
00861         $dbman->install_one_table_from_xmldb_file($CFG->dirroot . '/' . $installxmlfile . '/db/install.xml', $tablename, true); // with structure cache enabled!
00862         $this->tables[$tablename] = 1;
00863     }
00864 
00872     protected function create_test_tables($tablenames, $installxmlfile) {
00873         foreach ($tablenames as $tablename) {
00874             $this->create_test_table($tablename, $installxmlfile);
00875         }
00876     }
00877 
00882     protected function drop_test_table($tablename) {
00883         if (!isset($this->tables[$tablename])) {
00884             debugging('You are attempting to drop test table ' . $tablename . ' but it does not exist. Please review your code immediately.', DEBUG_DEVELOPER);
00885             return;
00886         }
00887         $dbman = $this->testdb->get_manager();
00888         $table = new xmldb_table($tablename);
00889         $dbman->drop_table($table);
00890         unset($this->tables[$tablename]);
00891     }
00892 
00897     protected function drop_test_tables($tablenames) {
00898         foreach ($tablenames as $tablename) {
00899             $this->drop_test_table($tablename);
00900         }
00901     }
00902 
00918     protected function load_test_data($table, array $cols, array $data) {
00919         $results = array();
00920         foreach ($data as $rowid => $row) {
00921             $obj = new stdClass;
00922             foreach ($cols as $key => $colname) {
00923                 $obj->$colname = $row[$key];
00924             }
00925             $obj->id = $this->testdb->insert_record($table, $obj);
00926             $results[$rowid] = $obj;
00927         }
00928         return $results;
00929     }
00930 
00940     protected function delete_test_data($table, array $rows) {
00941         $ids = array();
00942         foreach ($rows as $row) {
00943             $ids[] = $row->id;
00944         }
00945         $this->testdb->delete_records_list($table, 'id', $ids);
00946     }
00947 }
00948 
00949 
00956 class FakeDBUnitTestCase extends UnitTestCase {
00957     public $tables = array();
00958     public $pkfile;
00959     public $cfg;
00960     public $DB;
00961 
00970     public function __construct($label = false) {
00971         global $DB, $CFG;
00972 
00973         if (empty($CFG->unittestprefix)) {
00974             return;
00975         }
00976 
00977         parent::UnitTestCase($label);
00978         // MDL-16483 Get PKs and save data to text file
00979 
00980         $this->pkfile = $CFG->dataroot.'/testtablespks.csv';
00981         $this->cfg = $CFG;
00982 
00983         UnitTestDB::instantiate();
00984 
00985         $tables = $DB->get_tables();
00986 
00987         // The file exists, so use it to truncate tables (tests aborted before test data could be removed)
00988         if (file_exists($this->pkfile)) {
00989             $this->truncate_test_tables($this->get_table_data($this->pkfile));
00990 
00991         } else { // Create the file
00992             $tabledata = '';
00993 
00994             foreach ($tables as $table) {
00995                 if ($table != 'sessions') {
00996                     if (!$max_id = $DB->get_field_sql("SELECT MAX(id) FROM {$CFG->unittestprefix}{$table}")) {
00997                         $max_id = 0;
00998                     }
00999                     $tabledata .= "$table, $max_id\n";
01000                 }
01001             }
01002             if (!file_put_contents($this->pkfile, $tabledata)) {
01003                 $a = new stdClass();
01004                 $a->filename = $this->pkfile;
01005                 throw new moodle_exception('testtablescsvfileunwritable', 'tool_unittest', '', $a);
01006             }
01007         }
01008     }
01009 
01014     private function truncate_test_tables($tabledata) {
01015         global $CFG, $DB;
01016 
01017         if (empty($CFG->unittestprefix)) {
01018             return;
01019         }
01020 
01021         $tables = $DB->get_tables();
01022 
01023         foreach ($tables as $table) {
01024             if ($table != 'sessions' && isset($tabledata[$table])) {
01025                 // $DB->delete_records_select($table, "id > ?", array($tabledata[$table]));
01026             }
01027         }
01028     }
01029 
01039     public function get_table_data($filename) {
01040         global $CFG;
01041 
01042         if (empty($CFG->unittestprefix)) {
01043             return;
01044         }
01045 
01046         if (file_exists($this->pkfile)) {
01047             $handle = fopen($this->pkfile, 'r');
01048             $tabledata = array();
01049 
01050             while (($data = fgetcsv($handle, 1000, ",")) !== false) {
01051                 $tabledata[$data[0]] = $data[1];
01052             }
01053             return $tabledata;
01054         } else {
01055             $a = new stdClass();
01056             $a->filename = $this->pkfile;
01057             throw new moodle_exception('testtablescsvfilemissing', 'tool_unittest', '', $a);
01058             return false;
01059         }
01060     }
01061 
01067     public function setUp() {
01068         global $DB, $CFG;
01069 
01070         if (empty($CFG->unittestprefix)) {
01071             return;
01072         }
01073 
01074         parent::setUp();
01075         $this->DB =& $DB;
01076         ob_start();
01077     }
01078 
01082     public function tearDown() {
01083         global $DB, $CFG;
01084 
01085         if (empty($CFG->unittestprefix)) {
01086             return;
01087         }
01088 
01089         if (empty($DB)) {
01090             $DB = $this->DB;
01091         }
01092         $DB->cleanup();
01093         parent::tearDown();
01094 
01095         // Output buffering
01096         if (ob_get_length() > 0) {
01097             ob_end_flush();
01098         }
01099     }
01100 
01105     public function __destruct() {
01106         global $CFG, $DB;
01107 
01108         if (empty($CFG->unittestprefix)) {
01109             return;
01110         }
01111 
01112         $CFG = $this->cfg;
01113         $this->tearDown();
01114         UnitTestDB::restore();
01115         fulldelete($this->pkfile);
01116     }
01117 
01133     public function load_test_data($table, array $cols, array $data) {
01134         global $CFG, $DB;
01135 
01136         if (empty($CFG->unittestprefix)) {
01137             return;
01138         }
01139 
01140         $results = array();
01141         foreach ($data as $rowid => $row) {
01142             $obj = new stdClass;
01143             foreach ($cols as $key => $colname) {
01144                 $obj->$colname = $row[$key];
01145             }
01146             $obj->id = $DB->insert_record($table, $obj);
01147             $results[$rowid] = $obj;
01148         }
01149         return $results;
01150     }
01151 
01161     public function delete_test_data($table, array $rows) {
01162         global $CFG, $DB;
01163 
01164         if (empty($CFG->unittestprefix)) {
01165             return;
01166         }
01167 
01168         $ids = array();
01169         foreach ($rows as $row) {
01170             $ids[] = $row->id;
01171         }
01172         $DB->delete_records_list($table, 'id', $ids);
01173     }
01174 }
01175 
01188 class UnitTestDB {
01189     public static $DB;
01190     private static $real_db;
01191 
01192     public $table_data = array();
01193 
01198     public static function instantiate() {
01199         global $CFG, $DB;
01200         UnitTestDB::$real_db = clone($DB);
01201         if (empty($CFG->unittestprefix)) {
01202             print_error("prefixnotset", 'tool_unittest');
01203         }
01204 
01205         if (empty(UnitTestDB::$DB)) {
01206             UnitTestDB::$DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
01207             UnitTestDB::$DB->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->unittestprefix);
01208         }
01209 
01210         $manager = UnitTestDB::$DB->get_manager();
01211 
01212         if (!$manager->table_exists('user')) {
01213             print_error('tablesnotsetup', 'tool_unittest');
01214         }
01215 
01216         $DB = new UnitTestDB();
01217     }
01218 
01219     public function __call($method, $args) {
01220         // Set args to null if they don't exist (up to 10 args should do)
01221         if (!method_exists($this, $method)) {
01222             return call_user_func_array(array(UnitTestDB::$DB, $method), $args);
01223         } else {
01224             call_user_func_array(array($this, $method), $args);
01225         }
01226     }
01227 
01228     public function __get($variable) {
01229         return UnitTestDB::$DB->$variable;
01230     }
01231 
01232     public function __set($variable, $value) {
01233         UnitTestDB::$DB->$variable = $value;
01234     }
01235 
01236     public function __isset($variable) {
01237         return isset(UnitTestDB::$DB->$variable);
01238     }
01239 
01240     public function __unset($variable) {
01241         unset(UnitTestDB::$DB->$variable);
01242     }
01243 
01247     public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
01248         global $DB;
01249         $id = UnitTestDB::$DB->insert_record($table, $dataobject, $returnid, $bulk);
01250         $this->table_data[$table][] = $id;
01251         return $id;
01252     }
01253 
01260     public function update_record($table, $dataobject, $bulk=false) {
01261         global $DB;
01262         if ((empty($this->table_data[$table]) || !in_array($dataobject->id, $this->table_data[$table])) && !($table == 'course_categories' && $dataobject->id == 1)) {
01263             // return UnitTestDB::$DB->update_record($table, $dataobject, $bulk);
01264             $a = new stdClass();
01265             $a->id = $dataobject->id;
01266             $a->table = $table;
01267             throw new moodle_exception('updatingnoninsertedrecord', 'tool_unittest', '', $a);
01268         } else {
01269             return UnitTestDB::$DB->update_record($table, $dataobject, $bulk);
01270         }
01271     }
01272 
01279     public function delete_records($table, array $conditions=array()) {
01280         global $DB;
01281         $tables_to_ignore = array('context_temp');
01282 
01283         $a = new stdClass();
01284         $a->table = $table;
01285 
01286         // Get ids matching conditions
01287         if (!$ids_to_delete = $DB->get_field($table, 'id', $conditions)) {
01288             return UnitTestDB::$DB->delete_records($table, $conditions);
01289         }
01290 
01291         $proceed_with_delete = true;
01292 
01293         if (!is_array($ids_to_delete)) {
01294             $ids_to_delete = array($ids_to_delete);
01295         }
01296 
01297         foreach ($ids_to_delete as $id) {
01298             if (!in_array($table, $tables_to_ignore) && (empty($this->table_data[$table]) || !in_array($id, $this->table_data[$table]))) {
01299                 $proceed_with_delete = false;
01300                 $a->id = $id;
01301                 break;
01302             }
01303         }
01304 
01305         if ($proceed_with_delete) {
01306             return UnitTestDB::$DB->delete_records($table, $conditions);
01307         } else {
01308             throw new moodle_exception('deletingnoninsertedrecord', 'tool_unittest', '', $a);
01309         }
01310     }
01311 
01318     public function delete_records_select($table, $select, array $params=null) {
01319         global $DB;
01320         $a = new stdClass();
01321         $a->table = $table;
01322 
01323         // Get ids matching conditions
01324         if (!$ids_to_delete = $DB->get_field_select($table, 'id', $select, $params)) {
01325             return UnitTestDB::$DB->delete_records_select($table, $select, $params);
01326         }
01327 
01328         $proceed_with_delete = true;
01329 
01330         foreach ($ids_to_delete as $id) {
01331             if (!in_array($id, $this->table_data[$table])) {
01332                 $proceed_with_delete = false;
01333                 $a->id = $id;
01334                 break;
01335             }
01336         }
01337 
01338         if ($proceed_with_delete) {
01339             return UnitTestDB::$DB->delete_records_select($table, $select, $params);
01340         } else {
01341             throw new moodle_exception('deletingnoninsertedrecord', 'tool_unittest', '', $a);
01342         }
01343     }
01344 
01348     public function cleanup() {
01349         global $DB;
01350         foreach ($this->table_data as $table => $ids) {
01351             foreach ($ids as $id) {
01352                 $DB->delete_records($table, array('id' => $id));
01353             }
01354         }
01355     }
01356 
01360     public static function restore() {
01361         global $DB;
01362         $DB = UnitTestDB::$real_db;
01363     }
01364 
01365     public function get_field($table, $return, array $conditions) {
01366         if (!is_array($conditions)) {
01367             throw new coding_exception('$conditions is not an array.');
01368         }
01369         return UnitTestDB::$DB->get_field($table, $return, $conditions);
01370     }
01371 }
 All Data Structures Namespaces Files Functions Variables Enumerations