1 |
efrain |
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 |
* Testing util classes
|
|
|
19 |
*
|
|
|
20 |
* @package core
|
|
|
21 |
* @category test
|
|
|
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 |
abstract class testing_util {
|
|
|
26 |
/**
|
|
|
27 |
* @var string dataroot (likely to be $CFG->dataroot).
|
|
|
28 |
*/
|
|
|
29 |
private static $dataroot = null;
|
|
|
30 |
|
|
|
31 |
/**
|
|
|
32 |
* @var testing_data_generator
|
|
|
33 |
*/
|
|
|
34 |
protected static $generator = null;
|
|
|
35 |
|
|
|
36 |
/**
|
|
|
37 |
* @var string current version hash from php files
|
|
|
38 |
*/
|
|
|
39 |
protected static $versionhash = null;
|
|
|
40 |
|
|
|
41 |
/**
|
|
|
42 |
* @var array original content of all database tables
|
|
|
43 |
*/
|
|
|
44 |
protected static $tabledata = null;
|
|
|
45 |
|
|
|
46 |
/**
|
|
|
47 |
* @var array original structure of all database tables
|
|
|
48 |
*/
|
|
|
49 |
protected static $tablestructure = null;
|
|
|
50 |
|
|
|
51 |
/**
|
|
|
52 |
* @var array keep list of sequenceid used in a table.
|
|
|
53 |
*/
|
|
|
54 |
private static $tablesequences = [];
|
|
|
55 |
|
|
|
56 |
/**
|
|
|
57 |
* @var array list of updated tables.
|
|
|
58 |
*/
|
|
|
59 |
public static $tableupdated = [];
|
|
|
60 |
|
|
|
61 |
/**
|
|
|
62 |
* @var array original structure of all database tables
|
|
|
63 |
*/
|
|
|
64 |
protected static $sequencenames = null;
|
|
|
65 |
|
|
|
66 |
/**
|
|
|
67 |
* @var string name of the json file where we store the list of dataroot files to not reset during reset_dataroot.
|
|
|
68 |
*/
|
|
|
69 |
private static $originaldatafilesjson = 'originaldatafiles.json';
|
|
|
70 |
|
|
|
71 |
/**
|
|
|
72 |
* @var boolean set to true once $originaldatafilesjson file is created.
|
|
|
73 |
*/
|
|
|
74 |
private static $originaldatafilesjsonadded = false;
|
|
|
75 |
|
|
|
76 |
/**
|
|
|
77 |
* @var int next sequence value for a single test cycle.
|
|
|
78 |
*/
|
|
|
79 |
protected static $sequencenextstartingid = null;
|
|
|
80 |
|
|
|
81 |
/**
|
|
|
82 |
* Return the name of the JSON file containing the init filenames.
|
|
|
83 |
*
|
|
|
84 |
* @static
|
|
|
85 |
* @return string
|
|
|
86 |
*/
|
|
|
87 |
public static function get_originaldatafilesjson() {
|
|
|
88 |
return self::$originaldatafilesjson;
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
/**
|
|
|
92 |
* Return the dataroot. It's useful when mocking the dataroot when unit testing this class itself.
|
|
|
93 |
*
|
|
|
94 |
* @static
|
|
|
95 |
* @return string the dataroot.
|
|
|
96 |
*/
|
|
|
97 |
public static function get_dataroot() {
|
|
|
98 |
global $CFG;
|
|
|
99 |
|
|
|
100 |
// By default it's the test framework dataroot.
|
|
|
101 |
if (empty(self::$dataroot)) {
|
|
|
102 |
self::$dataroot = $CFG->dataroot;
|
|
|
103 |
}
|
|
|
104 |
|
|
|
105 |
return self::$dataroot;
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
/**
|
|
|
109 |
* Set the dataroot. It's useful when mocking the dataroot when unit testing this class itself.
|
|
|
110 |
*
|
|
|
111 |
* @param string $dataroot the dataroot of the test framework.
|
|
|
112 |
* @static
|
|
|
113 |
*/
|
|
|
114 |
public static function set_dataroot($dataroot) {
|
|
|
115 |
self::$dataroot = $dataroot;
|
|
|
116 |
}
|
|
|
117 |
|
|
|
118 |
/**
|
|
|
119 |
* Returns the testing framework name
|
|
|
120 |
* @static
|
|
|
121 |
* @return string
|
|
|
122 |
*/
|
|
|
123 |
final protected static function get_framework() {
|
|
|
124 |
$classname = get_called_class();
|
|
|
125 |
return substr($classname, 0, strpos($classname, '_'));
|
|
|
126 |
}
|
|
|
127 |
|
|
|
128 |
/**
|
|
|
129 |
* Get data generator
|
|
|
130 |
* @static
|
|
|
131 |
* @return testing_data_generator
|
|
|
132 |
*/
|
|
|
133 |
public static function get_data_generator() {
|
|
|
134 |
if (is_null(self::$generator)) {
|
|
|
135 |
require_once(__DIR__ . '/../generator/lib.php');
|
|
|
136 |
self::$generator = new testing_data_generator();
|
|
|
137 |
}
|
|
|
138 |
return self::$generator;
|
|
|
139 |
}
|
|
|
140 |
|
|
|
141 |
/**
|
|
|
142 |
* Does this site (db and dataroot) appear to be used for production?
|
|
|
143 |
* We try very hard to prevent accidental damage done to production servers!!
|
|
|
144 |
*
|
|
|
145 |
* @static
|
|
|
146 |
* @return bool
|
|
|
147 |
*/
|
|
|
148 |
public static function is_test_site() {
|
|
|
149 |
global $DB, $CFG;
|
|
|
150 |
|
|
|
151 |
$framework = self::get_framework();
|
|
|
152 |
|
|
|
153 |
if (!file_exists(self::get_dataroot() . '/' . $framework . 'testdir.txt')) {
|
|
|
154 |
// This is already tested in bootstrap script,
|
|
|
155 |
// But anyway presence of this file means the dataroot is for testing.
|
|
|
156 |
return false;
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
$tables = $DB->get_tables(false);
|
|
|
160 |
if ($tables) {
|
|
|
161 |
if (!$DB->get_manager()->table_exists('config')) {
|
|
|
162 |
return false;
|
|
|
163 |
}
|
|
|
164 |
if (!get_config('core', $framework . 'test')) {
|
|
|
165 |
return false;
|
|
|
166 |
}
|
|
|
167 |
}
|
|
|
168 |
|
|
|
169 |
return true;
|
|
|
170 |
}
|
|
|
171 |
|
|
|
172 |
/**
|
|
|
173 |
* Returns whether test database and dataroot were created using the current version codebase
|
|
|
174 |
*
|
|
|
175 |
* @return bool
|
|
|
176 |
*/
|
|
|
177 |
public static function is_test_data_updated() {
|
|
|
178 |
global $DB;
|
|
|
179 |
|
|
|
180 |
$framework = self::get_framework();
|
|
|
181 |
|
|
|
182 |
$datarootpath = self::get_dataroot() . '/' . $framework;
|
|
|
183 |
if (!file_exists($datarootpath . '/tabledata.ser') || !file_exists($datarootpath . '/tablestructure.ser')) {
|
|
|
184 |
return false;
|
|
|
185 |
}
|
|
|
186 |
|
|
|
187 |
if (!file_exists($datarootpath . '/versionshash.txt')) {
|
|
|
188 |
return false;
|
|
|
189 |
}
|
|
|
190 |
|
|
|
191 |
$hash = core_component::get_all_versions_hash();
|
|
|
192 |
$oldhash = file_get_contents($datarootpath . '/versionshash.txt');
|
|
|
193 |
|
|
|
194 |
if ($hash !== $oldhash) {
|
|
|
195 |
return false;
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
// A direct database request must be used to avoid any possible caching of an older value.
|
|
|
199 |
$dbhash = $DB->get_field('config', 'value', ['name' => $framework . 'test']);
|
|
|
200 |
if ($hash !== $dbhash) {
|
|
|
201 |
return false;
|
|
|
202 |
}
|
|
|
203 |
|
|
|
204 |
return true;
|
|
|
205 |
}
|
|
|
206 |
|
|
|
207 |
/**
|
|
|
208 |
* Stores the status of the database
|
|
|
209 |
*
|
|
|
210 |
* Serializes the contents and the structure and
|
|
|
211 |
* stores it in the test framework space in dataroot
|
|
|
212 |
*/
|
|
|
213 |
protected static function store_database_state() {
|
|
|
214 |
global $DB, $CFG;
|
|
|
215 |
|
|
|
216 |
$framework = self::get_framework();
|
|
|
217 |
|
|
|
218 |
// Store data for all tables.
|
|
|
219 |
$data = [];
|
|
|
220 |
$structure = [];
|
|
|
221 |
$tables = $DB->get_tables();
|
|
|
222 |
foreach ($tables as $table) {
|
|
|
223 |
$columns = $DB->get_columns($table);
|
|
|
224 |
$structure[$table] = $columns;
|
|
|
225 |
if (isset($columns['id']) && $columns['id']->auto_increment) {
|
|
|
226 |
$data[$table] = $DB->get_records($table, [], 'id ASC');
|
|
|
227 |
} else {
|
|
|
228 |
// There should not be many of these.
|
|
|
229 |
$data[$table] = $DB->get_records($table, []);
|
|
|
230 |
}
|
|
|
231 |
}
|
|
|
232 |
$data = serialize($data);
|
|
|
233 |
$datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
|
|
|
234 |
file_put_contents($datafile, $data);
|
|
|
235 |
testing_fix_file_permissions($datafile);
|
|
|
236 |
|
|
|
237 |
$structure = serialize($structure);
|
|
|
238 |
$structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
|
|
|
239 |
file_put_contents($structurefile, $structure);
|
|
|
240 |
testing_fix_file_permissions($structurefile);
|
|
|
241 |
}
|
|
|
242 |
|
|
|
243 |
/**
|
|
|
244 |
* Stores the version hash in both database and dataroot
|
|
|
245 |
*/
|
|
|
246 |
protected static function store_versions_hash() {
|
|
|
247 |
global $CFG;
|
|
|
248 |
|
|
|
249 |
$framework = self::get_framework();
|
|
|
250 |
$hash = core_component::get_all_versions_hash();
|
|
|
251 |
|
|
|
252 |
// Add test db flag.
|
|
|
253 |
set_config($framework . 'test', $hash);
|
|
|
254 |
|
|
|
255 |
// Hash all plugin versions - helps with very fast detection of db structure changes.
|
|
|
256 |
$hashfile = self::get_dataroot() . '/' . $framework . '/versionshash.txt';
|
|
|
257 |
file_put_contents($hashfile, $hash);
|
|
|
258 |
testing_fix_file_permissions($hashfile);
|
|
|
259 |
}
|
|
|
260 |
|
|
|
261 |
/**
|
|
|
262 |
* Returns contents of all tables right after installation.
|
|
|
263 |
* @static
|
|
|
264 |
* @return array $table=>$records
|
|
|
265 |
*/
|
|
|
266 |
protected static function get_tabledata() {
|
|
|
267 |
if (!isset(self::$tabledata)) {
|
|
|
268 |
$framework = self::get_framework();
|
|
|
269 |
|
|
|
270 |
$datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
|
|
|
271 |
if (!file_exists($datafile)) {
|
|
|
272 |
// Not initialised yet.
|
|
|
273 |
return [];
|
|
|
274 |
}
|
|
|
275 |
|
|
|
276 |
$data = file_get_contents($datafile);
|
|
|
277 |
self::$tabledata = unserialize($data);
|
|
|
278 |
}
|
|
|
279 |
|
|
|
280 |
if (!is_array(self::$tabledata)) {
|
|
|
281 |
testing_error(
|
|
|
282 |
1,
|
|
|
283 |
'Can not read dataroot/' . $framework . '/tabledata.ser or invalid format, reinitialize test database.',
|
|
|
284 |
);
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
return self::$tabledata;
|
|
|
288 |
}
|
|
|
289 |
|
|
|
290 |
/**
|
|
|
291 |
* Returns structure of all tables right after installation.
|
|
|
292 |
* @static
|
|
|
293 |
* @return array $table=>$records
|
|
|
294 |
*/
|
|
|
295 |
public static function get_tablestructure() {
|
|
|
296 |
if (!isset(self::$tablestructure)) {
|
|
|
297 |
$framework = self::get_framework();
|
|
|
298 |
|
|
|
299 |
$structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
|
|
|
300 |
if (!file_exists($structurefile)) {
|
|
|
301 |
// Not initialised yet.
|
|
|
302 |
return [];
|
|
|
303 |
}
|
|
|
304 |
|
|
|
305 |
$data = file_get_contents($structurefile);
|
|
|
306 |
self::$tablestructure = unserialize($data);
|
|
|
307 |
}
|
|
|
308 |
|
|
|
309 |
if (!is_array(self::$tablestructure)) {
|
|
|
310 |
testing_error(
|
|
|
311 |
1,
|
|
|
312 |
"Can not read dataroot/{$framework}/tablestructure.ser or invalid format, reinitialize test database.",
|
|
|
313 |
);
|
|
|
314 |
}
|
|
|
315 |
|
|
|
316 |
return self::$tablestructure;
|
|
|
317 |
}
|
|
|
318 |
|
|
|
319 |
/**
|
|
|
320 |
* Returns the names of sequences for each autoincrementing id field in all standard tables.
|
|
|
321 |
* @static
|
|
|
322 |
* @return array $table=>$sequencename
|
|
|
323 |
*/
|
|
|
324 |
public static function get_sequencenames() {
|
|
|
325 |
global $DB;
|
|
|
326 |
|
|
|
327 |
if (isset(self::$sequencenames)) {
|
|
|
328 |
return self::$sequencenames;
|
|
|
329 |
}
|
|
|
330 |
|
|
|
331 |
if (!$structure = self::get_tablestructure()) {
|
|
|
332 |
return [];
|
|
|
333 |
}
|
|
|
334 |
|
|
|
335 |
self::$sequencenames = [];
|
|
|
336 |
foreach ($structure as $table => $ignored) {
|
|
|
337 |
$name = $DB->get_manager()->generator->getSequenceFromDB(new xmldb_table($table));
|
|
|
338 |
if ($name !== false) {
|
|
|
339 |
self::$sequencenames[$table] = $name;
|
|
|
340 |
}
|
|
|
341 |
}
|
|
|
342 |
|
|
|
343 |
return self::$sequencenames;
|
|
|
344 |
}
|
|
|
345 |
|
|
|
346 |
/**
|
|
|
347 |
* Returns list of tables that are unmodified and empty.
|
|
|
348 |
*
|
|
|
349 |
* @static
|
|
|
350 |
* @return array of table names, empty if unknown
|
|
|
351 |
*/
|
|
|
352 |
protected static function guess_unmodified_empty_tables() {
|
|
|
353 |
global $DB;
|
|
|
354 |
|
|
|
355 |
$dbfamily = $DB->get_dbfamily();
|
|
|
356 |
|
|
|
357 |
if ($dbfamily === 'mysql') {
|
|
|
358 |
$empties = [];
|
|
|
359 |
$prefix = $DB->get_prefix();
|
|
|
360 |
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", [$prefix . '%']);
|
|
|
361 |
foreach ($rs as $info) {
|
|
|
362 |
$table = strtolower($info->name);
|
|
|
363 |
if (strpos($table, $prefix) !== 0) {
|
|
|
364 |
// Incorrect table match caused by _.
|
|
|
365 |
continue;
|
|
|
366 |
}
|
|
|
367 |
|
|
|
368 |
if (!is_null($info->auto_increment) && $info->rows == 0 && ($info->auto_increment == 1)) {
|
|
|
369 |
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
|
|
|
370 |
$empties[$table] = $table;
|
|
|
371 |
}
|
|
|
372 |
}
|
|
|
373 |
$rs->close();
|
|
|
374 |
return $empties;
|
|
|
375 |
} else if ($dbfamily === 'mssql') {
|
|
|
376 |
$empties = [];
|
|
|
377 |
$prefix = $DB->get_prefix();
|
|
|
378 |
$sql = "SELECT t.name
|
|
|
379 |
FROM sys.identity_columns i
|
|
|
380 |
JOIN sys.tables t ON t.object_id = i.object_id
|
|
|
381 |
WHERE t.name LIKE ?
|
|
|
382 |
AND i.name = 'id'
|
|
|
383 |
AND i.last_value IS NULL";
|
|
|
384 |
$rs = $DB->get_recordset_sql($sql, [$prefix . '%']);
|
|
|
385 |
foreach ($rs as $info) {
|
|
|
386 |
$table = strtolower($info->name);
|
|
|
387 |
if (strpos($table, $prefix) !== 0) {
|
|
|
388 |
// Incorrect table match caused by _.
|
|
|
389 |
continue;
|
|
|
390 |
}
|
|
|
391 |
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
|
|
|
392 |
$empties[$table] = $table;
|
|
|
393 |
}
|
|
|
394 |
$rs->close();
|
|
|
395 |
return $empties;
|
|
|
396 |
} else if ($dbfamily === 'oracle') {
|
|
|
397 |
$sequences = self::get_sequencenames();
|
|
|
398 |
$sequences = array_map('strtoupper', $sequences);
|
|
|
399 |
$lookup = array_flip($sequences);
|
|
|
400 |
$empties = [];
|
|
|
401 |
[$seqs, $params] = $DB->get_in_or_equal($sequences);
|
|
|
402 |
$sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs";
|
|
|
403 |
$rs = $DB->get_recordset_sql($sql, $params);
|
|
|
404 |
foreach ($rs as $seq) {
|
|
|
405 |
$table = $lookup[$seq->sequence_name];
|
|
|
406 |
$empties[$table] = $table;
|
|
|
407 |
}
|
|
|
408 |
$rs->close();
|
|
|
409 |
return $empties;
|
|
|
410 |
} else {
|
|
|
411 |
return [];
|
|
|
412 |
}
|
|
|
413 |
}
|
|
|
414 |
|
|
|
415 |
/**
|
|
|
416 |
* Determine the next unique starting id sequences.
|
|
|
417 |
*
|
|
|
418 |
* @static
|
|
|
419 |
* @param array $records The records to use to determine the starting value for the table.
|
|
|
420 |
* @param string $table table name.
|
|
|
421 |
* @return int The value the sequence should be set to.
|
|
|
422 |
*/
|
|
|
423 |
private static function get_next_sequence_starting_value($records, $table) {
|
|
|
424 |
if (isset(self::$tablesequences[$table])) {
|
|
|
425 |
return self::$tablesequences[$table];
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
$id = self::$sequencenextstartingid;
|
|
|
429 |
|
|
|
430 |
// If there are records, calculate the minimum id we can use.
|
|
|
431 |
// It must be bigger than the last record's id.
|
|
|
432 |
if (!empty($records)) {
|
|
|
433 |
$lastrecord = end($records);
|
|
|
434 |
$id = max($id, $lastrecord->id + 1);
|
|
|
435 |
}
|
|
|
436 |
|
|
|
437 |
self::$sequencenextstartingid = $id + 1000;
|
|
|
438 |
|
|
|
439 |
self::$tablesequences[$table] = $id;
|
|
|
440 |
|
|
|
441 |
return $id;
|
|
|
442 |
}
|
|
|
443 |
|
|
|
444 |
/**
|
|
|
445 |
* Reset all database sequences to initial values.
|
|
|
446 |
*
|
|
|
447 |
* @static
|
|
|
448 |
* @param array $empties tables that are known to be unmodified and empty
|
|
|
449 |
* @return void
|
|
|
450 |
*/
|
|
|
451 |
public static function reset_all_database_sequences(array $empties = null) {
|
|
|
452 |
global $DB;
|
|
|
453 |
|
|
|
454 |
if (!$data = self::get_tabledata()) {
|
|
|
455 |
// Not initialised yet.
|
|
|
456 |
return;
|
|
|
457 |
}
|
|
|
458 |
if (!$structure = self::get_tablestructure()) {
|
|
|
459 |
// Not initialised yet.
|
|
|
460 |
return;
|
|
|
461 |
}
|
|
|
462 |
|
|
|
463 |
$updatedtables = self::$tableupdated;
|
|
|
464 |
|
|
|
465 |
// If all starting Id's are the same, it's difficult to detect coding and testing
|
|
|
466 |
// errors that use the incorrect id in tests. The classic case is cmid vs instance id.
|
|
|
467 |
// To reduce the chance of the coding error, we start sequences at different values where possible.
|
|
|
468 |
// In a attempt to avoid tables with existing id's we start at a high number.
|
|
|
469 |
// Reset the value each time all database sequences are reset.
|
|
|
470 |
if (defined('PHPUNIT_SEQUENCE_START') && PHPUNIT_SEQUENCE_START) {
|
|
|
471 |
self::$sequencenextstartingid = PHPUNIT_SEQUENCE_START;
|
|
|
472 |
} else {
|
|
|
473 |
self::$sequencenextstartingid = 100000;
|
|
|
474 |
}
|
|
|
475 |
|
|
|
476 |
$dbfamily = $DB->get_dbfamily();
|
|
|
477 |
if ($dbfamily === 'postgres') {
|
|
|
478 |
$queries = [];
|
|
|
479 |
$prefix = $DB->get_prefix();
|
|
|
480 |
foreach ($data as $table => $records) {
|
|
|
481 |
// If table is not modified then no need to do anything.
|
|
|
482 |
if (!isset($updatedtables[$table])) {
|
|
|
483 |
continue;
|
|
|
484 |
}
|
|
|
485 |
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
|
|
|
486 |
$nextid = self::get_next_sequence_starting_value($records, $table);
|
|
|
487 |
$queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
|
|
|
488 |
}
|
|
|
489 |
}
|
|
|
490 |
if ($queries) {
|
|
|
491 |
$DB->change_database_structure(implode(';', $queries));
|
|
|
492 |
}
|
|
|
493 |
} else if ($dbfamily === 'mysql') {
|
|
|
494 |
$queries = [];
|
|
|
495 |
$sequences = [];
|
|
|
496 |
$prefix = $DB->get_prefix();
|
|
|
497 |
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", [$prefix . '%']);
|
|
|
498 |
foreach ($rs as $info) {
|
|
|
499 |
$table = strtolower($info->name);
|
|
|
500 |
if (strpos($table, $prefix) !== 0) {
|
|
|
501 |
// Incorrect table match caused by _.
|
|
|
502 |
continue;
|
|
|
503 |
}
|
|
|
504 |
if (!is_null($info->auto_increment)) {
|
|
|
505 |
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
|
|
|
506 |
$sequences[$table] = $info->auto_increment;
|
|
|
507 |
}
|
|
|
508 |
}
|
|
|
509 |
$rs->close();
|
|
|
510 |
$prefix = $DB->get_prefix();
|
|
|
511 |
foreach ($data as $table => $records) {
|
|
|
512 |
// If table is not modified then no need to do anything.
|
|
|
513 |
if (!isset($updatedtables[$table])) {
|
|
|
514 |
continue;
|
|
|
515 |
}
|
|
|
516 |
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
|
|
|
517 |
if (isset($sequences[$table])) {
|
|
|
518 |
$nextid = self::get_next_sequence_starting_value($records, $table);
|
|
|
519 |
if ($sequences[$table] != $nextid) {
|
|
|
520 |
$queries[] = "ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid";
|
|
|
521 |
}
|
|
|
522 |
} else {
|
|
|
523 |
// Some problem exists, fallback to standard code.
|
|
|
524 |
$DB->get_manager()->reset_sequence($table);
|
|
|
525 |
}
|
|
|
526 |
}
|
|
|
527 |
}
|
|
|
528 |
if ($queries) {
|
|
|
529 |
$DB->change_database_structure(implode(';', $queries));
|
|
|
530 |
}
|
|
|
531 |
} else if ($dbfamily === 'oracle') {
|
|
|
532 |
$sequences = self::get_sequencenames();
|
|
|
533 |
$sequences = array_map('strtoupper', $sequences);
|
|
|
534 |
$lookup = array_flip($sequences);
|
|
|
535 |
|
|
|
536 |
$current = [];
|
|
|
537 |
[$seqs, $params] = $DB->get_in_or_equal($sequences);
|
|
|
538 |
$sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs";
|
|
|
539 |
$rs = $DB->get_recordset_sql($sql, $params);
|
|
|
540 |
foreach ($rs as $seq) {
|
|
|
541 |
$table = $lookup[$seq->sequence_name];
|
|
|
542 |
$current[$table] = $seq->last_number;
|
|
|
543 |
}
|
|
|
544 |
$rs->close();
|
|
|
545 |
|
|
|
546 |
foreach ($data as $table => $records) {
|
|
|
547 |
// If table is not modified then no need to do anything.
|
|
|
548 |
if (!isset($updatedtables[$table])) {
|
|
|
549 |
continue;
|
|
|
550 |
}
|
|
|
551 |
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
|
|
|
552 |
$lastrecord = end($records);
|
|
|
553 |
if ($lastrecord) {
|
|
|
554 |
$nextid = $lastrecord->id + 1;
|
|
|
555 |
} else {
|
|
|
556 |
$nextid = 1;
|
|
|
557 |
}
|
|
|
558 |
if (!isset($current[$table])) {
|
|
|
559 |
$DB->get_manager()->reset_sequence($table);
|
|
|
560 |
} else if ($nextid == $current[$table]) {
|
|
|
561 |
continue;
|
|
|
562 |
}
|
|
|
563 |
// Reset as fast as possible.
|
|
|
564 |
// Alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle.
|
|
|
565 |
$seqname = $sequences[$table];
|
|
|
566 |
$cachesize = $DB->get_manager()->generator->sequence_cache_size;
|
|
|
567 |
$DB->change_database_structure("DROP SEQUENCE $seqname");
|
|
|
568 |
$DB->change_database_structure(
|
|
|
569 |
"CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize",
|
|
|
570 |
);
|
|
|
571 |
}
|
|
|
572 |
}
|
|
|
573 |
} else {
|
|
|
574 |
// Note: does mssql support any kind of faster reset?
|
|
|
575 |
// This also implies mssql will not use unique sequence values.
|
|
|
576 |
if (is_null($empties) && (empty($updatedtables))) {
|
|
|
577 |
$empties = self::guess_unmodified_empty_tables();
|
|
|
578 |
}
|
|
|
579 |
foreach ($data as $table => $records) {
|
|
|
580 |
// If table is not modified then no need to do anything.
|
|
|
581 |
if (isset($empties[$table]) || (!isset($updatedtables[$table]))) {
|
|
|
582 |
continue;
|
|
|
583 |
}
|
|
|
584 |
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
|
|
|
585 |
$DB->get_manager()->reset_sequence($table);
|
|
|
586 |
}
|
|
|
587 |
}
|
|
|
588 |
}
|
|
|
589 |
}
|
|
|
590 |
|
|
|
591 |
/**
|
|
|
592 |
* Reset all database tables to default values.
|
|
|
593 |
* @static
|
|
|
594 |
* @return bool true if reset done, false if skipped
|
|
|
595 |
*/
|
|
|
596 |
public static function reset_database() {
|
|
|
597 |
global $DB;
|
|
|
598 |
|
|
|
599 |
$tables = $DB->get_tables(false);
|
|
|
600 |
if (!$tables || empty($tables['config'])) {
|
|
|
601 |
// Not installed yet.
|
|
|
602 |
return false;
|
|
|
603 |
}
|
|
|
604 |
|
|
|
605 |
if (!$data = self::get_tabledata()) {
|
|
|
606 |
// Not initialised yet.
|
|
|
607 |
return false;
|
|
|
608 |
}
|
|
|
609 |
if (!$structure = self::get_tablestructure()) {
|
|
|
610 |
// Not initialised yet.
|
|
|
611 |
return false;
|
|
|
612 |
}
|
|
|
613 |
|
|
|
614 |
$empties = [];
|
|
|
615 |
// Use local copy of self::$tableupdated, as list gets updated in for loop.
|
|
|
616 |
$updatedtables = self::$tableupdated;
|
|
|
617 |
|
|
|
618 |
// If empty tablesequences list then it's the very first run.
|
|
|
619 |
if (empty(self::$tablesequences) && (($DB->get_dbfamily() != 'mysql') && ($DB->get_dbfamily() != 'postgres'))) {
|
|
|
620 |
// Only Mysql and Postgres support random sequence, so don't guess, just reset everything on very first run.
|
|
|
621 |
$empties = self::guess_unmodified_empty_tables();
|
|
|
622 |
}
|
|
|
623 |
|
|
|
624 |
// Check if any table has been modified by behat selenium process.
|
|
|
625 |
if (defined('BEHAT_SITE_RUNNING')) {
|
|
|
626 |
// Crazy way to reset :(.
|
|
|
627 |
$tablesupdatedfile = self::get_tables_updated_by_scenario_list_path();
|
|
|
628 |
if ($tablesupdated = @json_decode(file_get_contents($tablesupdatedfile), true)) {
|
|
|
629 |
self::$tableupdated = array_merge(self::$tableupdated, $tablesupdated);
|
|
|
630 |
unlink($tablesupdatedfile);
|
|
|
631 |
}
|
|
|
632 |
$updatedtables = self::$tableupdated;
|
|
|
633 |
}
|
|
|
634 |
|
|
|
635 |
foreach ($data as $table => $records) {
|
|
|
636 |
// If table is not modified then no need to do anything.
|
|
|
637 |
// $updatedtables tables is set after the first run, so check before checking for specific table update.
|
|
|
638 |
if (!empty($updatedtables) && !isset($updatedtables[$table])) {
|
|
|
639 |
continue;
|
|
|
640 |
}
|
|
|
641 |
|
|
|
642 |
if (empty($records)) {
|
|
|
643 |
if (!isset($empties[$table])) {
|
|
|
644 |
// Table has been modified and is not empty.
|
|
|
645 |
$DB->delete_records($table, []);
|
|
|
646 |
}
|
|
|
647 |
continue;
|
|
|
648 |
}
|
|
|
649 |
|
|
|
650 |
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
|
|
|
651 |
$currentrecords = $DB->get_records($table, [], 'id ASC');
|
|
|
652 |
$changed = false;
|
|
|
653 |
foreach ($records as $id => $record) {
|
|
|
654 |
if (!isset($currentrecords[$id])) {
|
|
|
655 |
$changed = true;
|
|
|
656 |
break;
|
|
|
657 |
}
|
|
|
658 |
if ((array)$record != (array)$currentrecords[$id]) {
|
|
|
659 |
$changed = true;
|
|
|
660 |
break;
|
|
|
661 |
}
|
|
|
662 |
unset($currentrecords[$id]);
|
|
|
663 |
}
|
|
|
664 |
if (!$changed) {
|
|
|
665 |
if ($currentrecords) {
|
|
|
666 |
$lastrecord = end($records);
|
|
|
667 |
$DB->delete_records_select($table, "id > ?", [$lastrecord->id]);
|
|
|
668 |
continue;
|
|
|
669 |
} else {
|
|
|
670 |
continue;
|
|
|
671 |
}
|
|
|
672 |
}
|
|
|
673 |
}
|
|
|
674 |
|
|
|
675 |
$DB->delete_records($table, []);
|
|
|
676 |
foreach ($records as $record) {
|
|
|
677 |
$DB->import_record($table, $record, false, true);
|
|
|
678 |
}
|
|
|
679 |
}
|
|
|
680 |
|
|
|
681 |
// Reset all next record ids - aka sequences.
|
|
|
682 |
self::reset_all_database_sequences($empties);
|
|
|
683 |
|
|
|
684 |
// Remove extra tables.
|
|
|
685 |
foreach ($tables as $table) {
|
|
|
686 |
if (!isset($data[$table])) {
|
|
|
687 |
$DB->get_manager()->drop_table(new xmldb_table($table));
|
|
|
688 |
}
|
|
|
689 |
}
|
|
|
690 |
|
|
|
691 |
self::reset_updated_table_list();
|
|
|
692 |
|
|
|
693 |
return true;
|
|
|
694 |
}
|
|
|
695 |
|
|
|
696 |
/**
|
|
|
697 |
* Purge dataroot directory
|
|
|
698 |
* @static
|
|
|
699 |
* @return void
|
|
|
700 |
*/
|
|
|
701 |
public static function reset_dataroot() {
|
|
|
702 |
global $CFG;
|
|
|
703 |
|
|
|
704 |
$childclassname = self::get_framework() . '_util';
|
|
|
705 |
|
|
|
706 |
// Do not delete automatically installed files.
|
|
|
707 |
self::skip_original_data_files($childclassname);
|
|
|
708 |
|
|
|
709 |
// Clear file status cache, before checking file_exists.
|
|
|
710 |
clearstatcache();
|
|
|
711 |
|
|
|
712 |
// Clean up the dataroot folder.
|
|
|
713 |
$handle = opendir(self::get_dataroot());
|
|
|
714 |
while (false !== ($item = readdir($handle))) {
|
|
|
715 |
if (in_array($item, $childclassname::$datarootskiponreset)) {
|
|
|
716 |
continue;
|
|
|
717 |
}
|
|
|
718 |
if (is_dir(self::get_dataroot() . "/$item")) {
|
|
|
719 |
remove_dir(self::get_dataroot() . "/$item", false);
|
|
|
720 |
} else {
|
|
|
721 |
unlink(self::get_dataroot() . "/$item");
|
|
|
722 |
}
|
|
|
723 |
}
|
|
|
724 |
closedir($handle);
|
|
|
725 |
|
|
|
726 |
// Clean up the dataroot/filedir folder.
|
|
|
727 |
if (file_exists(self::get_dataroot() . '/filedir')) {
|
|
|
728 |
$handle = opendir(self::get_dataroot() . '/filedir');
|
|
|
729 |
while (false !== ($item = readdir($handle))) {
|
|
|
730 |
if (in_array('filedir' . DIRECTORY_SEPARATOR . $item, $childclassname::$datarootskiponreset)) {
|
|
|
731 |
continue;
|
|
|
732 |
}
|
|
|
733 |
if (is_dir(self::get_dataroot() . "/filedir/$item")) {
|
|
|
734 |
remove_dir(self::get_dataroot() . "/filedir/$item", false);
|
|
|
735 |
} else {
|
|
|
736 |
unlink(self::get_dataroot() . "/filedir/$item");
|
|
|
737 |
}
|
|
|
738 |
}
|
|
|
739 |
closedir($handle);
|
|
|
740 |
}
|
|
|
741 |
|
|
|
742 |
make_temp_directory('');
|
|
|
743 |
make_backup_temp_directory('');
|
|
|
744 |
make_cache_directory('');
|
|
|
745 |
make_localcache_directory('');
|
|
|
746 |
// Purge all data from the caches. This is required for consistency between tests.
|
|
|
747 |
// Any file caches that happened to be within the data root will have already been clearer (because we just deleted cache)
|
|
|
748 |
// and now we will purge any other caches as well. This must be done before the cache_factory::reset() as that
|
|
|
749 |
// removes all definitions of caches and purge does not have valid caches to operate on.
|
|
|
750 |
cache_helper::purge_all();
|
|
|
751 |
// Reset the cache API so that it recreates it's required directories as well.
|
|
|
752 |
cache_factory::reset();
|
|
|
753 |
}
|
|
|
754 |
|
|
|
755 |
/**
|
|
|
756 |
* Gets a text-based site version description.
|
|
|
757 |
*
|
|
|
758 |
* @return string The site info
|
|
|
759 |
*/
|
|
|
760 |
public static function get_site_info() {
|
|
|
761 |
global $CFG;
|
|
|
762 |
|
|
|
763 |
$output = '';
|
|
|
764 |
|
|
|
765 |
// All developers have to understand English, do not localise!
|
|
|
766 |
$env = self::get_environment();
|
|
|
767 |
|
|
|
768 |
$output .= "Moodle " . $env['moodleversion'];
|
|
|
769 |
if ($hash = self::get_git_hash()) {
|
|
|
770 |
$output .= ", $hash";
|
|
|
771 |
}
|
|
|
772 |
$output .= "\n";
|
|
|
773 |
|
|
|
774 |
// Add php version.
|
|
|
775 |
require_once($CFG->libdir . '/environmentlib.php');
|
|
|
776 |
$output .= "Php: " . normalize_version($env['phpversion']);
|
|
|
777 |
|
|
|
778 |
// Add database type and version.
|
|
|
779 |
$output .= ", " . $env['dbtype'] . ": " . $env['dbversion'];
|
|
|
780 |
|
|
|
781 |
// OS details.
|
|
|
782 |
$output .= ", OS: " . $env['os'] . "\n";
|
|
|
783 |
|
|
|
784 |
return $output;
|
|
|
785 |
}
|
|
|
786 |
|
|
|
787 |
/**
|
|
|
788 |
* Try to get current git hash of the Moodle in $CFG->dirroot.
|
|
|
789 |
* @return string null if unknown, sha1 hash if known
|
|
|
790 |
*/
|
|
|
791 |
public static function get_git_hash() {
|
|
|
792 |
global $CFG;
|
|
|
793 |
|
|
|
794 |
// This is a bit naive, but it should mostly work for all platforms.
|
|
|
795 |
|
|
|
796 |
if (!file_exists("$CFG->dirroot/.git/HEAD")) {
|
|
|
797 |
return null;
|
|
|
798 |
}
|
|
|
799 |
|
|
|
800 |
$headcontent = file_get_contents("$CFG->dirroot/.git/HEAD");
|
|
|
801 |
if ($headcontent === false) {
|
|
|
802 |
return null;
|
|
|
803 |
}
|
|
|
804 |
|
|
|
805 |
$headcontent = trim($headcontent);
|
|
|
806 |
|
|
|
807 |
// If it is pointing to a hash we return it directly.
|
|
|
808 |
if (strlen($headcontent) === 40) {
|
|
|
809 |
return $headcontent;
|
|
|
810 |
}
|
|
|
811 |
|
|
|
812 |
if (strpos($headcontent, 'ref: ') !== 0) {
|
|
|
813 |
return null;
|
|
|
814 |
}
|
|
|
815 |
|
|
|
816 |
$ref = substr($headcontent, 5);
|
|
|
817 |
|
|
|
818 |
if (!file_exists("$CFG->dirroot/.git/$ref")) {
|
|
|
819 |
return null;
|
|
|
820 |
}
|
|
|
821 |
|
|
|
822 |
$hash = file_get_contents("$CFG->dirroot/.git/$ref");
|
|
|
823 |
|
|
|
824 |
if ($hash === false) {
|
|
|
825 |
return null;
|
|
|
826 |
}
|
|
|
827 |
|
|
|
828 |
$hash = trim($hash);
|
|
|
829 |
|
|
|
830 |
if (strlen($hash) != 40) {
|
|
|
831 |
return null;
|
|
|
832 |
}
|
|
|
833 |
|
|
|
834 |
return $hash;
|
|
|
835 |
}
|
|
|
836 |
|
|
|
837 |
/**
|
|
|
838 |
* Set state of modified tables.
|
|
|
839 |
*
|
|
|
840 |
* @param string $sql sql which is updating the table.
|
|
|
841 |
*/
|
|
|
842 |
public static function set_table_modified_by_sql($sql) {
|
|
|
843 |
global $DB;
|
|
|
844 |
|
|
|
845 |
$prefix = $DB->get_prefix();
|
|
|
846 |
|
|
|
847 |
preg_match('/( ' . $prefix . '\w*)(.*)/', $sql, $matches);
|
|
|
848 |
// Ignore random sql for testing like "XXUPDATE SET XSSD".
|
|
|
849 |
if (!empty($matches[1])) {
|
|
|
850 |
$table = trim($matches[1]);
|
|
|
851 |
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
|
|
|
852 |
self::$tableupdated[$table] = true;
|
|
|
853 |
|
|
|
854 |
if (defined('BEHAT_SITE_RUNNING')) {
|
|
|
855 |
$tablesupdatedfile = self::get_tables_updated_by_scenario_list_path();
|
|
|
856 |
$tablesupdated = @json_decode(file_get_contents($tablesupdatedfile), true);
|
|
|
857 |
if (!isset($tablesupdated[$table])) {
|
|
|
858 |
$tablesupdated[$table] = true;
|
|
|
859 |
@file_put_contents($tablesupdatedfile, json_encode($tablesupdated, JSON_PRETTY_PRINT));
|
|
|
860 |
}
|
|
|
861 |
}
|
|
|
862 |
}
|
|
|
863 |
}
|
|
|
864 |
|
|
|
865 |
/**
|
|
|
866 |
* Reset updated table list. This should be done after every reset.
|
|
|
867 |
*/
|
|
|
868 |
public static function reset_updated_table_list() {
|
|
|
869 |
self::$tableupdated = [];
|
|
|
870 |
}
|
|
|
871 |
|
|
|
872 |
/**
|
|
|
873 |
* Delete tablesupdatedbyscenario file. This should be called before suite,
|
|
|
874 |
* to ensure full db reset.
|
|
|
875 |
*/
|
|
|
876 |
public static function clean_tables_updated_by_scenario_list() {
|
|
|
877 |
$tablesupdatedfile = self::get_tables_updated_by_scenario_list_path();
|
|
|
878 |
if (file_exists($tablesupdatedfile)) {
|
|
|
879 |
unlink($tablesupdatedfile);
|
|
|
880 |
}
|
|
|
881 |
|
|
|
882 |
// Reset static cache of cli process.
|
|
|
883 |
self::reset_updated_table_list();
|
|
|
884 |
}
|
|
|
885 |
|
|
|
886 |
/**
|
|
|
887 |
* Returns the path to the file which holds list of tables updated in scenario.
|
|
|
888 |
* @return string
|
|
|
889 |
*/
|
|
|
890 |
final protected static function get_tables_updated_by_scenario_list_path() {
|
|
|
891 |
return self::get_dataroot() . '/tablesupdatedbyscenario.json';
|
|
|
892 |
}
|
|
|
893 |
|
|
|
894 |
/**
|
|
|
895 |
* Drop the whole test database
|
|
|
896 |
* @static
|
|
|
897 |
* @param bool $displayprogress
|
|
|
898 |
*/
|
|
|
899 |
protected static function drop_database($displayprogress = false) {
|
|
|
900 |
global $DB;
|
|
|
901 |
|
|
|
902 |
$tables = $DB->get_tables(false);
|
|
|
903 |
if (isset($tables['config'])) {
|
|
|
904 |
// Config always last to prevent problems with interrupted drops!
|
|
|
905 |
unset($tables['config']);
|
|
|
906 |
$tables['config'] = 'config';
|
|
|
907 |
}
|
|
|
908 |
|
|
|
909 |
if ($displayprogress) {
|
|
|
910 |
echo "Dropping tables:\n";
|
|
|
911 |
}
|
|
|
912 |
$dotsonline = 0;
|
|
|
913 |
foreach ($tables as $tablename) {
|
|
|
914 |
$table = new xmldb_table($tablename);
|
|
|
915 |
$DB->get_manager()->drop_table($table);
|
|
|
916 |
|
|
|
917 |
if ($dotsonline == 60) {
|
|
|
918 |
if ($displayprogress) {
|
|
|
919 |
echo "\n";
|
|
|
920 |
}
|
|
|
921 |
$dotsonline = 0;
|
|
|
922 |
}
|
|
|
923 |
if ($displayprogress) {
|
|
|
924 |
echo '.';
|
|
|
925 |
}
|
|
|
926 |
$dotsonline += 1;
|
|
|
927 |
}
|
|
|
928 |
if ($displayprogress) {
|
|
|
929 |
echo "\n";
|
|
|
930 |
}
|
|
|
931 |
}
|
|
|
932 |
|
|
|
933 |
/**
|
|
|
934 |
* Drops the test framework dataroot
|
|
|
935 |
* @static
|
|
|
936 |
*/
|
|
|
937 |
protected static function drop_dataroot() {
|
|
|
938 |
global $CFG;
|
|
|
939 |
|
|
|
940 |
$framework = self::get_framework();
|
|
|
941 |
$childclassname = $framework . '_util';
|
|
|
942 |
|
|
|
943 |
$files = scandir(self::get_dataroot() . '/' . $framework);
|
|
|
944 |
foreach ($files as $file) {
|
|
|
945 |
if (in_array($file, $childclassname::$datarootskipondrop)) {
|
|
|
946 |
continue;
|
|
|
947 |
}
|
|
|
948 |
$path = self::get_dataroot() . '/' . $framework . '/' . $file;
|
|
|
949 |
if (is_dir($path)) {
|
|
|
950 |
remove_dir($path, false);
|
|
|
951 |
} else {
|
|
|
952 |
unlink($path);
|
|
|
953 |
}
|
|
|
954 |
}
|
|
|
955 |
|
|
|
956 |
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
|
|
|
957 |
if (file_exists($jsonfilepath)) {
|
|
|
958 |
// Delete the json file.
|
|
|
959 |
unlink($jsonfilepath);
|
|
|
960 |
// Delete the dataroot filedir.
|
|
|
961 |
remove_dir(self::get_dataroot() . '/filedir', false);
|
|
|
962 |
}
|
|
|
963 |
}
|
|
|
964 |
|
|
|
965 |
/**
|
|
|
966 |
* Skip the original dataroot files to not been reset.
|
|
|
967 |
*
|
|
|
968 |
* @static
|
|
|
969 |
* @param string $utilclassname the util class name..
|
|
|
970 |
*/
|
|
|
971 |
protected static function skip_original_data_files($utilclassname) {
|
|
|
972 |
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
|
|
|
973 |
if (file_exists($jsonfilepath)) {
|
|
|
974 |
$listfiles = file_get_contents($jsonfilepath);
|
|
|
975 |
|
|
|
976 |
// Mark each files as to not be reset.
|
|
|
977 |
if (!empty($listfiles) && !self::$originaldatafilesjsonadded) {
|
|
|
978 |
$originaldatarootfiles = json_decode($listfiles);
|
|
|
979 |
// Keep the json file. Only drop_dataroot() should delete it.
|
|
|
980 |
$originaldatarootfiles[] = self::$originaldatafilesjson;
|
|
|
981 |
$utilclassname::$datarootskiponreset = array_merge(
|
|
|
982 |
$utilclassname::$datarootskiponreset,
|
|
|
983 |
$originaldatarootfiles
|
|
|
984 |
);
|
|
|
985 |
self::$originaldatafilesjsonadded = true;
|
|
|
986 |
}
|
|
|
987 |
}
|
|
|
988 |
}
|
|
|
989 |
|
|
|
990 |
/**
|
|
|
991 |
* Save the list of the original dataroot files into a json file.
|
|
|
992 |
*/
|
|
|
993 |
protected static function save_original_data_files() {
|
|
|
994 |
global $CFG;
|
|
|
995 |
|
|
|
996 |
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
|
|
|
997 |
|
|
|
998 |
// Save the original dataroot files if not done (only executed the first time).
|
|
|
999 |
if (!file_exists($jsonfilepath)) {
|
|
|
1000 |
$listfiles = [];
|
|
|
1001 |
$currentdir = 'filedir' . DIRECTORY_SEPARATOR . '.';
|
|
|
1002 |
$parentdir = 'filedir' . DIRECTORY_SEPARATOR . '..';
|
|
|
1003 |
$listfiles[$currentdir] = $currentdir;
|
|
|
1004 |
$listfiles[$parentdir] = $parentdir;
|
|
|
1005 |
|
|
|
1006 |
$filedir = self::get_dataroot() . '/filedir';
|
|
|
1007 |
if (file_exists($filedir)) {
|
|
|
1008 |
$directory = new RecursiveDirectoryIterator($filedir);
|
|
|
1009 |
foreach (new RecursiveIteratorIterator($directory) as $file) {
|
|
|
1010 |
if ($file->isDir()) {
|
|
|
1011 |
$key = substr($file->getPath(), strlen(self::get_dataroot() . '/'));
|
|
|
1012 |
} else {
|
|
|
1013 |
$key = substr($file->getPathName(), strlen(self::get_dataroot() . '/'));
|
|
|
1014 |
}
|
|
|
1015 |
$listfiles[$key] = $key;
|
|
|
1016 |
}
|
|
|
1017 |
}
|
|
|
1018 |
|
|
|
1019 |
// Save the file list in a JSON file.
|
|
|
1020 |
$fp = fopen($jsonfilepath, 'w');
|
|
|
1021 |
fwrite($fp, json_encode(array_values($listfiles)));
|
|
|
1022 |
fclose($fp);
|
|
|
1023 |
}
|
|
|
1024 |
}
|
|
|
1025 |
|
|
|
1026 |
/**
|
|
|
1027 |
* Return list of environment versions on which tests will run.
|
|
|
1028 |
* Environment includes:
|
|
|
1029 |
* - moodleversion
|
|
|
1030 |
* - phpversion
|
|
|
1031 |
* - dbtype
|
|
|
1032 |
* - dbversion
|
|
|
1033 |
* - os
|
|
|
1034 |
*
|
|
|
1035 |
* @return array
|
|
|
1036 |
*/
|
|
|
1037 |
public static function get_environment() {
|
|
|
1038 |
global $CFG, $DB;
|
|
|
1039 |
|
|
|
1040 |
$env = [];
|
|
|
1041 |
|
|
|
1042 |
// Add moodle version.
|
|
|
1043 |
$release = null;
|
|
|
1044 |
require("$CFG->dirroot/version.php");
|
|
|
1045 |
$env['moodleversion'] = $release;
|
|
|
1046 |
|
|
|
1047 |
// Add php version.
|
|
|
1048 |
$phpversion = phpversion();
|
|
|
1049 |
$env['phpversion'] = $phpversion;
|
|
|
1050 |
|
|
|
1051 |
// Add database type and version.
|
|
|
1052 |
$dbtype = $CFG->dbtype;
|
|
|
1053 |
$dbinfo = $DB->get_server_info();
|
|
|
1054 |
$dbversion = $dbinfo['version'];
|
|
|
1055 |
$env['dbtype'] = $dbtype;
|
|
|
1056 |
$env['dbversion'] = $dbversion;
|
|
|
1057 |
|
|
|
1058 |
// OS details.
|
|
|
1059 |
$osdetails = php_uname('s') . " " . php_uname('r') . " " . php_uname('m');
|
|
|
1060 |
$env['os'] = $osdetails;
|
|
|
1061 |
|
|
|
1062 |
return $env;
|
|
|
1063 |
}
|
|
|
1064 |
}
|