Commit the print_js_config, so people can see it as the read http://moodle.org/mod...
[moodle.git] / lib / simpletestlib.php
CommitLineData
56768525 1<?php // $Id$
3ef8c936 2/**
3 * Utility functions to make unit testing easier.
b9c639d6 4 *
3ef8c936 5 * These functions, particularly the the database ones, are quick and
b9c639d6 6 * dirty methods for getting things done in test cases. None of these
3ef8c936 7 * methods should be used outside test code.
8 *
9 * @copyright &copy; 2006 The Open University
10 * @author T.J.Hunt@open.ac.uk
11 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12 * @version $Id$
13 * @package SimpleTestEx
14 */
15
16require_once(dirname(__FILE__) . '/../config.php');
17require_once($CFG->libdir . '/simpletestlib/simpletest.php');
18require_once($CFG->libdir . '/simpletestlib/unit_tester.php');
19require_once($CFG->libdir . '/simpletestlib/expectation.php');
4309bd17 20require_once($CFG->libdir . '/simpletestlib/reporter.php');
a205dcdc 21require_once($CFG->libdir . '/simpletestlib/web_tester.php');
76144765 22require_once($CFG->libdir . '/simpletestlib/mock_objects.php');
3ef8c936 23
24/**
25 * Recursively visit all the files in the source tree. Calls the callback
b9c639d6 26 * function with the pathname of each file found.
27 *
28 * @param $path the folder to start searching from.
3ef8c936 29 * @param $callback the function to call with the name of each file found.
30 * @param $fileregexp a regexp used to filter the search (optional).
b9c639d6 31 * @param $exclude If true, pathnames that match the regexp will be ingored. If false,
3ef8c936 32 * only files that match the regexp will be included. (default false).
33 * @param array $ignorefolders will not go into any of these folders (optional).
b9c639d6 34 */
3ef8c936 35function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
36 $files = scandir($path);
37
38 foreach ($files as $file) {
39 $filepath = $path .'/'. $file;
40 if ($file == '.' || $file == '..') {
41 continue;
42 } else if (is_dir($filepath)) {
43 if (!in_array($filepath, $ignorefolders)) {
44 recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
45 }
46 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
47 call_user_func($callback, $filepath);
48 }
49 }
50}
51
52/**
53 * An expectation for comparing strings ignoring whitespace.
54 */
55class IgnoreWhitespaceExpectation extends SimpleExpectation {
56 var $expect;
57
58 function IgnoreWhitespaceExpectation($content, $message = '%s') {
59 $this->SimpleExpectation($message);
60 $this->expect=$this->normalise($content);
61 }
62
63 function test($ip) {
64 return $this->normalise($ip)==$this->expect;
65 }
66
67 function normalise($text) {
68 return preg_replace('/\s+/m',' ',trim($text));
69 }
70
71 function testMessage($ip) {
72 return "Input string [$ip] doesn't match the required value.";
73 }
74}
75
76/**
77 * An Expectation that two arrays contain the same list of values.
78 */
79class ArraysHaveSameValuesExpectation extends SimpleExpectation {
80 var $expect;
81
82 function ArraysHaveSameValuesExpectation($expected, $message = '%s') {
83 $this->SimpleExpectation($message);
84 if (!is_array($expected)) {
85 trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' .
86 'with an expected value that is not an array.');
87 }
88 $this->expect = $this->normalise($expected);
89 }
90
91 function test($actual) {
92 return $this->normalise($actual) == $this->expect;
93 }
94
95 function normalise($array) {
96 sort($array);
97 return $array;
98 }
99
100 function testMessage($actual) {
101 return 'Array [' . implode(', ', $actual) .
102 '] does not contain the expected list of values [' . implode(', ', $this->expect) . '].';
103 }
104}
105
106/**
107 * An Expectation that compares to objects, and ensures that for every field in the
108 * expected object, there is a key of the same name in the actual object, with
109 * the same value. (The actual object may have other fields to, but we ignore them.)
110 */
111class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
112 var $expect;
113
114 function CheckSpecifiedFieldsExpectation($expected, $message = '%s') {
115 $this->SimpleExpectation($message);
116 if (!is_object($expected)) {
117 trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' .
118 'with an expected value that is not an object.');
119 }
120 $this->expect = $expected;
121 }
122
123 function test($actual) {
124 foreach ($this->expect as $key => $value) {
125 if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
126 // OK
127 } else if (is_null($value) && is_null($actual->$key)) {
128 // OK
129 } else {
130 return false;
131 }
132 }
133 return true;
134 }
135
136 function testMessage($actual) {
137 $mismatches = array();
138 foreach ($this->expect as $key => $value) {
139 if (isset($value) && isset($actual->$key) && $actual->$key == $value) {
140 // OK
141 } else if (is_null($value) && is_null($actual->$key)) {
142 // OK
143 } else {
144 $mismatches[] = $key;
145 }
146 }
147 return 'Actual object does not have all the same fields with the same values as the expected object (' .
148 implode(', ', $mismatches) . ').';
149 }
150}
151
b9c639d6 152class MoodleUnitTestCase extends UnitTestCase {
356e0010 153 public $real_db;
154 public $tables = array();
90997d6d 155 public $pkfile;
156 public $cfg;
356e0010 157
90997d6d 158 /**
159 * In the constructor, record the max(id) of each test table into a csv file.
160 * If this file already exists, it means that a previous run of unit tests
161 * did not complete, and has left data undeleted in the DB. This data is then
162 * deleted and the file is retained. Otherwise it is created.
163 * @throws moodle_exception if CSV file cannot be created
164 */
b9c639d6 165 public function __construct($label = false) {
166 parent::UnitTestCase($label);
356e0010 167
90997d6d 168 // MDL-16483 Get PKs and save data to text file
169 global $DB, $CFG;
170 $this->pkfile = $CFG->dataroot.'/testtablespks.csv';
171 $this->cfg = $CFG;
cb352449 172 $this->setup();
90997d6d 173
174 $tables = $DB->get_tables();
175
176 // The file exists, so use it to truncate tables (tests aborted before test data could be removed)
177 if (file_exists($this->pkfile)) {
178 $this->truncate_test_tables($this->get_table_data($this->pkfile));
179
180 } else { // Create the file
181 $tabledata = '';
182
183 foreach ($tables as $table) {
184 if ($table != 'sessions2') {
185 if (!$max_id = $DB->get_field_sql("SELECT MAX(id) FROM {$CFG->unittestprefix}{$table}")) {
186 $max_id = 0;
187 }
188 $tabledata .= "$table, $max_id\n";
189 }
190 }
191 if (!file_put_contents($this->pkfile, $tabledata)) {
192 throw new moodle_exception('testtablescsvfileunwritable', 'error');
193 }
194 }
195 }
196
197 /**
198 * Given an array of tables and their max id, truncates all test table records whose id is higher than the ones in the $tabledata array.
199 * @param array $tabledata
200 */
201 private function truncate_test_tables($tabledata) {
202 global $CFG, $DB;
203
204 $tables = $DB->get_tables();
205
206 foreach ($tables as $table) {
207 if ($table != 'sessions2' && isset($tabledata[$table])) {
208 $DB->delete_records_select($table, "id > ?", array($tabledata[$table]));
209 }
210 }
356e0010 211 }
212
90997d6d 213 /**
214 * Given a filename, opens it and parses the csv contained therein. It expects two fields per line:
215 * 1. Table name
216 * 2. Max id
217 * @param string $filename
218 * @throws moodle_exception if file doesn't exist
219 */
220 public function get_table_data($filename) {
221 if (file_exists($this->pkfile)) {
222 $handle = fopen($this->pkfile, 'r');
223 $tabledata = array();
224
225 while (($data = fgetcsv($handle, 1000, ",")) !== false) {
226 $tabledata[$data[0]] = $data[1];
227 }
228 return $tabledata;
229 } else {
230 throw new moodle_exception('testtablescsvfilemissing', 'error');
231 return false;
232 }
233 }
234
235 /**
236 * Method called before each test method. Replaces the real $DB with the one configured for unit tests (different prefix, $CFG->unittestprefix).
237 * Also detects if this config setting is properly set, and if the user table exists.
238 * TODO Improve detection of incorrectly built DB test tables (e.g. detect version discrepancy and offer to upgrade/rebuild)
239 */
356e0010 240 public function setUp() {
b9c639d6 241 global $CFG, $DB;
356e0010 242 parent::setUp();
243
244 $this->real_db = $DB;
b9c639d6 245
90997d6d 246 if (empty($CFG->unittestprefix)) {
b9c639d6 247 print_error("prefixnotset", 'simpletest');
248 }
249
356e0010 250 $DB = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary);
90997d6d 251 $DB->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->dbpersist, $CFG->unittestprefix);
356e0010 252 $manager = $DB->get_manager();
253
254 if (!$manager->table_exists('user')) {
b9c639d6 255 print_error('tablesnotsetup', 'simpletest');
256 }
b9c639d6 257 }
258
90997d6d 259 /**
260 * Method called after each test method. Doesn't do anything extraordinary except restore the global $DB to the real one.
261 */
b9c639d6 262 public function tearDown() {
356e0010 263 global $DB;
b9c639d6 264 parent::tearDown();
356e0010 265
356e0010 266 $DB = $this->real_db;
b9c639d6 267 }
268
269 /**
270 * This will execute once all the tests have been run. It should delete the text file holding info about database contents prior to the tests
90997d6d 271 * It should also detect if data is missing from the original tables.
b9c639d6 272 */
273 public function __destruct() {
90997d6d 274 global $CFG;
275 $CFG = $this->cfg;
276 $this->truncate_test_tables($this->get_table_data($this->pkfile));
277 fulldelete($this->pkfile);
cb352449 278 $this->tearDown();
b9c639d6 279 }
280}
281
bb48a537 282?>