|
Moodle
2.2.1
http://www.collinsharper.com
|
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 }