Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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  namespace core_cache;
  18  
  19  use cache;
  20  use cache_application;
  21  use cache_config;
  22  use cache_config_disabled;
  23  use cache_config_testing;
  24  use cache_definition;
  25  use cache_disabled;
  26  use cache_factory;
  27  use cache_factory_disabled;
  28  use cache_helper;
  29  use cache_loader;
  30  use cache_phpunit_application;
  31  use cache_phpunit_cache;
  32  use cache_phpunit_dummy_object;
  33  use cache_phpunit_dummy_overrideclass;
  34  use cache_phpunit_factory;
  35  use cache_phpunit_request;
  36  use cache_phpunit_session;
  37  use cache_request;
  38  use cache_session;
  39  use cache_store;
  40  use cacheable_object_array;
  41  
  42  /**
  43   * PHPunit tests for the cache API
  44   *
  45   * This file is part of Moodle's cache API, affectionately called MUC.
  46   * It contains the components that are requried in order to use caching.
  47   *
  48   * @package    core
  49   * @category   cache
  50   * @copyright  2012 Sam Hemelryk
  51   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  52   * @coversDefaultClass \cache
  53   * @covers \cache
  54   */
  55  class cache_test extends \advanced_testcase {
  56  
  57      /**
  58       * Load required libraries and fixtures.
  59       */
  60      public static function setUpBeforeClass(): void {
  61          global $CFG;
  62  
  63          require_once($CFG->dirroot . '/cache/locallib.php');
  64          require_once($CFG->dirroot . '/cache/tests/fixtures/lib.php');
  65          require_once($CFG->dirroot . '/cache/tests/fixtures/cache_phpunit_dummy_datasource_versionable.php');
  66      }
  67  
  68      /**
  69       * Set things back to the default before each test.
  70       */
  71      public function setUp(): void {
  72          parent::setUp();
  73          cache_factory::reset();
  74          cache_config_testing::create_default_configuration();
  75      }
  76  
  77      /**
  78       * Final task is to reset the cache system
  79       */
  80      public static function tearDownAfterClass(): void {
  81          parent::tearDownAfterClass();
  82          cache_factory::reset();
  83      }
  84  
  85      /**
  86       * Returns the expected application cache store.
  87       * @return string
  88       */
  89      protected function get_expected_application_cache_store() {
  90          global $CFG;
  91          $expected = 'cachestore_file';
  92  
  93          // Verify if we are using any of the available ways to use a different application store within tests.
  94          if (defined('TEST_CACHE_USING_APPLICATION_STORE') && preg_match('#[a-zA-Z][a-zA-Z0-9_]*#', TEST_CACHE_USING_APPLICATION_STORE)) {
  95              // 1st way. Using some of the testing servers.
  96              $expected = 'cachestore_'.(string)TEST_CACHE_USING_APPLICATION_STORE;
  97  
  98          } else if (defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH && !empty($CFG->altcacheconfigpath)) {
  99              // 2nd way. Using an alternative configuration.
 100              $defaultstores = cache_helper::get_stores_suitable_for_mode_default();
 101              $instance = cache_config::instance();
 102              // Iterate over defined mode mappings until we get an application one not being the default.
 103              foreach ($instance->get_mode_mappings() as $mapping) {
 104                  // If the store is not for application mode, ignore.
 105                  if ($mapping['mode'] !== cache_store::MODE_APPLICATION) {
 106                      continue;
 107                  }
 108                  // If the store matches some default mapping store name, ignore.
 109                  if (array_key_exists($mapping['store'], $defaultstores) && !empty($defaultstores[$mapping['store']]['default'])) {
 110                      continue;
 111                  }
 112                  // Arrived here, have found an application mode store not being the default mapped one (file),
 113                  // that's the one we are using in the configuration for sure.
 114                  $expected = 'cachestore_'.$mapping['store'];
 115              }
 116          }
 117  
 118          return $expected;
 119      }
 120  
 121      /**
 122       * Tests cache configuration
 123       */
 124      public function test_cache_config() {
 125          global $CFG;
 126  
 127          if (defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH &&
 128              !empty($CFG->altcacheconfigpath)) {
 129              // We need to skip this test - it checks the default config structure, but very likely we arn't using the
 130              // default config structure here so theres no point in running the test.
 131              $this->markTestSkipped('Skipped testing default cache config structure as alt cache path is being used.');
 132          }
 133  
 134          if (defined('TEST_CACHE_USING_APPLICATION_STORE')) {
 135              // We need to skip this test - it checks the default config structure, but very likely we arn't using the
 136              // default config structure here because we are testing against an alternative application store.
 137              $this->markTestSkipped('Skipped testing default cache config structure as alt application store is being used.');
 138          }
 139  
 140          $instance = cache_config::instance();
 141          $this->assertInstanceOf(cache_config_testing::class, $instance);
 142  
 143          $this->assertTrue(cache_config_testing::config_file_exists());
 144  
 145          $stores = $instance->get_all_stores();
 146          $this->assertCount(3, $stores);
 147          foreach ($stores as $name => $store) {
 148              // Check its an array.
 149              $this->assertIsArray($store);
 150              // Check the name is the key.
 151              $this->assertEquals($name, $store['name']);
 152              // Check that it has been declared default.
 153              $this->assertTrue($store['default']);
 154              // Required attributes = name + plugin + configuration + modes + features.
 155              $this->assertArrayHasKey('name', $store);
 156              $this->assertArrayHasKey('plugin', $store);
 157              $this->assertArrayHasKey('configuration', $store);
 158              $this->assertArrayHasKey('modes', $store);
 159              $this->assertArrayHasKey('features', $store);
 160          }
 161  
 162          $modemappings = $instance->get_mode_mappings();
 163          $this->assertCount(3, $modemappings);
 164          $modes = array(
 165              cache_store::MODE_APPLICATION => false,
 166              cache_store::MODE_SESSION => false,
 167              cache_store::MODE_REQUEST => false,
 168          );
 169          foreach ($modemappings as $mapping) {
 170              // We expect 3 properties.
 171              $this->assertCount(3, $mapping);
 172              // Required attributes = mode + store.
 173              $this->assertArrayHasKey('mode', $mapping);
 174              $this->assertArrayHasKey('store', $mapping);
 175              // Record the mode.
 176              $modes[$mapping['mode']] = true;
 177          }
 178  
 179          // Must have the default 3 modes and no more.
 180          $this->assertCount(3, $mapping);
 181          foreach ($modes as $mode) {
 182              $this->assertTrue($mode);
 183          }
 184  
 185          $definitions = $instance->get_definitions();
 186          // The event invalidation definition is required for the cache API and must be there.
 187          $this->assertArrayHasKey('core/eventinvalidation', $definitions);
 188  
 189          $definitionmappings = $instance->get_definition_mappings();
 190          foreach ($definitionmappings as $mapping) {
 191              // Required attributes = definition + store.
 192              $this->assertArrayHasKey('definition', $mapping);
 193              $this->assertArrayHasKey('store', $mapping);
 194          }
 195      }
 196  
 197      /**
 198       * Tests for cache keys that would break on windows.
 199       */
 200      public function test_windows_nasty_keys() {
 201          $instance = cache_config_testing::instance();
 202          $instance->phpunit_add_definition('phpunit/windowskeytest', array(
 203              'mode' => cache_store::MODE_APPLICATION,
 204              'component' => 'phpunit',
 205              'area' => 'windowskeytest',
 206              'simplekeys' => true,
 207              'simpledata' => true
 208          ));
 209          $cache = cache::make('phpunit', 'windowskeytest');
 210          $this->assertTrue($cache->set('contest', 'test data 1'));
 211          $this->assertEquals('test data 1', $cache->get('contest'));
 212      }
 213  
 214      /**
 215       * Tests set_identifiers fails post cache creation.
 216       *
 217       * set_identifiers cannot be called after initial cache instantiation, as you need to create a difference cache.
 218       */
 219      public function test_set_identifiers() {
 220          $instance = cache_config_testing::instance();
 221          $instance->phpunit_add_definition('phpunit/identifier', array(
 222              'mode' => cache_store::MODE_APPLICATION,
 223              'component' => 'phpunit',
 224              'area' => 'identifier',
 225              'simplekeys' => true,
 226              'simpledata' => true,
 227              'staticacceleration' => true
 228          ));
 229          $cache = cache::make('phpunit', 'identifier', array('area'));
 230          $this->assertTrue($cache->set('contest', 'test data 1'));
 231          $this->assertEquals('test data 1', $cache->get('contest'));
 232  
 233          $this->expectException('coding_exception');
 234          $cache->set_identifiers(array());
 235      }
 236  
 237      /**
 238       * Tests the default application cache
 239       */
 240      public function test_default_application_cache() {
 241          $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'applicationtest');
 242          $this->assertInstanceOf(cache_application::class, $cache);
 243          $this->run_on_cache($cache);
 244  
 245          $instance = cache_config_testing::instance(true);
 246          $instance->phpunit_add_definition('phpunit/test_default_application_cache', array(
 247              'mode' => cache_store::MODE_APPLICATION,
 248              'component' => 'phpunit',
 249              'area' => 'test_default_application_cache',
 250              'staticacceleration' => true,
 251              'staticaccelerationsize' => 1
 252          ));
 253          $cache = cache::make('phpunit', 'test_default_application_cache');
 254          $this->assertInstanceOf(cache_application::class, $cache);
 255          $this->run_on_cache($cache);
 256      }
 257  
 258      /**
 259       * Tests the default session cache
 260       */
 261      public function test_default_session_cache() {
 262          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'applicationtest');
 263          $this->assertInstanceOf(cache_session::class, $cache);
 264          $this->run_on_cache($cache);
 265      }
 266  
 267      /**
 268       * Tests the default request cache
 269       */
 270      public function test_default_request_cache() {
 271          $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'phpunit', 'applicationtest');
 272          $this->assertInstanceOf(cache_request::class, $cache);
 273          $this->run_on_cache($cache);
 274      }
 275  
 276      /**
 277       * Tests using a cache system when there are no stores available (who knows what the admin did to achieve this).
 278       */
 279      public function test_on_cache_without_store() {
 280          $instance = cache_config_testing::instance(true);
 281          $instance->phpunit_add_definition('phpunit/nostoretest1', array(
 282              'mode' => cache_store::MODE_APPLICATION,
 283              'component' => 'phpunit',
 284              'area' => 'nostoretest1',
 285          ));
 286          $instance->phpunit_add_definition('phpunit/nostoretest2', array(
 287              'mode' => cache_store::MODE_APPLICATION,
 288              'component' => 'phpunit',
 289              'area' => 'nostoretest2',
 290              'staticacceleration' => true
 291          ));
 292          $instance->phpunit_remove_stores();
 293  
 294          $cache = cache::make('phpunit', 'nostoretest1');
 295          $this->run_on_cache($cache);
 296  
 297          $cache = cache::make('phpunit', 'nostoretest2');
 298          $this->run_on_cache($cache);
 299      }
 300  
 301      /**
 302       * Runs a standard series of access and use tests on a cache instance.
 303       *
 304       * This function is great because we can use it to ensure all of the loaders perform exactly the same way.
 305       *
 306       * @param cache_loader $cache
 307       */
 308      protected function run_on_cache(cache_loader $cache) {
 309          $key = 'contestkey';
 310          $datascalars = array('test data', null);
 311          $dataarray = array('contest' => 'data', 'part' => 'two');
 312          $dataobject = (object)$dataarray;
 313  
 314          foreach ($datascalars as $datascalar) {
 315              $this->assertTrue($cache->purge());
 316  
 317              // Check all read methods.
 318              $this->assertFalse($cache->get($key));
 319              $this->assertFalse($cache->has($key));
 320              $result = $cache->get_many(array($key));
 321              $this->assertCount(1, $result);
 322              $this->assertFalse(reset($result));
 323              $this->assertFalse($cache->has_any(array($key)));
 324              $this->assertFalse($cache->has_all(array($key)));
 325  
 326              // Set the data.
 327              $this->assertTrue($cache->set($key, $datascalar));
 328              // Setting it more than once should be permitted.
 329              $this->assertTrue($cache->set($key, $datascalar));
 330  
 331              // Recheck the read methods.
 332              $this->assertEquals($datascalar, $cache->get($key));
 333              $this->assertTrue($cache->has($key));
 334              $result = $cache->get_many(array($key));
 335              $this->assertCount(1, $result);
 336              $this->assertEquals($datascalar, reset($result));
 337              $this->assertTrue($cache->has_any(array($key)));
 338              $this->assertTrue($cache->has_all(array($key)));
 339  
 340              // Delete it.
 341              $this->assertTrue($cache->delete($key));
 342  
 343              // Check its gone.
 344              $this->assertFalse($cache->get($key));
 345              $this->assertFalse($cache->has($key));
 346          }
 347  
 348          // Test arrays.
 349          $this->assertTrue($cache->set($key, $dataarray));
 350          $this->assertEquals($dataarray, $cache->get($key));
 351  
 352          // Test objects.
 353          $this->assertTrue($cache->set($key, $dataobject));
 354          $this->assertEquals($dataobject, $cache->get($key));
 355  
 356          $starttime = microtime(true);
 357          $specobject = new cache_phpunit_dummy_object('red', 'blue', $starttime);
 358          $this->assertTrue($cache->set($key, $specobject));
 359          $result = $cache->get($key);
 360          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $result);
 361          $this->assertEquals('red_ptc_wfc', $result->property1);
 362          $this->assertEquals('blue_ptc_wfc', $result->property2);
 363          $this->assertGreaterThan($starttime, $result->propertytime);
 364  
 365          // Test array of objects.
 366          $specobject = new cache_phpunit_dummy_object('red', 'blue', $starttime);
 367          $data = new cacheable_object_array(array(
 368              clone($specobject),
 369              clone($specobject),
 370              clone($specobject))
 371          );
 372          $this->assertTrue($cache->set($key, $data));
 373          $result = $cache->get($key);
 374          $this->assertInstanceOf(cacheable_object_array::class, $result);
 375          $this->assertCount(3, $data);
 376          foreach ($result as $item) {
 377              $this->assertInstanceOf(cache_phpunit_dummy_object::class, $item);
 378              $this->assertEquals('red_ptc_wfc', $item->property1);
 379              $this->assertEquals('blue_ptc_wfc', $item->property2);
 380              // Ensure that wake from cache is called in all cases.
 381              $this->assertGreaterThan($starttime, $item->propertytime);
 382          }
 383  
 384          // Test set many.
 385          $cache->set_many(array('key1' => 'data1', 'key2' => 'data2', 'key3' => null));
 386          $this->assertEquals('data1', $cache->get('key1'));
 387          $this->assertEquals('data2', $cache->get('key2'));
 388          $this->assertEquals(null, $cache->get('key3'));
 389          $this->assertTrue($cache->delete('key1'));
 390          $this->assertTrue($cache->delete('key2'));
 391          $this->assertTrue($cache->delete('key3'));
 392  
 393          $cache->set_many(array(
 394              'key1' => array(1, 2, 3),
 395              'key2' => array(3, 2, 1),
 396          ));
 397          $this->assertIsArray($cache->get('key1'));
 398          $this->assertIsArray($cache->get('key2'));
 399          $this->assertCount(3, $cache->get('key1'));
 400          $this->assertCount(3, $cache->get('key2'));
 401          $this->assertIsArray($cache->get_many(array('key1', 'key2')));
 402          $this->assertCount(2, $cache->get_many(array('key1', 'key2')));
 403          $this->assertEquals(2, $cache->delete_many(array('key1', 'key2')));
 404  
 405          // Test delete many.
 406          $this->assertTrue($cache->set('key1', 'data1'));
 407          $this->assertTrue($cache->set('key2', 'data2'));
 408          $this->assertTrue($cache->set('key3', null));
 409  
 410          $this->assertEquals('data1', $cache->get('key1'));
 411          $this->assertEquals('data2', $cache->get('key2'));
 412          $this->assertEquals(null, $cache->get('key3'));
 413  
 414          $this->assertEquals(3, $cache->delete_many(array('key1', 'key2', 'key3')));
 415  
 416          $this->assertFalse($cache->get('key1'));
 417          $this->assertFalse($cache->get('key2'));
 418          $this->assertFalse($cache->get('key3'));
 419  
 420          // Quick reference test.
 421          $obj = new \stdClass;
 422          $obj->key = 'value';
 423          $ref =& $obj;
 424          $this->assertTrue($cache->set('obj', $obj));
 425  
 426          $obj->key = 'eulav';
 427          $var = $cache->get('obj');
 428          $this->assertInstanceOf(\stdClass::class, $var);
 429          $this->assertEquals('value', $var->key);
 430  
 431          $ref->key = 'eulav';
 432          $var = $cache->get('obj');
 433          $this->assertInstanceOf(\stdClass::class, $var);
 434          $this->assertEquals('value', $var->key);
 435  
 436          $this->assertTrue($cache->delete('obj'));
 437  
 438          // Deep reference test.
 439          $obj1 = new \stdClass;
 440          $obj1->key = 'value';
 441          $obj2 = new \stdClass;
 442          $obj2->key = 'test';
 443          $obj3 = new \stdClass;
 444          $obj3->key = 'pork';
 445          $obj1->subobj =& $obj2;
 446          $obj2->subobj =& $obj3;
 447          $this->assertTrue($cache->set('obj', $obj1));
 448  
 449          $obj1->key = 'eulav';
 450          $obj2->key = 'tset';
 451          $obj3->key = 'krop';
 452          $var = $cache->get('obj');
 453          $this->assertInstanceOf(\stdClass::class, $var);
 454          $this->assertEquals('value', $var->key);
 455          $this->assertInstanceOf(\stdClass::class, $var->subobj);
 456          $this->assertEquals('test', $var->subobj->key);
 457          $this->assertInstanceOf(\stdClass::class, $var->subobj->subobj);
 458          $this->assertEquals('pork', $var->subobj->subobj->key);
 459          $this->assertTrue($cache->delete('obj'));
 460  
 461          // Death reference test... basically we don't want this to die.
 462          $obj = new \stdClass;
 463          $obj->key = 'value';
 464          $obj->self =& $obj;
 465          $this->assertTrue($cache->set('obj', $obj));
 466          $var = $cache->get('obj');
 467          $this->assertInstanceOf(\stdClass::class, $var);
 468          $this->assertEquals('value', $var->key);
 469  
 470          // Reference test after retrieve.
 471          $obj = new \stdClass;
 472          $obj->key = 'value';
 473          $this->assertTrue($cache->set('obj', $obj));
 474  
 475          $var1 = $cache->get('obj');
 476          $this->assertInstanceOf(\stdClass::class, $var1);
 477          $this->assertEquals('value', $var1->key);
 478          $var1->key = 'eulav';
 479          $this->assertEquals('eulav', $var1->key);
 480  
 481          $var2 = $cache->get('obj');
 482          $this->assertInstanceOf(\stdClass::class, $var2);
 483          $this->assertEquals('value', $var2->key);
 484  
 485          $this->assertTrue($cache->delete('obj'));
 486  
 487          // Death reference test on get_many... basically we don't want this to die.
 488          $obj = new \stdClass;
 489          $obj->key = 'value';
 490          $obj->self =& $obj;
 491          $this->assertEquals(1, $cache->set_many(array('obj' => $obj)));
 492          $var = $cache->get_many(array('obj'));
 493          $this->assertInstanceOf(\stdClass::class, $var['obj']);
 494          $this->assertEquals('value', $var['obj']->key);
 495  
 496          // Reference test after retrieve.
 497          $obj = new \stdClass;
 498          $obj->key = 'value';
 499          $this->assertEquals(1, $cache->set_many(array('obj' => $obj)));
 500  
 501          $var1 = $cache->get_many(array('obj'));
 502          $this->assertInstanceOf(\stdClass::class, $var1['obj']);
 503          $this->assertEquals('value', $var1['obj']->key);
 504          $var1['obj']->key = 'eulav';
 505          $this->assertEquals('eulav', $var1['obj']->key);
 506  
 507          $var2 = $cache->get_many(array('obj'));
 508          $this->assertInstanceOf(\stdClass::class, $var2['obj']);
 509          $this->assertEquals('value', $var2['obj']->key);
 510  
 511          $this->assertTrue($cache->delete('obj'));
 512  
 513          // Test strictness exceptions.
 514          try {
 515              $cache->get('exception', MUST_EXIST);
 516              $this->fail('Exception expected from cache::get using MUST_EXIST');
 517          } catch (\Exception $e) {
 518              $this->assertTrue(true);
 519          }
 520          try {
 521              $cache->get_many(array('exception1', 'exception2'), MUST_EXIST);
 522              $this->fail('Exception expected from cache::get_many using MUST_EXIST');
 523          } catch (\Exception $e) {
 524              $this->assertTrue(true);
 525          }
 526          $cache->set('test', 'test');
 527          try {
 528              $cache->get_many(array('test', 'exception'), MUST_EXIST);
 529              $this->fail('Exception expected from cache::get_many using MUST_EXIST');
 530          } catch (\Exception $e) {
 531              $this->assertTrue(true);
 532          }
 533      }
 534  
 535      /**
 536       * Tests a definition using a data loader
 537       */
 538      public function test_definition_data_loader() {
 539          $instance = cache_config_testing::instance(true);
 540          $instance->phpunit_add_definition('phpunit/datasourcetest', array(
 541              'mode' => cache_store::MODE_APPLICATION,
 542              'component' => 'phpunit',
 543              'area' => 'datasourcetest',
 544              'datasource' => 'cache_phpunit_dummy_datasource',
 545              'datasourcefile' => 'cache/tests/fixtures/lib.php'
 546          ));
 547  
 548          $cache = cache::make('phpunit', 'datasourcetest');
 549          $this->assertInstanceOf(cache_application::class, $cache);
 550  
 551          // Purge it to be sure.
 552          $this->assertTrue($cache->purge());
 553          // It won't be there yet.
 554          $this->assertFalse($cache->has('Test'));
 555          // It should load it ;).
 556          $this->assertTrue($cache->has('Test', true));
 557  
 558          // Purge it to be sure.
 559          $this->assertTrue($cache->purge());
 560          $this->assertEquals('Test has no value really.', $cache->get('Test'));
 561  
 562          // Test multiple values.
 563          $this->assertTrue($cache->purge());
 564          $this->assertTrue($cache->set('b', 'B'));
 565          $result = $cache->get_many(array('a', 'b', 'c'));
 566          $this->assertIsArray($result);
 567          $this->assertCount(3, $result);
 568          $this->assertArrayHasKey('a', $result);
 569          $this->assertArrayHasKey('b', $result);
 570          $this->assertArrayHasKey('c', $result);
 571          $this->assertEquals('a has no value really.', $result['a']);
 572          $this->assertEquals('B', $result['b']);
 573          $this->assertEquals('c has no value really.', $result['c']);
 574      }
 575  
 576      /**
 577       * Tests a definition using a data loader with versioned keys.
 578       *
 579       * @covers ::get_versioned
 580       * @covers ::set_versioned
 581       */
 582      public function test_definition_data_loader_versioned() {
 583          // Create two definitions, one using a non-versionable data source and the other using
 584          // a versionable one.
 585          $instance = cache_config_testing::instance(true);
 586          $instance->phpunit_add_definition('phpunit/datasourcetest1', array(
 587              'mode' => cache_store::MODE_APPLICATION,
 588              'component' => 'phpunit',
 589              'area' => 'datasourcetest1',
 590              'datasource' => 'cache_phpunit_dummy_datasource',
 591              'datasourcefile' => 'cache/tests/fixtures/lib.php'
 592          ));
 593          $instance->phpunit_add_definition('phpunit/datasourcetest2', array(
 594              'mode' => cache_store::MODE_APPLICATION,
 595              'component' => 'phpunit',
 596              'area' => 'datasourcetest2',
 597              'datasource' => 'cache_phpunit_dummy_datasource_versionable',
 598              'datasourcefile' => 'cache/tests/fixtures/lib.php'
 599          ));
 600  
 601          // The first data source works for normal 'get'.
 602          $cache1 = cache::make('phpunit', 'datasourcetest1');
 603          $this->assertEquals('Frog has no value really.', $cache1->get('Frog'));
 604  
 605          // But it doesn't work for get_versioned.
 606          try {
 607              $cache1->get_versioned('zombie', 1);
 608              $this->fail();
 609          } catch (\coding_exception $e) {
 610              $this->assertStringContainsString('Data source is not versionable', $e->getMessage());
 611          }
 612  
 613          // The second data source works for get_versioned. Set up the datasource first.
 614          $cache2 = cache::make('phpunit', 'datasourcetest2');
 615  
 616          $datasource = \cache_phpunit_dummy_datasource_versionable::get_last_instance();
 617          $datasource->has_value('frog', 3, 'Kermit');
 618  
 619          // Check data with no value.
 620          $this->assertFalse($cache2->get_versioned('zombie', 1));
 621  
 622          // Check data with value in datastore of required version.
 623          $result = $cache2->get_versioned('frog', 3, IGNORE_MISSING, $actualversion);
 624          $this->assertEquals('Kermit', $result);
 625          $this->assertEquals(3, $actualversion);
 626  
 627          // Check when the datastore doesn't have required version.
 628          $this->assertFalse($cache2->get_versioned('frog', 4));
 629      }
 630  
 631      /**
 632       * Tests a definition using an overridden loader
 633       */
 634      public function test_definition_overridden_loader() {
 635          $instance = cache_config_testing::instance(true);
 636          $instance->phpunit_add_definition('phpunit/overridetest', array(
 637              'mode' => cache_store::MODE_APPLICATION,
 638              'component' => 'phpunit',
 639              'area' => 'overridetest',
 640              'overrideclass' => 'cache_phpunit_dummy_overrideclass',
 641              'overrideclassfile' => 'cache/tests/fixtures/lib.php'
 642          ));
 643          $cache = cache::make('phpunit', 'overridetest');
 644          $this->assertInstanceOf(cache_phpunit_dummy_overrideclass::class, $cache);
 645          $this->assertInstanceOf(cache_application::class, $cache);
 646          // Purge it to be sure.
 647          $this->assertTrue($cache->purge());
 648          // It won't be there yet.
 649          $this->assertFalse($cache->has('Test'));
 650          // Add it.
 651          $this->assertTrue($cache->set('Test', 'Test has no value really.'));
 652          // Check its there.
 653          $this->assertEquals('Test has no value really.', $cache->get('Test'));
 654      }
 655  
 656      /**
 657       * Test the mappingsonly setting.
 658       */
 659      public function test_definition_mappings_only() {
 660          /** @var cache_config_testing $instance */
 661          $instance = cache_config_testing::instance(true);
 662          $instance->phpunit_add_definition('phpunit/mappingsonly', array(
 663              'mode' => cache_store::MODE_APPLICATION,
 664              'component' => 'phpunit',
 665              'area' => 'mappingsonly',
 666              'mappingsonly' => true
 667          ), false);
 668          $instance->phpunit_add_definition('phpunit/nonmappingsonly', array(
 669              'mode' => cache_store::MODE_APPLICATION,
 670              'component' => 'phpunit',
 671              'area' => 'nonmappingsonly',
 672              'mappingsonly' => false
 673          ), false);
 674  
 675          $cacheonly = cache::make('phpunit', 'mappingsonly');
 676          $this->assertInstanceOf(cache_application::class, $cacheonly);
 677          $this->assertEquals('cachestore_dummy', $cacheonly->phpunit_get_store_class());
 678  
 679          $expected = $this->get_expected_application_cache_store();
 680          $cachenon = cache::make('phpunit', 'nonmappingsonly');
 681          $this->assertInstanceOf(cache_application::class, $cachenon);
 682          $this->assertEquals($expected, $cachenon->phpunit_get_store_class());
 683      }
 684  
 685      /**
 686       * Test a very basic definition.
 687       */
 688      public function test_definition() {
 689          $instance = cache_config_testing::instance();
 690          $instance->phpunit_add_definition('phpunit/test', array(
 691              'mode' => cache_store::MODE_APPLICATION,
 692              'component' => 'phpunit',
 693              'area' => 'test',
 694          ));
 695          $cache = cache::make('phpunit', 'test');
 696  
 697          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 698          $this->assertEquals('test data 1', $cache->get('testkey1'));
 699          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 700          $this->assertEquals('test data 2', $cache->get('testkey2'));
 701      }
 702  
 703      /**
 704       * Test a definition using the simple keys.
 705       */
 706      public function test_definition_simplekeys() {
 707          $instance = cache_config_testing::instance();
 708          $instance->phpunit_add_definition('phpunit/simplekeytest', array(
 709              'mode' => cache_store::MODE_APPLICATION,
 710              'component' => 'phpunit',
 711              'area' => 'simplekeytest',
 712              'simplekeys' => true
 713          ));
 714          $cache = cache::make('phpunit', 'simplekeytest');
 715  
 716          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 717          $this->assertEquals('test data 1', $cache->get('testkey1'));
 718          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 719          $this->assertEquals('test data 2', $cache->get('testkey2'));
 720  
 721          $cache->purge();
 722  
 723          $this->assertTrue($cache->set('1', 'test data 1'));
 724          $this->assertEquals('test data 1', $cache->get('1'));
 725          $this->assertTrue($cache->set('2', 'test data 2'));
 726          $this->assertEquals('test data 2', $cache->get('2'));
 727      }
 728  
 729      /**
 730       * Test a negative TTL on an application cache.
 731       */
 732      public function test_application_ttl_negative() {
 733          $instance = cache_config_testing::instance(true);
 734          $instance->phpunit_add_definition('phpunit/ttltest', array(
 735              'mode' => cache_store::MODE_APPLICATION,
 736              'component' => 'phpunit',
 737              'area' => 'ttltest',
 738              'ttl' => -86400 // Set to a day in the past to be extra sure.
 739          ));
 740          $cache = cache::make('phpunit', 'ttltest');
 741          $this->assertInstanceOf(cache_application::class, $cache);
 742  
 743          // Purge it to be sure.
 744          $this->assertTrue($cache->purge());
 745          // It won't be there yet.
 746          $this->assertFalse($cache->has('Test'));
 747          // Set it now.
 748          $this->assertTrue($cache->set('Test', 'Test'));
 749          // Check its not there.
 750          $this->assertFalse($cache->has('Test'));
 751          // Double check by trying to get it.
 752          $this->assertFalse($cache->get('Test'));
 753  
 754          // Test with multiple keys.
 755          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
 756          $result = $cache->get_many(array('a', 'b', 'c'));
 757          $this->assertIsArray($result);
 758          $this->assertCount(3, $result);
 759          $this->assertArrayHasKey('a', $result);
 760          $this->assertArrayHasKey('b', $result);
 761          $this->assertArrayHasKey('c', $result);
 762          $this->assertFalse($result['a']);
 763          $this->assertFalse($result['b']);
 764          $this->assertFalse($result['c']);
 765  
 766          // Test with multiple keys including missing ones.
 767          $result = $cache->get_many(array('a', 'c', 'e'));
 768          $this->assertIsArray($result);
 769          $this->assertCount(3, $result);
 770          $this->assertArrayHasKey('a', $result);
 771          $this->assertArrayHasKey('c', $result);
 772          $this->assertArrayHasKey('e', $result);
 773          $this->assertFalse($result['a']);
 774          $this->assertFalse($result['c']);
 775          $this->assertFalse($result['e']);
 776      }
 777  
 778      /**
 779       * Test a positive TTL on an application cache.
 780       */
 781      public function test_application_ttl_positive() {
 782          $instance = cache_config_testing::instance(true);
 783          $instance->phpunit_add_definition('phpunit/ttltest', array(
 784              'mode' => cache_store::MODE_APPLICATION,
 785              'component' => 'phpunit',
 786              'area' => 'ttltest',
 787              'ttl' => 86400 // Set to a day in the future to be extra sure.
 788          ));
 789          $cache = cache::make('phpunit', 'ttltest');
 790          $this->assertInstanceOf(cache_application::class, $cache);
 791  
 792          // Purge it to be sure.
 793          $this->assertTrue($cache->purge());
 794          // It won't be there yet.
 795          $this->assertFalse($cache->has('Test'));
 796          // Set it now.
 797          $this->assertTrue($cache->set('Test', 'Test'));
 798          // Check its there.
 799          $this->assertTrue($cache->has('Test'));
 800          // Double check by trying to get it.
 801          $this->assertEquals('Test', $cache->get('Test'));
 802  
 803          // Test with multiple keys.
 804          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
 805          $result = $cache->get_many(array('a', 'b', 'c'));
 806          $this->assertIsArray($result);
 807          $this->assertCount(3, $result);
 808          $this->assertArrayHasKey('a', $result);
 809          $this->assertArrayHasKey('b', $result);
 810          $this->assertArrayHasKey('c', $result);
 811          $this->assertEquals('A', $result['a']);
 812          $this->assertEquals('B', $result['b']);
 813          $this->assertEquals('C', $result['c']);
 814  
 815          // Test with multiple keys including missing ones.
 816          $result = $cache->get_many(array('a', 'c', 'e'));
 817          $this->assertIsArray($result);
 818          $this->assertCount(3, $result);
 819          $this->assertArrayHasKey('a', $result);
 820          $this->assertArrayHasKey('c', $result);
 821          $this->assertArrayHasKey('e', $result);
 822          $this->assertEquals('A', $result['a']);
 823          $this->assertEquals('C', $result['c']);
 824          $this->assertEquals(false, $result['e']);
 825      }
 826  
 827      /**
 828       * Test a negative TTL on an session cache.
 829       */
 830      public function test_session_ttl_positive() {
 831          $instance = cache_config_testing::instance(true);
 832          $instance->phpunit_add_definition('phpunit/ttltest', array(
 833              'mode' => cache_store::MODE_SESSION,
 834              'component' => 'phpunit',
 835              'area' => 'ttltest',
 836              'ttl' => 86400 // Set to a day in the future to be extra sure.
 837          ));
 838          $cache = cache::make('phpunit', 'ttltest');
 839          $this->assertInstanceOf(cache_session::class, $cache);
 840  
 841          // Purge it to be sure.
 842          $this->assertTrue($cache->purge());
 843          // It won't be there yet.
 844          $this->assertFalse($cache->has('Test'));
 845          // Set it now.
 846          $this->assertTrue($cache->set('Test', 'Test'));
 847          // Check its there.
 848          $this->assertTrue($cache->has('Test'));
 849          // Double check by trying to get it.
 850          $this->assertEquals('Test', $cache->get('Test'));
 851  
 852          // Test with multiple keys.
 853          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
 854          $result = $cache->get_many(array('a', 'b', 'c'));
 855          $this->assertIsArray($result);
 856          $this->assertCount(3, $result);
 857          $this->assertArrayHasKey('a', $result);
 858          $this->assertArrayHasKey('b', $result);
 859          $this->assertArrayHasKey('c', $result);
 860          $this->assertEquals('A', $result['a']);
 861          $this->assertEquals('B', $result['b']);
 862          $this->assertEquals('C', $result['c']);
 863  
 864          // Test with multiple keys including missing ones.
 865          $result = $cache->get_many(array('a', 'c', 'e'));
 866          $this->assertIsArray($result);
 867          $this->assertCount(3, $result);
 868          $this->assertArrayHasKey('a', $result);
 869          $this->assertArrayHasKey('c', $result);
 870          $this->assertArrayHasKey('e', $result);
 871          $this->assertEquals('A', $result['a']);
 872          $this->assertEquals('C', $result['c']);
 873          $this->assertEquals(false, $result['e']);
 874      }
 875  
 876      /**
 877       * Tests manual locking operations on an application cache
 878       */
 879      public function test_application_manual_locking() {
 880          $instance = cache_config_testing::instance();
 881          $instance->phpunit_add_definition('phpunit/lockingtest', array(
 882              'mode' => cache_store::MODE_APPLICATION,
 883              'component' => 'phpunit',
 884              'area' => 'lockingtest'
 885          ));
 886          $cache1 = cache::make('phpunit', 'lockingtest');
 887          $cache2 = clone($cache1);
 888  
 889          $this->assertTrue($cache1->set('testkey', 'test data'));
 890          $this->assertTrue($cache2->set('testkey', 'test data'));
 891  
 892          $this->assertTrue($cache1->acquire_lock('testkey'));
 893          $this->assertFalse($cache2->acquire_lock('testkey'));
 894  
 895          $this->assertTrue($cache1->check_lock_state('testkey'));
 896          $this->assertFalse($cache2->check_lock_state('testkey'));
 897  
 898          $this->assertTrue($cache1->release_lock('testkey'));
 899          $this->assertFalse($cache2->release_lock('testkey'));
 900  
 901          $this->assertTrue($cache1->set('testkey', 'test data'));
 902          $this->assertTrue($cache2->set('testkey', 'test data'));
 903      }
 904  
 905      /**
 906       * Tests application cache event invalidation
 907       */
 908      public function test_application_event_invalidation() {
 909          $instance = cache_config_testing::instance();
 910          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
 911              'mode' => cache_store::MODE_APPLICATION,
 912              'component' => 'phpunit',
 913              'area' => 'eventinvalidationtest',
 914              'invalidationevents' => array(
 915                  'crazyevent'
 916              )
 917          ));
 918          $cache = cache::make('phpunit', 'eventinvalidationtest');
 919  
 920          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 921          $this->assertEquals('test data 1', $cache->get('testkey1'));
 922          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 923          $this->assertEquals('test data 2', $cache->get('testkey2'));
 924  
 925          // Test invalidating a single entry.
 926          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
 927  
 928          $this->assertFalse($cache->get('testkey1'));
 929          $this->assertEquals('test data 2', $cache->get('testkey2'));
 930  
 931          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 932  
 933          // Test invalidating both entries.
 934          cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
 935  
 936          $this->assertFalse($cache->get('testkey1'));
 937          $this->assertFalse($cache->get('testkey2'));
 938      }
 939  
 940      /**
 941       * Tests session cache event invalidation
 942       */
 943      public function test_session_event_invalidation() {
 944          $instance = cache_config_testing::instance();
 945          $instance->phpunit_add_definition('phpunit/test_session_event_invalidation', array(
 946              'mode' => cache_store::MODE_SESSION,
 947              'component' => 'phpunit',
 948              'area' => 'test_session_event_invalidation',
 949              'invalidationevents' => array(
 950                  'crazyevent'
 951              )
 952          ));
 953          $cache = cache::make('phpunit', 'test_session_event_invalidation');
 954          $this->assertInstanceOf(cache_session::class, $cache);
 955  
 956          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 957          $this->assertEquals('test data 1', $cache->get('testkey1'));
 958          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 959          $this->assertEquals('test data 2', $cache->get('testkey2'));
 960  
 961          // Test invalidating a single entry.
 962          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
 963  
 964          $this->assertFalse($cache->get('testkey1'));
 965          $this->assertEquals('test data 2', $cache->get('testkey2'));
 966  
 967          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 968  
 969          // Test invalidating both entries.
 970          cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
 971  
 972          $this->assertFalse($cache->get('testkey1'));
 973          $this->assertFalse($cache->get('testkey2'));
 974      }
 975  
 976      /**
 977       * Tests application cache definition invalidation
 978       */
 979      public function test_application_definition_invalidation() {
 980          $instance = cache_config_testing::instance();
 981          $instance->phpunit_add_definition('phpunit/definitioninvalidation', array(
 982              'mode' => cache_store::MODE_APPLICATION,
 983              'component' => 'phpunit',
 984              'area' => 'definitioninvalidation'
 985          ));
 986          $cache = cache::make('phpunit', 'definitioninvalidation');
 987          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 988          $this->assertEquals('test data 1', $cache->get('testkey1'));
 989          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 990          $this->assertEquals('test data 2', $cache->get('testkey2'));
 991  
 992          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), 'testkey1');
 993  
 994          $this->assertFalse($cache->get('testkey1'));
 995          $this->assertEquals('test data 2', $cache->get('testkey2'));
 996  
 997          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 998  
 999          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1'));
