Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
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 * Support library for the cache PHPUnit tests. 19 * 20 * This file is part of Moodle's cache API, affectionately called MUC. 21 * It contains the components that are requried in order to use caching. 22 * 23 * @package core 24 * @category cache 25 * @copyright 2012 Sam Hemelryk 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 require_once($CFG->dirroot.'/cache/locallib.php'); 32 33 /** 34 * Override the default cache configuration for our own maniacal purposes. 35 * 36 * This class was originally named cache_config_phpunittest but was renamed in 2.9 37 * because it is used for both unit tests and acceptance tests. 38 * 39 * @since 2.9 40 * @copyright 2012 Sam Hemelryk 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class cache_config_testing extends cache_config_writer { 44 45 /** 46 * Creates the default configuration and saves it. 47 * 48 * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever 49 * be called when there is no configuration file already. 50 * 51 * @param bool $forcesave If set to true then we will forcefully save the default configuration file. 52 * @return true|array Returns true if the default configuration was successfully created. 53 * Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs. 54 */ 55 public static function create_default_configuration($forcesave = false) { 56 global $CFG; 57 // HACK ALERT. 58 // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the 59 // default store plugins are protected from deletion. 60 $writer = new self; 61 $writer->configstores = self::get_default_stores(); 62 $writer->configdefinitions = self::locate_definitions(); 63 $defaultapplication = 'default_application'; 64 65 $appdefine = defined('TEST_CACHE_USING_APPLICATION_STORE') ? TEST_CACHE_USING_APPLICATION_STORE : false; 66 if ($appdefine !== false && preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $appdefine)) { 67 $expectedstore = $appdefine; 68 $file = $CFG->dirroot.'/cache/stores/'.$appdefine.'/lib.php'; 69 $class = 'cachestore_'.$appdefine; 70 if (file_exists($file)) { 71 require_once($file); 72 } 73 if (class_exists($class) && $class::ready_to_be_used_for_testing()) { 74 /* @var cache_store $class */ 75 $writer->configstores['test_application'] = array( 76 'name' => 'test_application', 77 'plugin' => $expectedstore, 78 'modes' => $class::get_supported_modes(), 79 'features' => $class::get_supported_features(), 80 'configuration' => $class::unit_test_configuration() 81 ); 82 83 $defaultapplication = 'test_application'; 84 } 85 } 86 87 $writer->configmodemappings = array( 88 array( 89 'mode' => cache_store::MODE_APPLICATION, 90 'store' => $defaultapplication, 91 'sort' => -1 92 ), 93 array( 94 'mode' => cache_store::MODE_SESSION, 95 'store' => 'default_session', 96 'sort' => -1 97 ), 98 array( 99 'mode' => cache_store::MODE_REQUEST, 100 'store' => 'default_request', 101 'sort' => -1 102 ) 103 ); 104 $writer->configlocks = array( 105 'default_file_lock' => array( 106 'name' => 'cachelock_file_default', 107 'type' => 'cachelock_file', 108 'dir' => 'filelocks', 109 'default' => true 110 ) 111 ); 112 113 $factory = cache_factory::instance(); 114 // We expect the cache to be initialising presently. If its not then something has gone wrong and likely 115 // we are now in a loop. 116 if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) { 117 return $writer->generate_configuration_array(); 118 } 119 $factory->set_state(cache_factory::STATE_SAVING); 120 $writer->config_save(); 121 return true; 122 } 123 124 /** 125 * Returns the expected path to the configuration file. 126 * 127 * We override this function to add handling for $CFG->altcacheconfigpath. 128 * We want to support it so that people can run unit tests against alternative cache setups. 129 * However we don't want to ever make changes to the file at $CFG->altcacheconfigpath so we 130 * always use dataroot and copy the alt file there as required. 131 * 132 * @throws cache_exception 133 * @return string The absolute path 134 */ 135 protected static function get_config_file_path() { 136 global $CFG; 137 // We always use this path. 138 $configpath = $CFG->dataroot.'/muc/config.php'; 139 140 if (!empty($CFG->altcacheconfigpath)) { 141 142 // No need to check we are within a test here, this is the cache config class that gets used 143 // only when one of those is true. 144 if (!defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') || !TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) { 145 // TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH has not being defined or is false, we want to use the default. 146 return $configpath; 147 } 148 149 $path = $CFG->altcacheconfigpath; 150 if (is_dir($path) && is_writable($path)) { 151 // Its a writable directory, thats fine. Convert it to a file. 152 $path = $CFG->altcacheconfigpath.'/cacheconfig.php'; 153 } 154 if (is_readable($path)) { 155 $directory = dirname($configpath); 156 if ($directory !== $CFG->dataroot && !file_exists($directory)) { 157 $result = make_writable_directory($directory, false); 158 if (!$result) { 159 throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.'); 160 } 161 } 162 // We don't care that this fails but we should let the developer know. 163 if (!is_readable($configpath) && !@copy($path, $configpath)) { 164 debugging('Failed to copy alt cache config file to required location'); 165 } 166 } 167 } 168 169 // We always use the dataroot location. 170 return $configpath; 171 } 172 173 /** 174 * Adds a definition to the stack 175 * @param string $area 176 * @param array $properties 177 * @param bool $addmapping By default this method adds a definition and a mapping for that definition. You can 178 * however set this to false if you only want it to add the definition and not the mapping. 179 */ 180 public function phpunit_add_definition($area, array $properties, $addmapping = true) { 181 if (!array_key_exists('overrideclass', $properties)) { 182 switch ($properties['mode']) { 183 case cache_store::MODE_APPLICATION: 184 $properties['overrideclass'] = 'cache_phpunit_application'; 185 break; 186 case cache_store::MODE_SESSION: 187 $properties['overrideclass'] = 'cache_phpunit_session'; 188 break; 189 case cache_store::MODE_REQUEST: 190 $properties['overrideclass'] = 'cache_phpunit_request'; 191 break; 192 } 193 } 194 $this->configdefinitions[$area] = $properties; 195 if ($addmapping) { 196 switch ($properties['mode']) { 197 case cache_store::MODE_APPLICATION: 198 $this->phpunit_add_definition_mapping($area, 'default_application', 0); 199 break; 200 case cache_store::MODE_SESSION: 201 $this->phpunit_add_definition_mapping($area, 'default_session', 0); 202 break; 203 case cache_store::MODE_REQUEST: 204 $this->phpunit_add_definition_mapping($area, 'default_request', 0); 205 break; 206 } 207 } 208 } 209 210 /** 211 * Removes a definition. 212 * @param string $name 213 */ 214 public function phpunit_remove_definition($name) { 215 unset($this->configdefinitions[$name]); 216 } 217 218 /** 219 * Removes the configured stores so that there are none available. 220 */ 221 public function phpunit_remove_stores() { 222 $this->configstores = array(); 223 } 224 225 /** 226 * Forcefully adds a file store. 227 * 228 * @param string $name 229 */ 230 public function phpunit_add_file_store($name) { 231 $this->configstores[$name] = array( 232 'name' => $name, 233 'plugin' => 'file', 234 'configuration' => array( 235 'path' => '' 236 ), 237 'features' => 6, 238 'modes' => 3, 239 'mappingsonly' => false, 240 'class' => 'cachestore_file', 241 'default' => false, 242 'lock' => 'cachelock_file_default' 243 ); 244 } 245 246 /** 247 * Forcefully adds a session store. 248 * 249 * @param string $name 250 */ 251 public function phpunit_add_session_store($name) { 252 $this->configstores[$name] = array( 253 'name' => $name, 254 'plugin' => 'session', 255 'configuration' => array(), 256 'features' => 14, 257 'modes' => 2, 258 'default' => true, 259 'class' => 'cachestore_session', 260 'lock' => 'cachelock_file_default', 261 ); 262 } 263 264 /** 265 * Forcefully injects a definition => store mapping. 266 * 267 * This function does no validation, you should only be calling if it you know 268 * exactly what to expect. 269 * 270 * @param string $definition 271 * @param string $store 272 * @param int $sort 273 */ 274 public function phpunit_add_definition_mapping($definition, $store, $sort) { 275 $this->configdefinitionmappings[] = array( 276 'store' => $store, 277 'definition' => $definition, 278 'sort' => (int)$sort 279 ); 280 } 281 282 /** 283 * Overrides the default site identifier used by the Cache API so that we can be sure of what it is. 284 * 285 * @return string 286 */ 287 public function get_site_identifier() { 288 global $CFG; 289 return $CFG->wwwroot.'phpunit'; 290 } 291 292 /** 293 * Checks if the configuration file exists. 294 * 295 * @return bool True if it exists 296 */ 297 public static function config_file_exists() { 298 // Allow for late static binding by using static. 299 $configfilepath = static::get_config_file_path(); 300 301 // Invalidate opcode php cache, so we get correct status of file. 302 core_component::invalidate_opcode_php_cache($configfilepath); 303 return file_exists($configfilepath); 304 } 305 } 306 307 308 /** 309 * Dummy object for testing cacheable object interface and interaction 310 * 311 * Wake from cache needs specific testing at times to ensure that during multiple 312 * cache get() requests it's possible to verify that it's getting woken each time. 313 * 314 * @copyright 2012 Sam Hemelryk 315 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 316 */ 317 class cache_phpunit_dummy_object extends stdClass implements cacheable_object { 318 /** 319 * Test property 1 320 * @var string 321 */ 322 public $property1; 323 /** 324 * Test property 1 325 * @var string 326 */ 327 public $property2; 328 /** 329 * Test property time for verifying wake is run at each get() call. 330 * @var float 331 */ 332 public $propertytime; 333 /** 334 * Constructor 335 * @param string $property1 336 * @param string $property2 337 */ 338 public function __construct($property1, $property2, $propertytime = null) { 339 $this->property1 = $property1; 340 $this->property2 = $property2; 341 $this->propertytime = $propertytime === null ? microtime(true) : $propertytime; 342 } 343 /** 344 * Prepares this object for caching 345 * @return array 346 */ 347 public function prepare_to_cache() { 348 return array($this->property1.'_ptc', $this->property2.'_ptc', $this->propertytime); 349 } 350 /** 351 * Returns this object from the cache 352 * @param array $data 353 * @return cache_phpunit_dummy_object 354 */ 355 public static function wake_from_cache($data) { 356 $time = null; 357 if (!is_null($data[2])) { 358 // Windows 32bit microtime() resolution is 15ms, we ensure the time has moved forward. 359 do { 360 $time = microtime(true); 361 } while ($time == $data[2]); 362 363 } 364 return new cache_phpunit_dummy_object(array_shift($data).'_wfc', array_shift($data).'_wfc', $time); 365 } 366 } 367 368 /** 369 * Dummy data source object for testing data source interface and implementation 370 * 371 * @copyright 2012 Sam Hemelryk 372 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 373 */ 374 class cache_phpunit_dummy_datasource implements cache_data_source { 375 /** 376 * Returns an instance of this object for use with the cache. 377 * 378 * @param cache_definition $definition 379 * @return cache_phpunit_dummy_datasource 380 */ 381 public static function get_instance_for_cache(cache_definition $definition) { 382 return new cache_phpunit_dummy_datasource(); 383 } 384 385 /** 386 * Loads a key for the cache. 387 * 388 * @param string $key 389 * @return string 390 */ 391 public function load_for_cache($key) { 392 return $key.' has no value really.'; 393 } 394 395 /** 396 * Loads many keys for the cache 397 * 398 * @param array $keys 399 * @return array 400 */ 401 public function load_many_for_cache(array $keys) { 402 $return = array(); 403 foreach ($keys as $key) { 404 $return[$key] = $key.' has no value really.'; 405 } 406 return $return; 407 } 408 } 409 410 /** 411 * PHPUnit application cache loader. 412 * 413 * Used to expose things we could not otherwise see within an application cache. 414 * 415 * @copyright 2012 Sam Hemelryk 416 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 417 */ 418 class cache_phpunit_application extends cache_application { 419 420 /** 421 * Returns the class of the store immediately associated with this cache. 422 * @return string 423 */ 424 public function phpunit_get_store_class() { 425 return get_class($this->get_store()); 426 } 427 428 /** 429 * Returns all the interfaces the cache store implements. 430 * @return array 431 */ 432 public function phpunit_get_store_implements() { 433 return class_implements($this->get_store()); 434 } 435 436 /** 437 * Returns the given key directly from the static acceleration array. 438 * 439 * @param string $key 440 * @return false|mixed 441 */ 442 public function phpunit_static_acceleration_get($key) { 443 return $this->static_acceleration_get($key); 444 } 445 446 /** 447 * Purges only the static acceleration while leaving the rest of the store in tack. 448 * 449 * Used for behaving like you have loaded 2 pages, and reset static while the backing store 450 * still contains all the same data. 451 * 452 */ 453 public function phpunit_static_acceleration_purge() { 454 $this->static_acceleration_purge(); 455 } 456 } 457 458 /** 459 * PHPUnit session cache loader. 460 * 461 * Used to expose things we could not otherwise see within an session cache. 462 * 463 * @copyright 2012 Sam Hemelryk 464 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 465 */ 466 class cache_phpunit_session extends cache_session { 467 468 /** @var Static member used for emulating the behaviour of session_id() during the tests. */ 469 protected static $sessionidmockup = 'phpunitmockupsessionid'; 470 471 /** 472 * Returns the class of the store immediately associated with this cache. 473 * @return string 474 */ 475 public function phpunit_get_store_class() { 476 return get_class($this->get_store()); 477 } 478 479 /** 480 * Returns all the interfaces the cache store implements. 481 * @return array 482 */ 483 public function phpunit_get_store_implements() { 484 return class_implements($this->get_store()); 485 } 486 487 /** 488 * Provide access to the {@link cache_session::get_key_prefix()} method. 489 * 490 * @return string 491 */ 492 public function phpunit_get_key_prefix() { 493 return $this->get_key_prefix(); 494 } 495 496 /** 497 * Allows to inject the session identifier. 498 * 499 * @param string $sessionid 500 */ 501 public static function phpunit_mockup_session_id($sessionid) { 502 static::$sessionidmockup = $sessionid; 503 } 504 505 /** 506 * Override the parent behaviour so that it does not need the actual session_id() call. 507 */ 508 protected function set_session_id() { 509 $this->sessionid = static::$sessionidmockup; 510 } 511 } 512 513 /** 514 * PHPUnit request cache loader. 515 * 516 * Used to expose things we could not otherwise see within an request cache. 517 * 518 * @copyright 2012 Sam Hemelryk 519 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 520 */ 521 class cache_phpunit_request extends cache_request { 522 523 /** 524 * Returns the class of the store immediately associated with this cache. 525 * @return string 526 */ 527 public function phpunit_get_store_class() { 528 return get_class($this->get_store()); 529 } 530 531 /** 532 * Returns all the interfaces the cache store implements. 533 * @return array 534 */ 535 public function phpunit_get_store_implements() { 536 return class_implements($this->get_store()); 537 } 538 } 539 540 /** 541 * Dummy overridden cache loader class that we can use to test overriding loader functionality. 542 * 543 * @copyright 2012 Sam Hemelryk 544 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 545 */ 546 class cache_phpunit_dummy_overrideclass extends cache_application { 547 // Satisfying the code pre-checker is just part of my day job. 548 } 549 550 /** 551 * Cache PHPUnit specific factory. 552 * 553 * @copyright 2012 Sam Hemelryk 554 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 555 */ 556 class cache_phpunit_factory extends cache_factory { 557 /** 558 * Exposes the cache_factory's disable method. 559 * 560 * Perhaps one day that method will be made public, for the time being it is protected. 561 */ 562 public static function phpunit_disable() { 563 parent::disable(); 564 } 565 } 566 567 /** 568 * Cache PHPUnit specific Cache helper. 569 * 570 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 571 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 572 */ 573 class cache_phpunit_cache extends cache { 574 /** 575 * Make the changes which simulate a new request within the cache. 576 * This essentially resets currently held static values in the class, and increments the current timestamp. 577 */ 578 public static function simulate_new_request() { 579 self::$now += 0.1; 580 self::$purgetoken = null; 581 } 582 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body