Merge branch 'MDL-35388' of git://github.com/netspotau/moodle-mod_assign
[moodle.git] / lib / phpunit / classes / advanced_testcase.php
CommitLineData
7e7cfe7a
PS
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Advanced test case.
19 *
20 * @package core
21 * @category phpunit
22 * @copyright 2012 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26
27/**
28 * Advanced PHPUnit test case customised for Moodle.
29 *
30 * @package core
31 * @category phpunit
32 * @copyright 2012 Petr Skoda {@link http://skodak.org}
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34 */
35abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
36 /** @var bool automatically reset everything? null means log changes */
37 private $resetAfterTest;
38
39 /** @var moodle_transaction */
40 private $testdbtransaction;
41
42 /**
43 * Constructs a test case with the given name.
44 *
45 * Note: use setUp() or setUpBeforeClass() in your test cases.
46 *
47 * @param string $name
48 * @param array $data
49 * @param string $dataName
50 */
51 final public function __construct($name = null, array $data = array(), $dataName = '') {
52 parent::__construct($name, $data, $dataName);
53
54 $this->setBackupGlobals(false);
55 $this->setBackupStaticAttributes(false);
56 $this->setRunTestInSeparateProcess(false);
57 }
58
59 /**
60 * Runs the bare test sequence.
61 * @return void
62 */
63 final public function runBare() {
64 global $DB;
65
66 if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
67 // this happens when previous test does not reset, we can not use transactions
68 $this->testdbtransaction = null;
69
70 } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') {
71 // database must allow rollback of DDL, so no mysql here
72 $this->testdbtransaction = $DB->start_delegated_transaction();
73 }
74
75 try {
76 parent::runBare();
77 // set DB reference in case somebody mocked it in test
78 $DB = phpunit_util::get_global_backup('DB');
79 } catch (Exception $e) {
80 // cleanup after failed expectation
81 phpunit_util::reset_all_data();
82 throw $e;
83 }
84
85 if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
86 $this->testdbtransaction = null;
87 }
88
89 if ($this->resetAfterTest === true) {
90 if ($this->testdbtransaction) {
91 $DB->force_transaction_rollback();
92 phpunit_util::reset_all_database_sequences();
93 phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
94 }
95 phpunit_util::reset_all_data();
96
97 } else if ($this->resetAfterTest === false) {
98 if ($this->testdbtransaction) {
99 $this->testdbtransaction->allow_commit();
100 }
101 // keep all data untouched for other tests
102
103 } else {
104 // reset but log what changed
105 if ($this->testdbtransaction) {
106 try {
107 $this->testdbtransaction->allow_commit();
108 } catch (dml_transaction_exception $e) {
109 phpunit_util::reset_all_data();
110 throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
111 }
112 }
113 phpunit_util::reset_all_data(true);
114 }
115
116 // make sure test did not forget to close transaction
117 if ($DB->is_transaction_started()) {
118 phpunit_util::reset_all_data();
119 if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
120 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
121 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
122 throw new coding_exception('Test '.$this->getName().' did not close database transaction');
123 }
124 }
125 }
126
127 /**
128 * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.)
129 *
130 * @param string $xmlFile
131 * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet
132 */
133 protected function createFlatXMLDataSet($xmlFile) {
134 return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile);
135 }
136
137 /**
138 * Creates a new XMLDataSet with the given $xmlFile. (absolute path.)
139 *
140 * @param string $xmlFile
141 * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet
142 */
143 protected function createXMLDataSet($xmlFile) {
144 return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile);
145 }
146
147 /**
148 * Creates a new CsvDataSet from the given array of csv files. (absolute paths.)
149 *
150 * @param array $files array tablename=>cvsfile
151 * @param string $delimiter
152 * @param string $enclosure
153 * @param string $escape
154 * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet
155 */
156 protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') {
157 $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape);
158 foreach($files as $table=>$file) {
159 $dataSet->addTable($table, $file);
160 }
161 return $dataSet;
162 }
163
164 /**
165 * Creates new ArrayDataSet from given array
166 *
167 * @param array $data array of tables, first row in each table is columns
168 * @return phpunit_ArrayDataSet
169 */
170 protected function createArrayDataSet(array $data) {
171 return new phpunit_ArrayDataSet($data);
172 }
173
174 /**
175 * Load date into moodle database tables from standard PHPUnit data set.
176 *
177 * Note: it is usually better to use data generators
178 *
179 * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset
180 * @return void
181 */
182 protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) {
183 global $DB;
184
185 $structure = phpunit_util::get_tablestructure();
186
187 foreach($dataset->getTableNames() as $tablename) {
188 $table = $dataset->getTable($tablename);
189 $metadata = $dataset->getTableMetaData($tablename);
190 $columns = $metadata->getColumns();
191
192 $doimport = false;
193 if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) {
194 $doimport = in_array('id', $columns);
195 }
196
197 for($r=0; $r<$table->getRowCount(); $r++) {
198 $record = $table->getRow($r);
199 if ($doimport) {
200 $DB->import_record($tablename, $record);
201 } else {
202 $DB->insert_record($tablename, $record);
203 }
204 }
205 if ($doimport) {
206 $DB->get_manager()->reset_sequence(new xmldb_table($tablename));
207 }
208 }
209 }
210
211 /**
212 * Call this method from test if you want to make sure that
213 * the resetting of database is done the slow way without transaction
214 * rollback.
215 *
216 * This is useful especially when testing stuff that is not compatible with transactions.
217 *
218 * @return void
219 */
220 public function preventResetByRollback() {
221 if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
222 $this->testdbtransaction->allow_commit();
223 $this->testdbtransaction = null;
224 }
225 }
226
227 /**
228 * Reset everything after current test.
229 * @param bool $reset true means reset state back, false means keep all data for the next test,
230 * null means reset state and show warnings if anything changed
231 * @return void
232 */
233 public function resetAfterTest($reset = true) {
234 $this->resetAfterTest = $reset;
235 }
236
237 /**
238 * Cleanup after all tests are executed.
239 *
240 * Note: do not forget to call this if overridden...
241 *
242 * @static
243 * @return void
244 */
245 public static function tearDownAfterClass() {
246 phpunit_util::reset_all_data();
247 }
248
249 /**
250 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
251 * @static
252 * @return void
253 */
254 public static function resetAllData() {
255 phpunit_util::reset_all_data();
256 }
257
258 /**
259 * Set current $USER, reset access cache.
260 * @static
d59ef9c5 261 * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid
7e7cfe7a
PS
262 * @return void
263 */
264 public static function setUser($user = null) {
265 global $CFG, $DB;
266
267 if (is_object($user)) {
268 $user = clone($user);
269 } else if (!$user) {
270 $user = new stdClass();
271 $user->id = 0;
272 $user->mnethostid = $CFG->mnet_localhost_id;
273 } else {
274 $user = $DB->get_record('user', array('id'=>$user));
275 }
276 unset($user->description);
277 unset($user->access);
d59ef9c5 278 unset($user->preference);
7e7cfe7a
PS
279
280 session_set_user($user);
281 }
282
d59ef9c5
PS
283 /**
284 * Set current $USER to admin account, reset access cache.
285 * @static
286 * @return void
287 */
288 public static function setAdminUser() {
289 self::setUser(2);
290 }
291
292 /**
293 * Set current $USER to guest account, reset access cache.
294 * @static
295 * @return void
296 */
297 public static function setGuestUser() {
298 self::setUser(1);
299 }
300
7e7cfe7a
PS
301 /**
302 * Get data generator
303 * @static
304 * @return phpunit_data_generator
305 */
306 public static function getDataGenerator() {
307 return phpunit_util::get_data_generator();
308 }
309
310 /**
311 * Recursively visit all the files in the source tree. Calls the callback
312 * function with the pathname of each file found.
313 *
314 * @param string $path the folder to start searching from.
315 * @param string $callback the method of this class to call with the name of each file found.
316 * @param string $fileregexp a regexp used to filter the search (optional).
317 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
318 * only files that match the regexp will be included. (default false).
319 * @param array $ignorefolders will not go into any of these folders (optional).
320 * @return void
321 */
322 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
323 $files = scandir($path);
324
325 foreach ($files as $file) {
326 $filepath = $path .'/'. $file;
327 if (strpos($file, '.') === 0) {
328 /// Don't check hidden files.
329 continue;
330 } else if (is_dir($filepath)) {
331 if (!in_array($filepath, $ignorefolders)) {
332 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
333 }
334 } else if ($exclude xor preg_match($fileregexp, $filepath)) {
335 $this->$callback($filepath);
336 }
337 }
338 }
339}