1000  
1001          $this->assertFalse($cache->get('testkey1'));
1002          $this->assertEquals('test data 2', $cache->get('testkey2'));
1003  
1004          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1005  
1006          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1', 'testkey2'));
1007  
1008          $this->assertFalse($cache->get('testkey1'));
1009          $this->assertFalse($cache->get('testkey2'));
1010      }
1011  
1012      /**
1013       * Tests session cache definition invalidation
1014       */
1015      public function test_session_definition_invalidation() {
1016          $instance = cache_config_testing::instance();
1017          $instance->phpunit_add_definition('phpunit/test_session_definition_invalidation', array(
1018              'mode' => cache_store::MODE_SESSION,
1019              'component' => 'phpunit',
1020              'area' => 'test_session_definition_invalidation'
1021          ));
1022          $cache = cache::make('phpunit', 'test_session_definition_invalidation');
1023          $this->assertInstanceOf(cache_session::class, $cache);
1024          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1025          $this->assertEquals('test data 1', $cache->get('testkey1'));
1026          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1027          $this->assertEquals('test data 2', $cache->get('testkey2'));
1028  
1029          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(), 'testkey1');
1030  
1031          $this->assertFalse($cache->get('testkey1'));
1032          $this->assertEquals('test data 2', $cache->get('testkey2'));
1033  
1034          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1035  
1036          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
1037                  array('testkey1'));
1038  
1039          $this->assertFalse($cache->get('testkey1'));
1040          $this->assertEquals('test data 2', $cache->get('testkey2'));
1041  
1042          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1043  
1044          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
1045                  array('testkey1', 'testkey2'));
1046  
1047          $this->assertFalse($cache->get('testkey1'));
1048          $this->assertFalse($cache->get('testkey2'));
1049      }
1050  
1051      /**
1052       * Tests application cache event invalidation over a distributed setup.
1053       */
1054      public function test_distributed_application_event_invalidation() {
1055          global $CFG;
1056          // This is going to be an intense wee test.
1057          // We need to add data the to cache, invalidate it by event, manually force it back without MUC knowing to simulate a
1058          // disconnected/distributed setup (think load balanced server using local cache), instantiate the cache again and finally
1059          // check that it is not picked up.
1060          $instance = cache_config_testing::instance();
1061          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1062              'mode' => cache_store::MODE_APPLICATION,
1063              'component' => 'phpunit',
1064              'area' => 'eventinvalidationtest',
1065              'simplekeys' => true,
1066              'simpledata' => true,
1067              'invalidationevents' => array(
1068                  'crazyevent'
1069              )
1070          ));
1071          $cache = cache::make('phpunit', 'eventinvalidationtest');
1072          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1073          $this->assertEquals('test data 1', $cache->get('testkey1'));
1074  
1075          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
1076  
1077          $this->assertFalse($cache->get('testkey1'));
1078  
1079          // OK data added, data invalidated, and invalidation time has been set.
1080          // Now we need to manually add back the data and adjust the invalidation time.
1081          $hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
1082          $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las-cache/lastinvalidation-$hash.cache";
1083          // Make sure the file is correct.
1084          $this->assertTrue(file_exists($timefile));
1085          $timecont = serialize(cache::now(true) - 60); // Back 60sec in the past to force it to re-invalidate.
1086          make_writable_directory(dirname($timefile));
1087          file_put_contents($timefile, $timecont);
1088          $this->assertTrue(file_exists($timefile));
1089  
1090          $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes-cache/testkey1-$hash.cache";
1091          $datacont = serialize("test data 1");
1092          make_writable_directory(dirname($datafile));
1093          file_put_contents($datafile, $datacont);
1094          $this->assertTrue(file_exists($datafile));
1095  
1096          // Test 1: Rebuild without the event and test its there.
1097          cache_factory::reset();
1098          $instance = cache_config_testing::instance();
1099          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1100              'mode' => cache_store::MODE_APPLICATION,
1101              'component' => 'phpunit',
1102              'area' => 'eventinvalidationtest',
1103              'simplekeys' => true,
1104              'simpledata' => true,
1105          ));
1106          $cache = cache::make('phpunit', 'eventinvalidationtest');
1107          $this->assertEquals('test data 1', $cache->get('testkey1'));
1108  
1109          // Test 2: Rebuild and test the invalidation of the event via the invalidation cache.
1110          cache_factory::reset();
1111  
1112          $instance = cache_config_testing::instance();
1113          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1114              'mode' => cache_store::MODE_APPLICATION,
1115              'component' => 'phpunit',
1116              'area' => 'eventinvalidationtest',
1117              'simplekeys' => true,
1118              'simpledata' => true,
1119              'invalidationevents' => array(
1120                  'crazyevent'
1121              )
1122          ));
1123  
1124          $cache = cache::make('phpunit', 'eventinvalidationtest');
1125          $this->assertFalse($cache->get('testkey1'));
1126  
1127          // Test 3: Verify that an existing lastinvalidation cache file is updated when needed.
1128  
1129          // Make a new cache class.  This should should invalidate testkey2.
1130          $cache = cache::make('phpunit', 'eventinvalidationtest');
1131  
1132          // Invalidation token should have been reset.
1133          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1134  
1135          // Set testkey2 data.
1136          $cache->set('testkey2', 'test data 2');
1137  
1138          // Backdate the event invalidation time by 30 seconds.
1139          $invalidationcache = cache::make('core', 'eventinvalidation');
1140          $invalidationcache->set('crazyevent', array('testkey2' => cache::now() - 30));
1141  
1142          // Lastinvalidation should already be cache::now().
1143          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1144  
1145          // Set it to 15 seconds ago so that we know if it changes.
1146          $pasttime = cache::now(true) - 15;
1147          $cache->set('lastinvalidation', $pasttime);
1148  
1149          // Make a new cache class.  This should not invalidate anything.
1150          cache_factory::instance()->reset_cache_instances();
1151          $cache = cache::make('phpunit', 'eventinvalidationtest');
1152  
1153          // Lastinvalidation shouldn't change since it was already newer than invalidation event.
1154          $this->assertEquals($pasttime, $cache->get('lastinvalidation'));
1155  
1156          // Now set the event invalidation to newer than the lastinvalidation time.
1157          $invalidationcache->set('crazyevent', array('testkey2' => cache::now() - 5));
1158          // Make a new cache class.  This should should invalidate testkey2.
1159          cache_factory::instance()->reset_cache_instances();
1160          $cache = cache::make('phpunit', 'eventinvalidationtest');
1161          // Lastinvalidation timestamp should have updated to cache::now().
1162          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1163  
1164          // Now simulate a purge_by_event 5 seconds ago.
1165          $invalidationcache = cache::make('core', 'eventinvalidation');
1166          $invalidationcache->set('crazyevent', array('purged' => cache::now(true) - 5));
1167          // Set our lastinvalidation timestamp to 15 seconds ago.
1168          $cache->set('lastinvalidation', cache::now(true) - 15);
1169          // Make a new cache class.  This should invalidate the cache.
1170          cache_factory::instance()->reset_cache_instances();
1171          $cache = cache::make('phpunit', 'eventinvalidationtest');
1172          // Lastinvalidation timestamp should have updated to cache::now().
1173          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1174  
1175      }
1176  
1177      /**
1178       * Tests application cache event purge
1179       */
1180      public function test_application_event_purge() {
1181          $instance = cache_config_testing::instance();
1182          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
1183              'mode' => cache_store::MODE_APPLICATION,
1184              'component' => 'phpunit',
1185              'area' => 'eventpurgetest',
1186              'invalidationevents' => array(
1187                  'crazyevent'
1188              )
1189          ));
1190          $instance->phpunit_add_definition('phpunit/eventpurgetestaccelerated', array(
1191              'mode' => cache_store::MODE_APPLICATION,
1192              'component' => 'phpunit',
1193              'area' => 'eventpurgetestaccelerated',
1194              'staticacceleration' => true,
1195              'invalidationevents' => array(
1196                  'crazyevent'
1197              )
1198          ));
1199          $cache = cache::make('phpunit', 'eventpurgetest');
1200  
1201          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1202          $this->assertEquals('test data 1', $cache->get('testkey1'));
1203          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1204          $this->assertEquals('test data 2', $cache->get('testkey2'));
1205  
1206          // Purge the event.
1207          cache_helper::purge_by_event('crazyevent');
1208  
1209          // Check things have been removed.
1210          $this->assertFalse($cache->get('testkey1'));
1211          $this->assertFalse($cache->get('testkey2'));
1212  
1213          // Now test the static acceleration array.
1214          $cache = cache::make('phpunit', 'eventpurgetestaccelerated');
1215          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1216          $this->assertEquals('test data 1', $cache->get('testkey1'));
1217          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1218          $this->assertEquals('test data 2', $cache->get('testkey2'));
1219  
1220          // Purge the event.
1221          cache_helper::purge_by_event('crazyevent');
1222  
1223          // Check things have been removed.
1224          $this->assertFalse($cache->get('testkey1'));
1225          $this->assertFalse($cache->get('testkey2'));
1226      }
1227  
1228      /**
1229       * Tests session cache event purge
1230       */
1231      public function test_session_event_purge() {
1232          $instance = cache_config_testing::instance();
1233          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
1234              'mode' => cache_store::MODE_SESSION,
1235              'component' => 'phpunit',
1236              'area' => 'eventpurgetest',
1237              'invalidationevents' => array(
1238                  'crazyevent'
1239              )
1240          ));
1241          $instance->phpunit_add_definition('phpunit/eventpurgetestaccelerated', array(
1242              'mode' => cache_store::MODE_SESSION,
1243              'component' => 'phpunit',
1244              'area' => 'eventpurgetestaccelerated',
1245              'staticacceleration' => true,
1246              'invalidationevents' => array(
1247                  'crazyevent'
1248              )
1249          ));
1250          $cache = cache::make('phpunit', 'eventpurgetest');
1251  
1252          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1253          $this->assertEquals('test data 1', $cache->get('testkey1'));
1254          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1255          $this->assertEquals('test data 2', $cache->get('testkey2'));
1256  
1257          // Purge the event.
1258          cache_helper::purge_by_event('crazyevent');
1259  
1260          // Check things have been removed.
1261          $this->assertFalse($cache->get('testkey1'));
1262          $this->assertFalse($cache->get('testkey2'));
1263  
1264          // Now test the static acceleration array.
1265          $cache = cache::make('phpunit', 'eventpurgetestaccelerated');
1266          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1267          $this->assertEquals('test data 1', $cache->get('testkey1'));
1268          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1269          $this->assertEquals('test data 2', $cache->get('testkey2'));
1270  
1271          // Purge the event.
1272          cache_helper::purge_by_event('crazyevent');
1273  
1274          // Check things have been removed.
1275          $this->assertFalse($cache->get('testkey1'));
1276          $this->assertFalse($cache->get('testkey2'));
1277      }
1278  
1279      /**
1280       * Tests application cache definition purge
1281       */
1282      public function test_application_definition_purge() {
1283          $instance = cache_config_testing::instance();
1284          $instance->phpunit_add_definition('phpunit/definitionpurgetest', array(
1285              'mode' => cache_store::MODE_APPLICATION,
1286              'component' => 'phpunit',
1287              'area' => 'definitionpurgetest',
1288              'invalidationevents' => array(
1289                  'crazyevent'
1290              )
1291          ));
1292          $cache = cache::make('phpunit', 'definitionpurgetest');
1293  
1294          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1295          $this->assertEquals('test data 1', $cache->get('testkey1'));
1296          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1297          $this->assertEquals('test data 2', $cache->get('testkey2'));
1298  
1299          // Purge the event.
1300          cache_helper::purge_by_definition('phpunit', 'definitionpurgetest');
1301  
1302          // Check things have been removed.
1303          $this->assertFalse($cache->get('testkey1'));
1304          $this->assertFalse($cache->get('testkey2'));
1305      }
1306  
1307      /**
1308       * Test the use of an alt path.
1309       * If we can generate a config instance we are done :)
1310       */
1311      public function test_alt_cache_path() {
1312          global $CFG;
1313          if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
1314              $this->markTestSkipped('Skipped testing alt cache path as it is already being used.');
1315          }
1316          $this->resetAfterTest();
1317          $CFG->altcacheconfigpath = $CFG->dataroot.'/cache/altcacheconfigpath';
1318          $instance = cache_config_testing::instance();
1319          $this->assertInstanceOf(cache_config::class, $instance);
1320      }
1321  
1322      /**
1323       * Test disabling the cache stores.
1324       */
1325      public function test_disable_stores() {
1326          $instance = cache_config_testing::instance();
1327          $instance->phpunit_add_definition('phpunit/disabletest1', array(
1328              'mode' => cache_store::MODE_APPLICATION,
1329              'component' => 'phpunit',
1330              'area' => 'disabletest1'
1331          ));
1332          $instance->phpunit_add_definition('phpunit/disabletest2', array(
1333              'mode' => cache_store::MODE_SESSION,
1334              'component' => 'phpunit',
1335              'area' => 'disabletest2'
1336          ));
1337          $instance->phpunit_add_definition('phpunit/disabletest3', array(
1338              'mode' => cache_store::MODE_REQUEST,
1339              'component' => 'phpunit',
1340              'area' => 'disabletest3'
1341          ));
1342  
1343          $caches = array(
1344              'disabletest1' => cache::make('phpunit', 'disabletest1'),
1345              'disabletest2' => cache::make('phpunit', 'disabletest2'),
1346              'disabletest3' => cache::make('phpunit', 'disabletest3')
1347          );
1348  
1349          $this->assertInstanceOf(cache_phpunit_application::class, $caches['disabletest1']);
1350          $this->assertInstanceOf(cache_phpunit_session::class, $caches['disabletest2']);
1351          $this->assertInstanceOf(cache_phpunit_request::class, $caches['disabletest3']);
1352  
1353          $this->assertEquals('cachestore_file', $caches['disabletest1']->phpunit_get_store_class());
1354          $this->assertEquals('cachestore_session', $caches['disabletest2']->phpunit_get_store_class());
1355          $this->assertEquals('cachestore_static', $caches['disabletest3']->phpunit_get_store_class());
1356  
1357          foreach ($caches as $cache) {
1358              $this->assertFalse($cache->get('test'));
1359              $this->assertTrue($cache->set('test', 'test'));
1360              $this->assertEquals('test', $cache->get('test'));
1361          }
1362  
1363          cache_factory::disable_stores();
1364  
1365          $caches = array(
1366              'disabletest1' => cache::make('phpunit', 'disabletest1'),
1367              'disabletest2' => cache::make('phpunit', 'disabletest2'),
1368              'disabletest3' => cache::make('phpunit', 'disabletest3')
1369          );
1370  
1371          $this->assertInstanceOf(cache_phpunit_application::class, $caches['disabletest1']);
1372          $this->assertInstanceOf(cache_phpunit_session::class, $caches['disabletest2']);
1373          $this->assertInstanceOf(cache_phpunit_request::class, $caches['disabletest3']);
1374  
1375          $this->assertEquals('cachestore_dummy', $caches['disabletest1']->phpunit_get_store_class());
1376          $this->assertEquals('cachestore_dummy', $caches['disabletest2']->phpunit_get_store_class());
1377          $this->assertEquals('cachestore_dummy', $caches['disabletest3']->phpunit_get_store_class());
1378  
1379          foreach ($caches as $cache) {
1380              $this->assertFalse($cache->get('test'));
1381              $this->assertTrue($cache->set('test', 'test'));
1382              $this->assertEquals('test', $cache->get('test'));
1383          }
1384      }
1385  
1386      /**
1387       * Test disabling the cache.
1388       */
1389      public function test_disable() {
1390          global $CFG;
1391  
1392          if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
1393              // We can't run this test as it requires us to delete the cache configuration script which we just
1394              // cant do with a custom path in play.
1395              $this->markTestSkipped('Skipped testing cache disable functionality as alt cache path is being used.');
1396          }
1397  
1398          $configfile = $CFG->dataroot.'/muc/config.php';
1399  
1400          // The config file will not exist yet as we've not done anything with the cache.
1401          // reset_all_data removes the file and without a call to create a configuration it doesn't exist
1402          // as yet.
1403          $this->assertFileDoesNotExist($configfile);
1404  
1405          // Disable the cache
1406          cache_phpunit_factory::phpunit_disable();
1407  
1408          // Check we get the expected disabled factory.
1409          $factory = cache_factory::instance();
1410          $this->assertInstanceOf(cache_factory_disabled::class, $factory);
1411  
1412          // Check we get the expected disabled config.
1413          $config = $factory->create_config_instance();
1414          $this->assertInstanceOf(cache_config_disabled::class, $config);
1415  
1416          // Check we get the expected disabled caches.
1417          $cache = cache::make('core', 'string');
1418          $this->assertInstanceOf(cache_disabled::class, $cache);
1419  
1420          // Test an application cache.
1421          $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'disable');
1422          $this->assertInstanceOf(cache_disabled::class, $cache);
1423  
1424          $this->assertFalse($cache->get('test'));
1425          $this->assertFalse($cache->get_versioned('v', 1));
1426          $this->assertFalse($cache->set('test', 'test'));
1427          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1428          $this->assertFalse($cache->delete('test'));
1429          $this->assertTrue($cache->purge());
1430  
1431          // Test a session cache.
1432          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'disable');
1433          $this->assertInstanceOf(cache_disabled::class, $cache);
1434  
1435          $this->assertFalse($cache->get('test'));
1436          $this->assertFalse($cache->get_versioned('v', 1));
1437          $this->assertFalse($cache->set('test', 'test'));
1438          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1439          $this->assertFalse($cache->delete('test'));
1440          $this->assertTrue($cache->purge());
1441  
1442          // Finally test a request cache.
1443          $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'phpunit', 'disable');
1444          $this->assertInstanceOf(cache_disabled::class, $cache);
1445  
1446          $this->assertFalse($cache->get('test'));
1447          $this->assertFalse($cache->get_versioned('v', 1));
1448          $this->assertFalse($cache->set('test', 'test'));
1449          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1450          $this->assertFalse($cache->delete('test'));
1451          $this->assertTrue($cache->purge());
1452  
1453          cache_factory::reset();
1454  
1455          $factory = cache_factory::instance(true);
1456          $config = $factory->create_config_instance();
1457          $this->assertEquals('cache_config_testing', get_class($config));
1458      }
1459  
1460      /**
1461       * Test that multiple application loaders work ok.
1462       */
1463      public function test_multiple_application_loaders() {
1464          $instance = cache_config_testing::instance(true);
1465          $instance->phpunit_add_file_store('phpunittest1');
1466          $instance->phpunit_add_file_store('phpunittest2');
1467          $instance->phpunit_add_definition('phpunit/multi_loader', array(
1468              'mode' => cache_store::MODE_APPLICATION,
1469              'component' => 'phpunit',
1470              'area' => 'multi_loader'
1471          ));
1472          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest1', 3);
1473          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest2', 2);
1474  
1475          $cache = cache::make('phpunit', 'multi_loader');
1476          $this->assertInstanceOf(cache_application::class, $cache);
1477          $this->assertFalse($cache->get('test'));
1478          $this->assertTrue($cache->set('test', 'test'));
1479          $this->assertEquals('test', $cache->get('test'));
1480          $this->assertTrue($cache->delete('test'));
1481          $this->assertFalse($cache->get('test'));
1482          $this->assertTrue($cache->set('test', 'test'));
1483          $this->assertTrue($cache->purge());
1484          $this->assertFalse($cache->get('test'));
1485  
1486          // Test the many commands.
1487          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
1488          $result = $cache->get_many(array('a', 'b', 'c'));
1489          $this->assertIsArray($result);
1490          $this->assertCount(3, $result);
1491          $this->assertArrayHasKey('a', $result);
1492          $this->assertArrayHasKey('b', $result);
1493          $this->assertArrayHasKey('c', $result);
1494          $this->assertEquals('A', $result['a']);
1495          $this->assertEquals('B', $result['b']);
1496          $this->assertEquals('C', $result['c']);
1497          $this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
1498          $this->assertEquals(2, $cache->delete_many(array('a', 'c')));
1499          $result = $cache->get_many(array('a', 'b', 'c'));
1500          $this->assertIsArray($result);
1501          $this->assertCount(3, $result);
1502          $this->assertArrayHasKey('a', $result);
1503          $this->assertArrayHasKey('b', $result);
1504          $this->assertArrayHasKey('c', $result);
1505          $this->assertFalse($result['a']);
1506          $this->assertEquals('B', $result['b']);
1507          $this->assertFalse($result['c']);
1508  
1509          // Test non-recursive deletes.
1510          $this->assertTrue($cache->set('test', 'test'));
1511          $this->assertSame('test', $cache->get('test'));
1512          $this->assertTrue($cache->delete('test', false));
1513          // We should still have it on a deeper loader.
1514          $this->assertSame('test', $cache->get('test'));
1515          // Test non-recusive with many functions.
1516          $this->assertSame(3, $cache->set_many(array(
1517              'one' => 'one',
1518              'two' => 'two',
1519              'three' => 'three'
1520          )));
1521          $this->assertSame('one', $cache->get('one'));
1522          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1523          $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false));
1524          $this->assertSame('one', $cache->get('one'));
1525          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1526      }
1527  
1528      /**
1529       * Data provider to try using a TTL or non-TTL cache.
1530       *
1531       * @return array
1532       */
1533      public function ttl_or_not(): array {
1534          return [[false], [true]];
1535      }
1536  
1537      /**
1538       * Data provider to try using a TTL or non-TTL cache, and static acceleration or not.
1539       *
1540       * @return array
1541       */
1542      public function ttl_and_static_acceleration_or_not(): array {
1543          return [[false, false], [false, true], [true, false], [true, true]];
1544      }
1545  
1546      /**
1547       * Data provider to try using a TTL or non-TTL cache, and simple data on or off.
1548       *
1549       * @return array
1550       */
1551      public function ttl_and_simple_data_or_not(): array {
1552          // Same values as for ttl and static acceleration (two booleans).
1553          return $this->ttl_and_static_acceleration_or_not();
1554      }
1555  
1556      /**
1557       * Shared code to set up a two or three-layer versioned cache for testing.
1558       *
1559       * @param bool $ttl If true, sets TTL in the definition
1560       * @param bool $threelayer If true, uses a 3-layer instead of 2-layer cache
1561       * @param bool $staticacceleration If true, enables static acceleration
1562       * @param bool $simpledata If true, enables simple data
1563       * @return \cache_application Cache
1564       */
1565      protected function create_versioned_cache(bool $ttl, bool $threelayer = false,
1566              bool $staticacceleration = false, bool $simpledata = false): \cache_application {
1567          $instance = cache_config_testing::instance(true);
1568          $instance->phpunit_add_file_store('a', false);
1569          $instance->phpunit_add_file_store('b', false);
1570          if ($threelayer) {
1571              $instance->phpunit_add_file_store('c', false);
1572          }
1573          $defarray = [
1574              'mode' => cache_store::MODE_APPLICATION,
1575              'component' => 'phpunit',
1576              'area' => 'multi_loader'
1577          ];
1578          if ($ttl) {
1579              $defarray['ttl'] = '600';
1580          }
1581          if ($staticacceleration) {
1582              $defarray['staticacceleration'] = true;
1583              $defarray['staticaccelerationsize'] = 10;
1584          }
1585          if ($simpledata) {
1586              $defarray['simpledata'] = true;
1587          }
1588          $instance->phpunit_add_definition('phpunit/multi_loader', $defarray, false);
1589          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'a', 1);
1590          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'b', 2);
1591          if ($threelayer) {
1592              $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'c', 3);
1593          }
1594  
1595          $multicache = cache::make('phpunit', 'multi_loader');
1596          return $multicache;
1597      }
1598  
1599      /**
1600       * Tests basic use of versioned cache.
1601       *
1602       * @dataProvider ttl_and_simple_data_or_not
1603       * @param bool $ttl If true, uses a TTL cache.
1604       * @param bool $simpledata If true, turns on simple data flag
1605       * @covers ::set_versioned
1606       * @covers ::get_versioned
1607       */
1608      public function test_versioned_cache_basic(bool $ttl, bool $simpledata): void {
1609          $multicache = $this->create_versioned_cache($ttl, false, false, $simpledata);
1610  
1611          $this->assertTrue($multicache->set_versioned('game', 1, 'Pooh-sticks'));
1612  
1613          $result = $multicache->get_versioned('game', 1, IGNORE_MISSING, $actualversion);
1614          $this->assertEquals('Pooh-sticks', $result);
1615          $this->assertEquals(1, $actualversion);
1616      }
1617  
1618      /**
1619       * Tests versioned cache with objects.
1620       *
1621       * @dataProvider ttl_and_static_acceleration_or_not
1622       * @param bool $ttl If true, uses a TTL cache.
1623       * @param bool $staticacceleration If true, enables static acceleration
1624       * @covers ::set_versioned
1625       * @covers ::get_versioned
1626       */
1627      public function test_versioned_cache_objects(bool $ttl, bool $staticacceleration): void {
1628          $multicache = $this->create_versioned_cache($ttl, false, $staticacceleration);
1629  
1630          // Set an object value.
1631          $data = (object)['game' => 'Pooh-sticks'];
1632          $this->assertTrue($multicache->set_versioned('game', 1, $data));
1633  
1634          // Get it.
1635          $result = $multicache->get_versioned('game', 1);
1636          $this->assertEquals('Pooh-sticks', $result->game);
1637  
1638          // Mess about with the value in the returned object.
1639          $result->game = 'Tag';
1640  
1641          // Get it again and confirm the cached object has not been affected.
1642          $result = $multicache->get_versioned('game', 1);
1643          $this->assertEquals('Pooh-sticks', $result->game);
1644      }
1645  
1646      /**
1647       * Tests requesting a version that doesn't exist.
1648       *
1649       * @dataProvider ttl_or_not
1650       * @param bool $ttl If true, uses a TTL cache.
1651       * @covers ::set_versioned
1652       * @covers ::get_versioned
1653       */
1654      public function test_versioned_cache_not_exist(bool $ttl): void {
1655          $multicache = $this->create_versioned_cache($ttl);
1656  
1657          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1658  
1659          // Exists but with wrong version.
1660          $this->assertFalse($multicache->get_versioned('game', 2));
1661  
1662          // Doesn't exist at all.
1663          $this->assertFalse($multicache->get_versioned('frog', 0));
1664      }
1665  
1666      /**
1667       * Tests attempts to use get after set_version or get_version after set.
1668       *
1669       * @dataProvider ttl_or_not
1670       * @param bool $ttl If true, uses a TTL cache.
1671       * @covers ::set_versioned
1672       * @covers ::get_versioned
1673       */
1674      public function test_versioned_cache_incompatible_versioning(bool $ttl): void {
1675          $multicache = $this->create_versioned_cache($ttl);
1676  
1677          // What if you use get on a get_version cache?
1678          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1679          try {
1680              $multicache->get('game');
1681              $this->fail();
1682          } catch (\coding_exception $e) {
1683              $this->assertStringContainsString('Unexpectedly found versioned cache entry', $e->getMessage());
1684          }
1685  
1686          // Or get_version on a get cache?
1687          $multicache->set('toy', 'Train set');
1688          try {
1689              $multicache->get_versioned('toy', 1);
1690              $this->fail();
1691          } catch (\coding_exception $e) {
1692              $this->assertStringContainsString('Unexpectedly found non-versioned cache entry', $e->getMessage());
1693          }
1694      }
1695  
1696      /**
1697       * Versions are only stored once, so if you set a newer version you will always get it even
1698       * if you ask for the lower version number.
1699       *
1700       * @dataProvider ttl_or_not
1701       * @param bool $ttl If true, uses a TTL cache.
1702       * @covers ::set_versioned
1703       * @covers ::get_versioned
1704       */
1705      public function test_versioned_cache_single_copy(bool $ttl): void {
1706          $multicache = $this->create_versioned_cache($ttl);
1707  
1708          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1709          $multicache->set_versioned('game', 2, 'Tag');
1710          $this->assertEquals('Tag', $multicache->get_versioned('game', 1, IGNORE_MISSING, $actualversion));
1711  
1712          // The reported version number matches the one returned, not requested.
1713          $this->assertEquals(2, $actualversion);
1714      }
1715  
1716      /**
1717       * If the first (local) store has an outdated copy but the second (shared) store has a newer
1718       * one, then it should automatically be retrieved.
1719       *
1720       * @dataProvider ttl_or_not
1721       * @param bool $ttl If true, uses a TTL cache.
1722       * @covers ::set_versioned
1723       * @covers ::get_versioned
1724       */
1725      public function test_versioned_cache_outdated_local(bool $ttl): void {
1726          $multicache = $this->create_versioned_cache($ttl);
1727  
1728          // Set initial value to version 2, 'Tag', in both stores.
1729          $multicache->set_versioned('game', 2, 'Tag');
1730  
1731          // Get the two separate cache stores for the multi-level cache.
1732          $factory = cache_factory::instance();
1733          $definition = $factory->create_definition('phpunit', 'multi_loader');
1734          [0 => $storea, 1 => $storeb] = $factory->get_store_instances_in_use($definition);
1735  
1736          // Simulate what happens if the shared cache is updated with a new version but the
1737          // local one still has an old version.
1738          $hashgame = cache_helper::hash_key('game', $definition);
1739          $data = 'British Bulldog';
1740          if ($ttl) {
1741              $data = new \cache_ttl_wrapper($data, 600);
1742          }
1743          $storeb->set($hashgame, new \core_cache\version_wrapper($data, 3));
1744  
1745          // If we ask for the old one we'll get it straight off from local cache.
1746          $this->assertEquals('Tag', $multicache->get_versioned('game', 2));
1747  
1748          // But if we ask for the new one it will still get it via the shared cache.
1749          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 3));
1750  
1751          // Also, now it will have been updated in the local cache as well.
1752          $localvalue = $storea->get($hashgame);
1753          if ($ttl) {
1754              // In case the time has changed slightly since the first set, we can't do an exact
1755              // compare, so check it ignoring the time field.
1756              $this->assertEquals(3, $localvalue->version);
1757              $ttldata = $localvalue->data;
1758              $this->assertInstanceOf('cache_ttl_wrapper', $ttldata);
1759              $this->assertEquals('British Bulldog', $ttldata->data);
1760          } else {
1761              $this->assertEquals(new \core_cache\version_wrapper('British Bulldog', 3), $localvalue);
1762          }
1763      }
1764  
1765      /**
1766       * When we request a newer version, older ones are automatically deleted in every level of the
1767       * cache (to save I/O if there are multiple requests, as if there is another request it will
1768       * not have to retrieve the values to find out that they're old).
1769       *
1770       * @dataProvider ttl_or_not
1771       * @param bool $ttl If true, uses a TTL cache.
1772       * @covers ::set_versioned
1773       * @covers ::get_versioned
1774       */
1775      public function test_versioned_cache_deleting_outdated(bool $ttl): void {
1776          $multicache = $this->create_versioned_cache($ttl);
1777  
1778          // Set initial value to version 2, 'Tag', in both stores.
1779          $multicache->set_versioned('game', 2, 'Tag');
1780  
1781          // Get the two separate cache stores for the multi-level cache.
1782          $factory = cache_factory::instance();
1783          $definition = $factory->create_definition('phpunit', 'multi_loader');
1784          [0 => $storea, 1 => $storeb] = $factory->get_store_instances_in_use($definition);
1785  
1786          // If we request a newer version, then any older version should be deleted in each
1787          // cache level.
1788          $this->assertFalse($multicache->get_versioned('game', 4));
1789          $hashgame = cache_helper::hash_key('game', $definition);
1790          $this->assertFalse($storea->get($hashgame));
1791          $this->assertFalse($storeb->get($hashgame));
1792      }
1793  
1794      /**
1795       * Tests a versioned cache when using static cache.
1796       *
1797       * @covers ::set_versioned
1798       * @covers ::get_versioned
1799       */
1800      public function test_versioned_cache_static(): void {
1801          $staticcache = $this->create_versioned_cache(false, false, true);
1802  
1803          // Set a value in the cache, version 1. This will store it in static acceleration.
1804          $staticcache->set_versioned('game', 1, 'Pooh-sticks');
1805  
1806          // Get the first cache store (we don't need the second one for this test).
1807          $factory = cache_factory::instance();
1808          $definition = $factory->create_definition('phpunit', 'multi_loader');
1809          [0 => $storea] = $factory->get_store_instances_in_use($definition);
1810  
1811          // Hack a newer version into cache store without directly calling set (now the static
1812          // has v1, store has v2). This simulates another client updating the cache.
1813          $hashgame = cache_helper::hash_key('game', $definition);
1814          $storea->set($hashgame, new \core_cache\version_wrapper('Tag', 2));
1815  
1816          // Get the key from the cache, v1. This will use static acceleration.
1817          $this->assertEquals('Pooh-sticks', $staticcache->get_versioned('game', 1));
1818  
1819          // Now if we ask for a newer version, it should not use the static cached one.
1820          $this->assertEquals('Tag', $staticcache->get_versioned('game', 2));
1821  
1822          // This get should have updated static acceleration, so it will be used next time without
1823          // a store request.
1824          $storea->set($hashgame, new \core_cache\version_wrapper('British Bulldog', 3));
1825          $this->assertEquals('Tag', $staticcache->get_versioned('game', 2));
1826  
1827          // Requesting the higher version will get rid of static acceleration again.
1828          $this->assertEquals('British Bulldog', $staticcache->get_versioned('game', 3));
1829  
1830          // Finally ask for a version that doesn't exist anywhere, just to confirm it returns null.
1831          $this->assertFalse($staticcache->get_versioned('game', 4));
1832      }
1833  
1834      /**
1835       * Tests basic use of 3-layer versioned caches.
1836       *
1837       * @covers ::set_versioned
1838       * @covers ::get_versioned
1839       */
1840      public function test_versioned_cache_3_layers_basic(): void {
1841          $multicache = $this->create_versioned_cache(false, true);
1842  
1843          // Basic use of set_versioned and get_versioned.
1844          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1845          $this->assertEquals('Pooh-sticks', $multicache->get_versioned('game', 1));
1846  
1847          // What if you ask for a version that doesn't exist?
1848          $this->assertFalse($multicache->get_versioned('game', 2));
1849  
1850          // Setting a new version wipes out the old version; if you request it, you get the new one.
1851          $multicache->set_versioned('game', 2, 'Tag');
1852          $this->assertEquals('Tag', $multicache->get_versioned('game', 1));
1853      }
1854  
1855      /**
1856       * Tests use of 3-layer versioned caches where the 3 layers currently have different versions.
1857       *
1858       * @covers ::set_versioned
1859       * @covers ::get_versioned
1860       */
1861      public function test_versioned_cache_3_layers_different_data(): void {
1862          // Set version 2 using normal method.
1863          $multicache = $this->create_versioned_cache(false, true);
1864          $multicache->set_versioned('game', 2, 'Tag');
1865  
1866          // Get the three separate cache stores for the multi-level cache.
1867          $factory = cache_factory::instance();
1868          $definition = $factory->create_definition('phpunit', 'multi_loader');
1869          [0 => $storea, 1 => $storeb, 2 => $storec] = $factory->get_store_instances_in_use($definition);
1870  
1871          // Set up two other versions so every level has a different version.
1872          $hashgame = cache_helper::hash_key('game', $definition);
1873          $storeb->set($hashgame, new \core_cache\version_wrapper('British Bulldog', 3));
1874          $storec->set($hashgame, new \core_cache\version_wrapper('Hopscotch', 4));
1875  
1876          // First request can be satisfied from A; second request requires B...
1877          $this->assertEquals('Tag', $multicache->get_versioned('game', 2));
1878          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 3));
1879  
1880          // And should update the data in A.
1881          $this->assertEquals(new \core_cache\version_wrapper('British Bulldog', 3), $storea->get($hashgame));
1882          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 1));
1883  
1884          // But newer data should still be in C.
1885          $this->assertEquals('Hopscotch', $multicache->get_versioned('game', 4));
1886          // Now it's stored in A and B too.
1887          $this->assertEquals(new \core_cache\version_wrapper('Hopscotch', 4), $storea->get($hashgame));
1888          $this->assertEquals(new \core_cache\version_wrapper('Hopscotch', 4), $storeb->get($hashgame));
1889      }
1890  
1891      /**
1892       * Test that multiple application loaders work ok.
1893       */
1894      public function test_multiple_session_loaders() {
1895          /* @var cache_config_testing $instance */
1896          $instance = cache_config_testing::instance(true);
1897          $instance->phpunit_add_session_store('phpunittest1');
1898          $instance->phpunit_add_session_store('phpunittest2');
1899          $instance->phpunit_add_definition('phpunit/multi_loader', array(
1900              'mode' => cache_store::MODE_SESSION,
1901              'component' => 'phpunit',
1902              'area' => 'multi_loader'
1903          ));
1904          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest1', 3);
1905          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest2', 2);
1906  
1907          $cache = cache::make('phpunit', 'multi_loader');
1908          $this->assertInstanceOf(cache_session::class, $cache);
1909          $this->assertFalse($cache->get('test'));
1910          $this->assertTrue($cache->set('test', 'test'));
1911          $this->assertEquals('test', $cache->get('test'));
1912          $this->assertTrue($cache->delete('test'));
1913          $this->assertFalse($cache->get('test'));
1914          $this->assertTrue($cache->set('test', 'test'));
1915          $this->assertTrue($cache->purge());
1916          $this->assertFalse($cache->get('test'));
1917  
1918          // Test the many commands.
1919          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
1920          $result = $cache->get_many(array('a', 'b', 'c'));
1921          $this->assertIsArray($result);
1922          $this->assertCount(3, $result);
1923          $this->assertArrayHasKey('a', $result);
1924          $this->assertArrayHasKey('b', $result);
1925          $this->assertArrayHasKey('c', $result);
1926          $this->assertEquals('A', $result['a']);
1927          $this->assertEquals('B', $result['b']);
1928          $this->assertEquals('C', $result['c']);
1929          $this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
1930          $this->assertEquals(2, $cache->delete_many(array('a', 'c')));
1931          $result = $cache->get_many(array('a', 'b', 'c'));
1932          $this->assertIsArray($result);
1933          $this->assertCount(3, $result);
1934          $this->assertArrayHasKey('a', $result);
1935          $this->assertArrayHasKey('b', $result);
1936          $this->assertArrayHasKey('c', $result);
1937          $this->assertFalse($result['a']);
1938          $this->assertEquals('B', $result['b']);
1939          $this->assertFalse($result['c']);
1940  
1941          // Test non-recursive deletes.
1942          $this->assertTrue($cache->set('test', 'test'));
1943          $this->assertSame('test', $cache->get('test'));
1944          $this->assertTrue($cache->delete('test', false));
1945          // We should still have it on a deeper loader.
1946          $this->assertSame('test', $cache->get('test'));
1947          // Test non-recusive with many functions.
1948          $this->assertSame(3, $cache->set_many(array(
1949              'one' => 'one',
1950              'two' => 'two',
1951              'three' => 'three'
1952          )));
1953          $this->assertSame('one', $cache->get('one'));
1954          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1955          $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false));
1956          $this->assertSame('one', $cache->get('one'));
1957          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1958      }
1959  
1960      /**
1961       * Test switching users with session caches.
1962       */
1963      public function test_session_cache_switch_user() {
1964          $this->resetAfterTest(true);
1965          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache');
1966          $user1 = $this->getDataGenerator()->create_user();
1967          $user2 = $this->getDataGenerator()->create_user();
1968  
1969          // Log in as the first user.
1970          $this->setUser($user1);
1971          $sesskey1 = sesskey();
1972  
1973          // Set a basic value in the cache.
1974          $cache->set('var', 1);
1975          $this->assertTrue($cache->has('var'));
1976          $this->assertEquals(1, $cache->get('var'));
1977  
1978          // Change to the second user.
1979          $this->setUser($user2);
1980          $sesskey2 = sesskey();
1981  
1982          // Make sure the cache doesn't give us the data for the last user.
1983          $this->assertNotEquals($sesskey1, $sesskey2);
1984          $this->assertFalse($cache->has('var'));
1985          $this->assertEquals(false, $cache->get('var'));
1986      }
1987  
1988      /**
1989       * Test switching users with session caches.
1990       */
1991      public function test_session_cache_switch_user_application_mapping() {
1992          $this->resetAfterTest(true);
1993          $instance = cache_config_testing::instance(true);
1994          $instance->phpunit_add_file_store('testfilestore');
1995          $instance->phpunit_add_definition('phpunit/testappsession', array(
1996              'mode' => cache_store::MODE_SESSION,
1997              'component' => 'phpunit',
1998              'area' => 'testappsession'
1999          ));
2000          $instance->phpunit_add_definition_mapping('phpunit/testappsession', 'testfilestore', 3);
2001          $cache = cache::make('phpunit', 'testappsession');
2002          $user1 = $this->getDataGenerator()->create_user();
2003          $user2 = $this->getDataGenerator()->create_user();
2004  
2005          // Log in as the first user.
2006          $this->setUser($user1);
2007          $sesskey1 = sesskey();
2008  
2009          // Set a basic value in the cache.
2010          $cache->set('var', 1);
2011          $this->assertTrue($cache->has('var'));
2012          $this->assertEquals(1, $cache->get('var'));
2013  
2014          // Change to the second user.
2015          $this->setUser($user2);
2016          $sesskey2 = sesskey();
2017  
2018          // Make sure the cache doesn't give us the data for the last user.
2019          $this->assertNotEquals($sesskey1, $sesskey2);
2020          $this->assertFalse($cache->has('var'));
2021          $this->assertEquals(false, $cache->get('var'));
2022      }
2023  
2024      /**
2025       * Test two session caches being used at once to confirm collisions don't occur.
2026       */
2027      public function test_dual_session_caches() {
2028          $instance = cache_config_testing::instance(true);
2029          $instance->phpunit_add_definition('phpunit/testsess1', array(
2030              'mode' => cache_store::MODE_SESSION,
2031              'component' => 'phpunit',
2032              'area' => 'testsess1'
2033          ));
2034          $instance->phpunit_add_definition('phpunit/testsess2', array(
2035              'mode' => cache_store::MODE_SESSION,
2036              'component' => 'phpunit',
2037              'area' => 'testsess2'
2038          ));
2039          $cache1 = cache::make('phpunit', 'testsess1');
2040          $cache2 = cache::make('phpunit', 'testsess2');
2041  
2042          $this->assertFalse($cache1->has('test'));
2043          $this->assertFalse($cache2->has('test'));
2044  
2045          $this->assertTrue($cache1->set('test', '1'));
2046  
2047          $this->assertTrue($cache1->has('test'));
2048          $this->assertFalse($cache2->has('test'));
2049  
2050          $this->assertTrue($cache2->set('test', '2'));
2051  
2052          $this->assertEquals(1, $cache1->get('test'));
2053          $this->assertEquals(2, $cache2->get('test'));
2054  
2055          $this->assertTrue($cache1->delete('test'));
2056      }
2057  
2058      /**
2059       * Test multiple session caches when switching user.
2060       */
2061      public function test_session_cache_switch_user_multiple() {
2062          $this->resetAfterTest(true);
2063          $cache1 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache1');
2064          $cache2 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache2');
2065          $user1 = $this->getDataGenerator()->create_user();
2066          $user2 = $this->getDataGenerator()->create_user();
2067  
2068          // Log in as the first user.
2069          $this->setUser($user1);
2070          $sesskey1 = sesskey();
2071  
2072          // Set a basic value in the caches.
2073          $cache1->set('var', 1);
2074          $cache2->set('var', 2);
2075          $this->assertEquals(1, $cache1->get('var'));
2076          $this->assertEquals(2, $cache2->get('var'));
2077  
2078          // Change to the second user.
2079          $this->setUser($user2);
2080          $sesskey2 = sesskey();
2081  
2082          // Make sure the cache doesn't give us the data for the last user.
2083          // Also make sure that switching the user has lead to both caches being purged.
2084          $this->assertNotEquals($sesskey1, $sesskey2);
2085          $this->assertEquals(false, $cache1->get('var'));
2086          $this->assertEquals(false, $cache2->get('var'));
2087      }
2088  
2089      /**
2090       * Test application locking.
2091       */
2092      public function test_application_locking() {
2093          $instance = cache_config_testing::instance(true);
2094          $instance->phpunit_add_definition('phpunit/test_application_locking', array(
2095              'mode' => cache_store::MODE_APPLICATION,
2096              'component' => 'phpunit',
2097              'area' => 'test_application_locking',
2098              'staticacceleration' => true,
2099              'staticaccelerationsize' => 1,
2100              'requirelockingread' => true,
2101              'requirelockingwrite' => true
2102          ));
2103          $cache = cache::make('phpunit', 'test_application_locking');
2104          $this->assertInstanceOf(cache_application::class, $cache);
2105  
2106          $this->assertTrue($cache->set('a', 'A'));
2107          $this->assertTrue($cache->set('b', 'B'));
2108          $this->assertTrue($cache->set('c', 'C'));
2109          $this->assertEquals('A', $cache->get('a'));
2110          $this->assertEquals(array('b' => 'B', 'c' => 'C'), $cache->get_many(array('b', 'c')));
2111          $this->assertTrue($cache->delete('a'));
2112          $this->assertFalse($cache->has('a'));
2113      }
2114  
2115      /**
2116       * Test the static cache_helper method purge_stores_used_by_definition.
2117       */
2118      public function test_purge_stores_used_by_definition() {
2119          $instance = cache_config_testing::instance(true);
2120          $instance->phpunit_add_definition('phpunit/test_purge_stores_used_by_definition', array(
2121              'mode' => cache_store::MODE_APPLICATION,
2122              'component' => 'phpunit',
2123              'area' => 'test_purge_stores_used_by_definition'
2124          ));
2125          $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition');
2126          $this->assertInstanceOf(cache_application::class, $cache);
2127          $this->assertTrue($cache->set('test', 'test'));
2128          unset($cache);
2129  
2130          cache_helper::purge_stores_used_by_definition('phpunit', 'test_purge_stores_used_by_definition');
2131  
2132          $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition');
2133          $this->assertInstanceOf(cache_application::class, $cache);
2134          $this->assertFalse($cache->get('test'));
2135      }
2136  
2137      /**
2138       * Test purge routines.
2139       */
2140      public function test_purge_routines() {
2141          $instance = cache_config_testing::instance(true);
2142          $instance->phpunit_add_definition('phpunit/purge1', array(
2143              'mode' => cache_store::MODE_APPLICATION,
2144              'component' => 'phpunit',
2145              'area' => 'purge1'
2146          ));
2147          $instance->phpunit_add_definition('phpunit/purge2', array(
2148              'mode' => cache_store::MODE_APPLICATION,
2149              'component' => 'phpunit',
2150              'area' => 'purge2',
2151              'requireidentifiers' => array(
2152                  'id'
2153              )
2154          ));
2155  
2156          $factory = cache_factory::instance();
2157          $definition = $factory->create_definition('phpunit', 'purge1');
2158          $this->assertFalse($definition->has_required_identifiers());
2159          $cache = $factory->create_cache($definition);
2160          $this->assertInstanceOf(cache_application::class, $cache);
2161          $this->assertTrue($cache->set('test', 'test'));
2162          $this->assertTrue($cache->has('test'));
2163          cache_helper::purge_by_definition('phpunit', 'purge1');
2164          $this->assertFalse($cache->has('test'));
2165  
2166          $factory = cache_factory::instance();
2167          $definition = $factory->create_definition('phpunit', 'purge2');
2168          $this->assertTrue($definition->has_required_identifiers());
2169          $cache = $factory->create_cache($definition);
2170          $this->assertInstanceOf(cache_application::class, $cache);
2171          $this->assertTrue($cache->set('test', 'test'));
2172          $this->assertTrue($cache->has('test'));
2173          cache_helper::purge_stores_used_by_definition('phpunit', 'purge2');
2174          $this->assertFalse($cache->has('test'));
2175  
2176          try {
2177              cache_helper::purge_by_definition('phpunit', 'purge2');
2178              $this->fail('Should not be able to purge a definition required identifiers without providing them.');
2179          } catch (\coding_exception $ex) {
2180              $this->assertStringContainsString('Identifier required for cache has not been provided', $ex->getMessage());
2181          }
2182      }
2183  
2184      /**
2185       * Tests that ad-hoc caches are correctly purged with a purge_all call.
2186       */
2187      public function test_purge_all_with_adhoc_caches() {
2188          $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'core_cache', 'test');
2189          $cache->set('test', 123);
2190          cache_helper::purge_all();
2191          $this->assertFalse($cache->get('test'));
2192      }
2193  
2194      /**
2195       * Test that the default stores all support searching.
2196       */
2197      public function test_defaults_support_searching() {
2198          $instance = cache_config_testing::instance(true);
2199          $instance->phpunit_add_definition('phpunit/search1', array(
2200              'mode' => cache_store::MODE_APPLICATION,
2201              'component' => 'phpunit',
2202              'area' => 'search1',
2203              'requiresearchable' => true
2204          ));
2205          $instance->phpunit_add_definition('phpunit/search2', array(
2206              'mode' => cache_store::MODE_SESSION,
2207              'component' => 'phpunit',
2208              'area' => 'search2',
2209              'requiresearchable' => true
2210          ));
2211          $instance->phpunit_add_definition('phpunit/search3', array(
2212              'mode' => cache_store::MODE_REQUEST,
2213              'component' => 'phpunit',
2214              'area' => 'search3',
2215              'requiresearchable' => true
2216          ));
2217          $factory = cache_factory::instance();
2218  
2219          // Test application cache is searchable.
2220          $definition = $factory->create_definition('phpunit', 'search1');
2221          $this->assertInstanceOf(cache_definition::class, $definition);
2222          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2223          $cache = $factory->create_cache($definition);
2224          $this->assertInstanceOf(cache_application::class, $cache);
2225          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2226  
2227          // Test session cache is searchable.
2228          $definition = $factory->create_definition('phpunit', 'search2');
2229          $this->assertInstanceOf(cache_definition::class, $definition);
2230          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2231          $cache = $factory->create_cache($definition);
2232          $this->assertInstanceOf(cache_session::class, $cache);
2233          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2234  
2235          // Test request cache is searchable.
2236          $definition = $factory->create_definition('phpunit', 'search3');
2237          $this->assertInstanceOf(cache_definition::class, $definition);
2238          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2239          $cache = $factory->create_cache($definition);
2240          $this->assertInstanceOf(cache_request::class, $cache);
2241          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2242      }
2243  
2244      /**
2245       * Test static acceleration
2246       *
2247       * Note: All the assertGreaterThanOrEqual() in this test should be assertGreaterThan() be because of some microtime()
2248       * resolution problems under some OSs / PHP versions, we are accepting equal as valid outcome. For more info see MDL-57147.
2249       */
2250      public function test_static_acceleration() {
2251          $instance = cache_config_testing::instance();
2252          $instance->phpunit_add_definition('phpunit/accelerated', array(
2253              'mode' => cache_store::MODE_APPLICATION,
2254              'component' => 'phpunit',
2255              'area' => 'accelerated',
2256              'staticacceleration' => true,
2257              'staticaccelerationsize' => 3,
2258          ));
2259          $instance->phpunit_add_definition('phpunit/accelerated2', array(
2260              'mode' => cache_store::MODE_APPLICATION,
2261              'component' => 'phpunit',
2262              'area' => 'accelerated2',
2263              'staticacceleration' => true,
2264              'staticaccelerationsize' => 3,
2265          ));
2266          $instance->phpunit_add_definition('phpunit/accelerated3', array(
2267              'mode' => cache_store::MODE_APPLICATION,
2268              'component' => 'phpunit',
2269              'area' => 'accelerated3',
2270              'staticacceleration' => true,
2271              'staticaccelerationsize' => 3,
2272          ));
2273          $instance->phpunit_add_definition('phpunit/accelerated4', array(
2274              'mode' => cache_store::MODE_APPLICATION,
2275              'component' => 'phpunit',
2276              'area' => 'accelerated4',
2277              'staticacceleration' => true,
2278              'staticaccelerationsize' => 4,
2279          ));
2280          $instance->phpunit_add_definition('phpunit/simpledataarea1', array(
2281              'mode' => cache_store::MODE_APPLICATION,
2282              'component' => 'phpunit',
2283              'area' => 'simpledataarea1',
2284              'staticacceleration' => true,
2285              'simpledata' => false
2286          ));
2287          $instance->phpunit_add_definition('phpunit/simpledataarea2', array(
2288              'mode' => cache_store::MODE_APPLICATION,
2289              'component' => 'phpunit',
2290              'area' => 'simpledataarea2',
2291              'staticacceleration' => true,
2292              'simpledata' => true
2293          ));
2294  
2295          $cache = cache::make('phpunit', 'accelerated');
2296          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2297  
2298          // Set and get three elements.
2299          $this->assertTrue($cache->set('a', 'A'));
2300          $this->assertTrue($cache->set('b', 'B'));
2301          $this->assertTrue($cache->set('c', 'C'));
2302          $this->assertEquals('A', $cache->get('a'));
2303          $this->assertEquals(array('b' => 'B', 'c' => 'C'), $cache->get_many(array('b', 'c')));
2304  
2305          // Make sure all items are in static acceleration array.
2306          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2307          $this->assertEquals('B', $cache->phpunit_static_acceleration_get('b'));
2308          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2309  
2310          // Add new value and make sure it is in cache and it is in array.
2311          $this->assertTrue($cache->set('d', 'D'));
2312          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2313          $this->assertEquals('D', $cache->get('d'));
2314  
2315          // Now the least recent accessed item (a) is no longer in acceleration array.
2316          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2317          $this->assertEquals('B', $cache->phpunit_static_acceleration_get('b'));
2318          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2319  
2320          // Adding and deleting element.
2321          $this->assertTrue($cache->set('a', 'A'));
2322          $this->assertTrue($cache->delete('a'));
2323          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2324          $this->assertFalse($cache->has('a'));
2325  
2326          // Make sure "purge" deletes from the array as well.
2327          $cache->purge();
2328          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2329          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2330          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2331          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2332          $this->assertFalse($cache->phpunit_static_acceleration_get('e'));
2333  
2334          // Check that the array holds the last accessed items by get/set.
2335          $this->assertTrue($cache->set('a', 'A'));
2336          $this->assertTrue($cache->set('b', 'B'));
2337          $this->assertTrue($cache->set('c', 'C'));
2338          $this->assertTrue($cache->set('d', 'D'));
2339          $this->assertTrue($cache->set('e', 'E'));
2340          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2341          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2342          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2343          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2344          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2345  
2346          // Store a cacheable_object, get many times and ensure each time wake_for_cache is used.
2347          // Both get and get_many are tested.  Two cache entries are used to ensure the times aren't
2348          // confused with multiple calls to get()/get_many().
2349          $startmicrotime = microtime(true);
2350          $cacheableobject = new cache_phpunit_dummy_object(1, 1, $startmicrotime);
2351          $cacheableobject2 = new cache_phpunit_dummy_object(2, 2, $startmicrotime);
2352          $this->assertTrue($cache->set('a', $cacheableobject));
2353          $this->assertTrue($cache->set('b', $cacheableobject2));
2354          $staticaccelerationreturntime = $cache->phpunit_static_acceleration_get('a')->propertytime;
2355          $staticaccelerationreturntimeb = $cache->phpunit_static_acceleration_get('b')->propertytime;
2356          $this->assertGreaterThanOrEqual($startmicrotime, $staticaccelerationreturntime, 'Restore time of static must be newer.');
2357  
2358          // Reset the static cache without resetting backing store.
2359          $cache->phpunit_static_acceleration_purge();
2360  
2361          // Get the value from the backend store, populating the static cache.
2362          $cachevalue = $cache->get('a');
2363          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $cachevalue);
2364          $this->assertGreaterThanOrEqual($staticaccelerationreturntime, $cachevalue->propertytime);
2365          $backingstorereturntime = $cachevalue->propertytime;
2366  
2367          $results = $cache->get_many(array('b'));
2368          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $results['b']);
2369          $this->assertGreaterThanOrEqual($staticaccelerationreturntimeb, $results['b']->propertytime);
2370          $backingstorereturntimeb = $results['b']->propertytime;
2371  
2372          // Obtain the value again and confirm that static cache is using wake_from_cache.
2373          // Upon failure, the times are not adjusted as wake_from_cache is skipped as the
2374          // value is stored serialized in the static acceleration cache.
2375          $cachevalue = $cache->phpunit_static_acceleration_get('a');
2376          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $cachevalue);
2377          $this->assertGreaterThanOrEqual($backingstorereturntime, $cachevalue->propertytime);
2378  
2379          $results = $cache->get_many(array('b'));
2380          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $results['b']);
2381          $this->assertGreaterThanOrEqual($backingstorereturntimeb, $results['b']->propertytime);
2382  
2383          /** @var cache_phpunit_application $cache */
2384          $cache = cache::make('phpunit', 'accelerated2');
2385          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2386  
2387          // Check that the array holds the last accessed items by get/set.
2388          $this->assertTrue($cache->set('a', 'A'));
2389          $this->assertTrue($cache->set('b', 'B'));
2390          $this->assertTrue($cache->set('c', 'C'));
2391          $this->assertTrue($cache->set('d', 'D'));
2392          $this->assertTrue($cache->set('e', 'E'));
2393          // Current keys in the array: c, d, e.
2394          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2395          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2396          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2397          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2398          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2399  
2400          $this->assertEquals('A', $cache->get('a'));
2401          // Current keys in the array: d, e, a.
2402          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2403          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2404          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2405          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2406          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2407  
2408          // Current keys in the array: d, e, a.
2409          $this->assertEquals(array('c' => 'C'), $cache->get_many(array('c')));
2410          // Current keys in the array: e, a, c.
2411          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2412          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2413          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2414          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2415          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2416  
2417  
2418          $cache = cache::make('phpunit', 'accelerated3');
2419          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2420  
2421          // Check that the array holds the last accessed items by get/set.
2422          $this->assertTrue($cache->set('a', 'A'));
2423          $this->assertTrue($cache->set('b', 'B'));
2424          $this->assertTrue($cache->set('c', 'C'));
2425          $this->assertTrue($cache->set('d', 'D'));
2426          $this->assertTrue($cache->set('e', 'E'));
2427          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2428          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2429          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2430          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2431          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2432  
2433          $this->assertTrue($cache->set('b', 'B2'));
2434          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2435          $this->assertEquals('B2', $cache->phpunit_static_acceleration_get('b'));
2436          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2437          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2438          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2439  
2440          $this->assertEquals(2, $cache->set_many(array('b' => 'B3', 'c' => 'C3')));
2441          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2442          $this->assertEquals('B3', $cache->phpunit_static_acceleration_get('b'));
2443          $this->assertEquals('C3', $cache->phpunit_static_acceleration_get('c'));
2444          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2445          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2446  
2447          $cache = cache::make('phpunit', 'accelerated4');
2448          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2449          $this->assertTrue($cache->set('a', 'A'));
2450          $this->assertTrue($cache->set('a', 'A'));
2451          $this->assertTrue($cache->set('a', 'A'));
2452          $this->assertTrue($cache->set('a', 'A'));
2453          $this->assertTrue($cache->set('a', 'A'));
2454          $this->assertTrue($cache->set('a', 'A'));
2455          $this->assertTrue($cache->set('a', 'A'));
2456          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2457          $this->assertEquals('A', $cache->get('a'));
2458  
2459          // Setting simpledata to false objects are cloned when retrieving data.
2460          $cache = cache::make('phpunit', 'simpledataarea1');
2461          $notreallysimple = new \stdClass();
2462          $notreallysimple->name = 'a';
2463          $cache->set('a', $notreallysimple);
2464          $returnedinstance1 = $cache->get('a');
2465          $returnedinstance2 = $cache->get('a');
2466          $returnedinstance1->name = 'b';
2467          $this->assertEquals('a', $returnedinstance2->name);
2468  
2469          // Setting simpledata to true we assume that data does not contain references.
2470          $cache = cache::make('phpunit', 'simpledataarea2');
2471          $notreallysimple = new \stdClass();
2472          $notreallysimple->name = 'a';
2473          $cache->set('a', $notreallysimple);
2474          $returnedinstance1 = $cache->get('a');
2475          $returnedinstance2 = $cache->get('a');
2476          $returnedinstance1->name = 'b';
2477          $this->assertEquals('b', $returnedinstance2->name);
2478      }
2479  
2480      public function test_identifiers_have_separate_caches() {
2481          $cachepg = cache::make('core', 'databasemeta', array('dbfamily' => 'pgsql'));
2482          $cachepg->set(1, 'here');
2483          $cachemy = cache::make('core', 'databasemeta', array('dbfamily' => 'mysql'));
2484          $cachemy->set(2, 'there');
2485          $this->assertEquals('here', $cachepg->get(1));
2486          $this->assertEquals('there', $cachemy->get(2));
2487          $this->assertFalse($cachemy->get(1));
2488      }
2489  
2490      public function test_performance_debug() {
2491          global $CFG;
2492          $this->resetAfterTest(true);
2493          $CFG->perfdebug = 15;
2494  
2495          $instance = cache_config_testing::instance();
2496          $applicationid = 'phpunit/applicationperf';
2497          $instance->phpunit_add_definition($applicationid, array(
2498              'mode' => cache_store::MODE_APPLICATION,
2499              'component' => 'phpunit',
2500              'area' => 'applicationperf'
2501          ));
2502          $sessionid = 'phpunit/sessionperf';
2503          $instance->phpunit_add_definition($sessionid, array(
2504              'mode' => cache_store::MODE_SESSION,
2505              'component' => 'phpunit',
2506              'area' => 'sessionperf'
2507          ));
2508          $requestid = 'phpunit/requestperf';
2509          $instance->phpunit_add_definition($requestid, array(
2510              'mode' => cache_store::MODE_REQUEST,
2511              'component' => 'phpunit',
2512              'area' => 'requestperf'
2513          ));
2514  
2515          $application = cache::make('phpunit', 'applicationperf');
2516          $session = cache::make('phpunit', 'sessionperf');
2517          $request = cache::make('phpunit', 'requestperf');
2518  
2519          // Check that no stats are recorded for these definitions yet.
2520          $stats = cache_helper::get_stats();
2521          $this->assertArrayNotHasKey($applicationid, $stats);
2522          $this->assertArrayHasKey($sessionid, $stats);       // Session cache sets a key on construct.
2523          $this->assertArrayNotHasKey($requestid, $stats);
2524  
2525          // Check that stores register misses.
2526          $this->assertFalse($application->get('missMe'));
2527          $this->assertFalse($application->get('missMe'));
2528          $this->assertFalse($session->get('missMe'));
2529          $this->assertFalse($session->get('missMe'));
2530          $this->assertFalse($session->get('missMe'));
2531          $this->assertFalse($request->get('missMe'));
2532          $this->assertFalse($request->get('missMe'));
2533          $this->assertFalse($request->get('missMe'));
2534          $this->assertFalse($request->get('missMe'));
2535  
2536          $endstats = cache_helper::get_stats();
2537          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['misses']);
2538          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits']);
2539          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets']);
2540          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['misses']);
2541          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits']);
2542          $this->assertEquals(1, $endstats[$sessionid]['stores']['default_session']['sets']);
2543          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['misses']);
2544          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits']);
2545          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets']);
2546  
2547          $startstats = cache_helper::get_stats();
2548  
2549          // Check that stores register sets.
2550          $this->assertTrue($application->set('setMe1', 1));
2551          $this->assertTrue($application->set('setMe2', 2));
2552          $this->assertTrue($session->set('setMe1', 1));
2553          $this->assertTrue($session->set('setMe2', 2));
2554          $this->assertTrue($session->set('setMe3', 3));
2555          $this->assertTrue($request->set('setMe1', 1));
2556          $this->assertTrue($request->set('setMe2', 2));
2557          $this->assertTrue($request->set('setMe3', 3));
2558          $this->assertTrue($request->set('setMe4', 4));
2559  
2560          $endstats = cache_helper::get_stats();
2561          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2562                               $startstats[$applicationid]['stores']['default_application']['misses']);
2563          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
2564                               $startstats[$applicationid]['stores']['default_application']['hits']);
2565          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
2566                               $startstats[$applicationid]['stores']['default_application']['sets']);
2567          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2568                               $startstats[$sessionid]['stores']['default_session']['misses']);
2569          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
2570                               $startstats[$sessionid]['stores']['default_session']['hits']);
2571          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
2572                               $startstats[$sessionid]['stores']['default_session']['sets']);
2573          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2574                               $startstats[$requestid]['stores']['default_request']['misses']);
2575          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
2576                               $startstats[$requestid]['stores']['default_request']['hits']);
2577          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
2578                               $startstats[$requestid]['stores']['default_request']['sets']);
2579  
2580          $startstats = cache_helper::get_stats();
2581  
2582          // Check that stores register hits.
2583          $this->assertEquals($application->get('setMe1'), 1);
2584          $this->assertEquals($application->get('setMe2'), 2);
2585          $this->assertEquals($session->get('setMe1'), 1);
2586          $this->assertEquals($session->get('setMe2'), 2);
2587          $this->assertEquals($session->get('setMe3'), 3);
2588          $this->assertEquals($request->get('setMe1'), 1);
2589          $this->assertEquals($request->get('setMe2'), 2);
2590          $this->assertEquals($request->get('setMe3'), 3);
2591          $this->assertEquals($request->get('setMe4'), 4);
2592  
2593          $endstats = cache_helper::get_stats();
2594          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2595                               $startstats[$applicationid]['stores']['default_application']['misses']);
2596          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
2597                               $startstats[$applicationid]['stores']['default_application']['hits']);
2598          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
2599                               $startstats[$applicationid]['stores']['default_application']['sets']);
2600          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2601                               $startstats[$sessionid]['stores']['default_session']['misses']);
2602          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
2603                               $startstats[$sessionid]['stores']['default_session']['hits']);
2604          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
2605                               $startstats[$sessionid]['stores']['default_session']['sets']);
2606          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2607                               $startstats[$requestid]['stores']['default_request']['misses']);
2608          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
2609                               $startstats[$requestid]['stores']['default_request']['hits']);
2610          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
2611                               $startstats[$requestid]['stores']['default_request']['sets']);
2612  
2613          $startstats = cache_helper::get_stats();
2614  
2615          // Check that stores register through get_many.
2616          $application->get_many(array('setMe1', 'setMe2'));
2617          $session->get_many(array('setMe1', 'setMe2', 'setMe3'));
2618          $request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
2619  
2620          $endstats = cache_helper::get_stats();
2621          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2622                               $startstats[$applicationid]['stores']['default_application']['misses']);
2623          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
2624                               $startstats[$applicationid]['stores']['default_application']['hits']);
2625          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
2626                               $startstats[$applicationid]['stores']['default_application']['sets']);
2627          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2628                               $startstats[$sessionid]['stores']['default_session']['misses']);
2629          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
2630                               $startstats[$sessionid]['stores']['default_session']['hits']);
2631          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
2632                               $startstats[$sessionid]['stores']['default_session']['sets']);
2633          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2634                               $startstats[$requestid]['stores']['default_request']['misses']);
2635          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
2636                               $startstats[$requestid]['stores']['default_request']['hits']);
2637          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
2638                               $startstats[$requestid]['stores']['default_request']['sets']);
2639  
2640          $startstats = cache_helper::get_stats();
2641  
2642          // Check that stores register through set_many.
2643          $this->assertEquals(2, $application->set_many(['setMe1' => 1, 'setMe2' => 2]));
2644          $this->assertEquals(3, $session->set_many(['setMe1' => 1, 'setMe2' => 2, 'setMe3' => 3]));
2645          $this->assertEquals(4, $request->set_many(['setMe1' => 1, 'setMe2' => 2, 'setMe3' => 3, 'setMe4' => 4]));
2646  
2647          $endstats = cache_helper::get_stats();
2648  
2649          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2650              $startstats[$applicationid]['stores']['default_application']['misses']);
2651          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
2652              $startstats[$applicationid]['stores']['default_application']['hits']);
2653          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
2654              $startstats[$applicationid]['stores']['default_application']['sets']);
2655          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2656              $startstats[$sessionid]['stores']['default_session']['misses']);
2657          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
2658              $startstats[$sessionid]['stores']['default_session']['hits']);
2659          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
2660              $startstats[$sessionid]['stores']['default_session']['sets']);
2661          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2662              $startstats[$requestid]['stores']['default_request']['misses']);
2663          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
2664              $startstats[$requestid]['stores']['default_request']['hits']);
2665          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
2666              $startstats[$requestid]['stores']['default_request']['sets']);
2667      }
2668  
2669      /**
2670       * Data provider for static acceleration performance tests.
2671       *
2672       * @return array
2673       */
2674      public function static_acceleration_performance_provider(): array {
2675          // Note: These are the delta values, not the absolute values.
2676          // Also note that the set will actually store the valuein the static cache immediately.
2677          $validfirst = [
2678              'default_application' => [
2679                  'hits' => 1,
2680                  'misses' => 0,
2681              ],
2682              cache_store::STATIC_ACCEL => [
2683                  'hits' => 0,
2684                  'misses' => 1,
2685              ],
2686          ];
2687  
2688          $validsecond = [
2689              'default_application' => [
2690                  'hits' => 0,
2691                  'misses' => 0,
2692              ],
2693              cache_store::STATIC_ACCEL => [
2694                  'hits' => 1,
2695                  'misses' => 0,
2696              ],
2697          ];
2698  
2699          $invalidfirst = [
2700              'default_application' => [
2701                  'hits' => 0,
2702                  'misses' => 1,
2703              ],
2704              cache_store::STATIC_ACCEL => [
2705                  'hits' => 0,
2706                  'misses' => 1,
2707              ],
2708          ];
2709          $invalidsecond = [
2710              'default_application' => [
2711                  'hits' => 0,
2712                  'misses' => 1,
2713              ],
2714              cache_store::STATIC_ACCEL => [
2715                  'hits' => 0,
2716                  'misses' => 1,
2717              ],
2718          ];;
2719  
2720          return [
2721              'Truthy' => [
2722                  true,
2723                  $validfirst,
2724                  $validsecond,
2725              ],
2726              'Null' => [
2727                  null,
2728                  $validfirst,
2729                  $validsecond,
2730              ],
2731              'Empty Array' => [
2732                  [],
2733                  $validfirst,
2734                  $validsecond,
2735              ],
2736              'Empty String' => [
2737                  '',
2738                  $validfirst,
2739                  $validsecond,
2740              ],
2741              'False' => [
2742                  false,
2743                  $invalidfirst,
2744                  $invalidsecond,
2745              ],
2746          ];
2747      }
2748  
2749      /**
2750       * Test performance of static acceleration caches with values which are frequently confused with missing values.
2751       *
2752       * @dataProvider static_acceleration_performance_provider
2753       * @param mixed $value The value to test
2754       * @param array $firstfetchstats The expected stats on the first fetch
2755       * @param array $secondfetchstats The expected stats on the subsequent fetch
2756       */
2757      public function test_static_acceleration_values_performance(
2758          $value,
2759          array $firstfetchstats,
2760          array $secondfetchstats
2761      ): void {
2762          // Note: We need to modify perfdebug to test this.
2763          global $CFG;
2764          $this->resetAfterTest(true);
2765          $CFG->perfdebug = 15;
2766  
2767          $instance = cache_config_testing::instance();
2768          $instance->phpunit_add_definition('phpunit/accelerated', [
2769              'mode' => cache_store::MODE_APPLICATION,
2770              'component' => 'phpunit',
2771              'area' => 'accelerated',
2772              'staticacceleration' => true,
2773              'staticaccelerationsize' => 1,
2774          ]);
2775  
2776          $cache = cache::make('phpunit', 'accelerated');
2777          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2778  
2779          $this->assertTrue($cache->set('value', $value));
2780  
2781          $checkstats = function(
2782              array $start,
2783              array $expectedstats
2784          ): array {
2785              $applicationid = 'phpunit/accelerated';
2786              $endstats = cache_helper::get_stats();
2787  
2788              $start = $start[$applicationid]['stores'];
2789              $end = $endstats[$applicationid]['stores'];
2790  
2791              foreach ($expectedstats as $cachename => $expected) {
2792                  foreach ($expected as $type => $value) {
2793                      $startvalue = array_key_exists($cachename, $start) ? $start[$cachename][$type] : 0;
2794                      $endvalue = array_key_exists($cachename, $end) ? $end[$cachename][$type] : 0;
2795                      $diff = $endvalue - $startvalue;
2796                      $this->assertEquals(
2797                          $value,
2798                          $diff,
2799                          "Expected $cachename $type to be $value, got $diff"
2800                      );
2801                  }
2802              }
2803  
2804              return $endstats;
2805          };
2806  
2807          // Reset the cache factory so that we can get the stats from a fresh instance.
2808          $factory = cache_factory::instance();
2809          $factory->reset_cache_instances();
2810          $cache = cache::make('phpunit', 'accelerated');
2811  
2812          // Get the initial stats.
2813          $startstats = cache_helper::get_stats();
2814  
2815          // Fetching the value the first time should seed the static cache from the application cache.
2816          $this->assertEquals($value, $cache->get('value'));
2817          $startstats = $checkstats($startstats, $firstfetchstats);
2818  
2819          // Fetching the value should only hit the static cache.
2820          $this->assertEquals($value, $cache->get('value'));
2821          $checkstats($startstats, $secondfetchstats);
2822      }
2823  
2824  
2825      public function test_static_cache() {
2826          global $CFG;
2827          $this->resetAfterTest(true);
2828          $CFG->perfdebug = 15;
2829  
2830          // Create cache store with static acceleration.
2831          $instance = cache_config_testing::instance();
2832          $applicationid = 'phpunit/applicationperf';
2833          $instance->phpunit_add_definition($applicationid, array(
2834              'mode' => cache_store::MODE_APPLICATION,
2835              'component' => 'phpunit',
2836              'area' => 'applicationperf',
2837              'simplekeys' => true,
2838              'staticacceleration' => true,
2839              'staticaccelerationsize' => 3
2840          ));
2841  
2842          $application = cache::make('phpunit', 'applicationperf');
2843  
2844          // Check that stores register sets.
2845          $this->assertTrue($application->set('setMe1', 1));
2846          $this->assertTrue($application->set('setMe2', 0));
2847          $this->assertTrue($application->set('setMe3', array()));
2848          $this->assertTrue($application->get('setMe1') !== false);
2849          $this->assertTrue($application->get('setMe2') !== false);
2850          $this->assertTrue($application->get('setMe3') !== false);
2851  
2852          // Check that the static acceleration worked, even on empty arrays and the number 0.
2853          $endstats = cache_helper::get_stats();
2854          $this->assertEquals(0, $endstats[$applicationid]['stores']['** static accel. **']['misses']);
2855          $this->assertEquals(3, $endstats[$applicationid]['stores']['** static accel. **']['hits']);
2856      }
2857  
2858      public function test_performance_debug_off() {
2859          global $CFG;
2860          $this->resetAfterTest(true);
2861          $CFG->perfdebug = 7;
2862  
2863          $instance = cache_config_testing::instance();
2864          $applicationid = 'phpunit/applicationperfoff';
2865          $instance->phpunit_add_definition($applicationid, array(
2866              'mode' => cache_store::MODE_APPLICATION,
2867              'component' => 'phpunit',
2868              'area' => 'applicationperfoff'
2869          ));
2870          $sessionid = 'phpunit/sessionperfoff';
2871          $instance->phpunit_add_definition($sessionid, array(
2872              'mode' => cache_store::MODE_SESSION,
2873              'component' => 'phpunit',
2874              'area' => 'sessionperfoff'
2875          ));
2876          $requestid = 'phpunit/requestperfoff';
2877          $instance->phpunit_add_definition($requestid, array(
2878              'mode' => cache_store::MODE_REQUEST,
2879              'component' => 'phpunit',
2880              'area' => 'requestperfoff'
2881          ));
2882  
2883          $application = cache::make('phpunit', 'applicationperfoff');
2884          $session = cache::make('phpunit', 'sessionperfoff');
2885          $request = cache::make('phpunit', 'requestperfoff');
2886  
2887          // Check that no stats are recorded for these definitions yet.
2888          $stats = cache_helper::get_stats();
2889          $this->assertArrayNotHasKey($applicationid, $stats);
2890          $this->assertArrayNotHasKey($sessionid, $stats);
2891          $this->assertArrayNotHasKey($requestid, $stats);
2892  
2893          // Trigger cache misses, cache sets and cache hits.
2894          $this->assertFalse($application->get('missMe'));
2895          $this->assertTrue($application->set('setMe', 1));
2896          $this->assertEquals(1, $application->get('setMe'));
2897          $this->assertFalse($session->get('missMe'));
2898          $this->assertTrue($session->set('setMe', 3));
2899          $this->assertEquals(3, $session->get('setMe'));
2900          $this->assertFalse($request->get('missMe'));
2901          $this->assertTrue($request->set('setMe', 4));
2902          $this->assertEquals(4, $request->get('setMe'));
2903  
2904          // Check that no stats are being recorded for these definitions.
2905          $endstats = cache_helper::get_stats();
2906          $this->assertArrayNotHasKey($applicationid, $endstats);
2907          $this->assertArrayNotHasKey($sessionid, $endstats);
2908          $this->assertArrayNotHasKey($requestid, $endstats);
2909      }
2910  
2911      /**
2912       * Tests session cache event purge and subsequent visit in the same request.
2913       *
2914       * This test simulates a cache being created, a value being set, then the value being purged.
2915       * A subsequent use of the same cache is started in the same request which fills the cache.
2916       * A new request is started a short time later.
2917       * The cache should be filled.
2918       */
2919      public function test_session_event_purge_same_second() {
2920          $instance = cache_config_testing::instance();
2921          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
2922              'mode' => cache_store::MODE_SESSION,
2923              'component' => 'phpunit',
2924              'area' => 'eventpurgetest',
2925              'invalidationevents' => array(
2926                  'crazyevent',
2927              )
2928          ));
2929  
2930          // Create the cache, set a value, and immediately purge it by event.
2931          $cache = cache::make('phpunit', 'eventpurgetest');
2932          $cache->set('testkey1', 'test data 1');
2933          $this->assertEquals('test data 1', $cache->get('testkey1'));
2934          cache_helper::purge_by_event('crazyevent');
2935          $this->assertFalse($cache->get('testkey1'));
2936  
2937          // Set up the cache again in the same request and add a new value back in.
2938          $factory = cache_factory::instance();
2939          $factory->reset_cache_instances();
2940          $cache = cache::make('phpunit', 'eventpurgetest');
2941          $cache->set('testkey1', 'test data 2');
2942          $this->assertEquals('test data 2', $cache->get('testkey1'));
2943  
2944          // Trick the cache into thinking that this is a new request.
2945          cache_phpunit_cache::simulate_new_request();
2946          $factory = cache_factory::instance();
2947          $factory->reset_cache_instances();
2948  
2949          // Set up the cache again.
2950          // This is a subsequent request at a new time, so we instead the invalidation time will be checked.
2951          // The invalidation time should match the last purged time and the cache will not be re-purged.
2952          $cache = cache::make('phpunit', 'eventpurgetest');
2953          $this->assertEquals('test data 2', $cache->get('testkey1'));
2954      }
2955  
2956      /**
2957       * Test that values set in different sessions are stored with different key prefixes.
2958       */
2959      public function test_session_distinct_storage_key() {
2960          $this->resetAfterTest();
2961  
2962          // Prepare a dummy session cache configuration.
2963          $config = cache_config_testing::instance();
2964          $config->phpunit_add_definition('phpunit/test_session_distinct_storage_key', array(
2965              'mode' => cache_store::MODE_SESSION,
2966              'component' => 'phpunit',
2967              'area' => 'test_session_distinct_storage_key'
2968          ));
2969  
2970          // First anonymous user's session cache.
2971          cache_phpunit_session::phpunit_mockup_session_id('foo');
2972          $this->setUser(0);
2973          $cache1 = cache::make('phpunit', 'test_session_distinct_storage_key');
2974  
2975          // Reset cache instances to emulate a new request.
2976          cache_factory::instance()->reset_cache_instances();
2977  
2978          // Another anonymous user's session cache.
2979          cache_phpunit_session::phpunit_mockup_session_id('bar');
2980          $this->setUser(0);
2981          $cache2 = cache::make('phpunit', 'test_session_distinct_storage_key');
2982  
2983          cache_factory::instance()->reset_cache_instances();
2984  
2985          // Guest user's session cache.
2986          cache_phpunit_session::phpunit_mockup_session_id('baz');
2987          $this->setGuestUser();
2988          $cache3 = cache::make('phpunit', 'test_session_distinct_storage_key');
2989  
2990          cache_factory::instance()->reset_cache_instances();
2991  
2992          // Same guest user's session cache but in another browser window.
2993          cache_phpunit_session::phpunit_mockup_session_id('baz');
2994          $this->setGuestUser();
2995          $cache4 = cache::make('phpunit', 'test_session_distinct_storage_key');
2996  
2997          // Assert that different PHP session implies different key prefix for storing values.
2998          $this->assertNotEquals($cache1->phpunit_get_key_prefix(), $cache2->phpunit_get_key_prefix());
2999  
3000          // Assert that same PHP session implies same key prefix for storing values.
3001          $this->assertEquals($cache3->phpunit_get_key_prefix(), $cache4->phpunit_get_key_prefix());
3002      }
3003  }