Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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          // Configure the lock timeout so the test doesn't take too long to run.
 887          $instance->phpunit_edit_store_config('default_application', ['lockwait' => 2]);
 888          $cache1 = cache::make('phpunit', 'lockingtest');
 889          $cache2 = clone($cache1);
 890  
 891          $this->assertTrue($cache1->set('testkey', 'test data'));
 892          $this->assertTrue($cache2->set('testkey', 'test data'));
 893  
 894          $cache1->acquire_lock('testkey');
 895          try {
 896              $cache2->acquire_lock('testkey');
 897              $this->fail();
 898          } catch (\moodle_exception $e) {
 899              // Check the right exception message, and debug info mentions the store type.
 900              $this->assertMatchesRegularExpression('~Unable to acquire a lock.*cachestore_file.*~',
 901                      $e->getMessage());
 902          }
 903  
 904          $this->assertTrue($cache1->check_lock_state('testkey'));
 905          $this->assertFalse($cache2->check_lock_state('testkey'));
 906  
 907          $this->assertTrue($cache1->release_lock('testkey'));
 908          $this->assertFalse($cache2->release_lock('testkey'));
 909  
 910          $this->assertTrue($cache1->set('testkey', 'test data'));
 911          $this->assertTrue($cache2->set('testkey', 'test data'));
 912      }
 913  
 914      /**
 915       * Tests application cache event invalidation
 916       */
 917      public function test_application_event_invalidation() {
 918          $instance = cache_config_testing::instance();
 919          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
 920              'mode' => cache_store::MODE_APPLICATION,
 921              'component' => 'phpunit',
 922              'area' => 'eventinvalidationtest',
 923              'invalidationevents' => array(
 924                  'crazyevent'
 925              )
 926          ));
 927          $cache = cache::make('phpunit', 'eventinvalidationtest');
 928  
 929          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 930          $this->assertEquals('test data 1', $cache->get('testkey1'));
 931          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 932          $this->assertEquals('test data 2', $cache->get('testkey2'));
 933  
 934          // Test invalidating a single entry.
 935          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
 936  
 937          $this->assertFalse($cache->get('testkey1'));
 938          $this->assertEquals('test data 2', $cache->get('testkey2'));
 939  
 940          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 941  
 942          // Test invalidating both entries.
 943          cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
 944  
 945          $this->assertFalse($cache->get('testkey1'));
 946          $this->assertFalse($cache->get('testkey2'));
 947      }
 948  
 949      /**
 950       * Tests session cache event invalidation
 951       */
 952      public function test_session_event_invalidation() {
 953          $instance = cache_config_testing::instance();
 954          $instance->phpunit_add_definition('phpunit/test_session_event_invalidation', array(
 955              'mode' => cache_store::MODE_SESSION,
 956              'component' => 'phpunit',
 957              'area' => 'test_session_event_invalidation',
 958              'invalidationevents' => array(
 959                  'crazyevent'
 960              )
 961          ));
 962          $cache = cache::make('phpunit', 'test_session_event_invalidation');
 963          $this->assertInstanceOf(cache_session::class, $cache);
 964  
 965          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 966          $this->assertEquals('test data 1', $cache->get('testkey1'));
 967          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 968          $this->assertEquals('test data 2', $cache->get('testkey2'));
 969  
 970          // Test invalidating a single entry.
 971          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
 972  
 973          $this->assertFalse($cache->get('testkey1'));
 974          $this->assertEquals('test data 2', $cache->get('testkey2'));
 975  
 976          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 977  
 978          // Test invalidating both entries.
 979          cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
 980  
 981          $this->assertFalse($cache->get('testkey1'));
 982          $this->assertFalse($cache->get('testkey2'));
 983      }
 984  
 985      /**
 986       * Tests application cache definition invalidation
 987       */
 988      public function test_application_definition_invalidation() {
 989          $instance = cache_config_testing::instance();
 990          $instance->phpunit_add_definition('phpunit/definitioninvalidation', array(
 991              'mode' => cache_store::MODE_APPLICATION,
 992              'component' => 'phpunit',
 993              'area' => 'definitioninvalidation'
 994          ));
 995          $cache = cache::make('phpunit', 'definitioninvalidation');
 996          $this->assertTrue($cache->set('testkey1', 'test data 1'));
 997          $this->assertEquals('test data 1', $cache->get('testkey1'));
 998          $this->assertTrue($cache->set('testkey2', 'test data 2'));
 999          $this->assertEquals('test data 2', $cache->get('testkey2'));
1000  
1001          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), 'testkey1');
1002  
1003          $this->assertFalse($cache->get('testkey1'));
1004          $this->assertEquals('test data 2', $cache->get('testkey2'));
1005  
1006          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1007  
1008          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1'));
1009  
1010          $this->assertFalse($cache->get('testkey1'));
1011          $this->assertEquals('test data 2', $cache->get('testkey2'));
1012  
1013          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1014  
1015          cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1', 'testkey2'));
1016  
1017          $this->assertFalse($cache->get('testkey1'));
1018          $this->assertFalse($cache->get('testkey2'));
1019      }
1020  
1021      /**
1022       * Tests session cache definition invalidation
1023       */
1024      public function test_session_definition_invalidation() {
1025          $instance = cache_config_testing::instance();
1026          $instance->phpunit_add_definition('phpunit/test_session_definition_invalidation', array(
1027              'mode' => cache_store::MODE_SESSION,
1028              'component' => 'phpunit',
1029              'area' => 'test_session_definition_invalidation'
1030          ));
1031          $cache = cache::make('phpunit', 'test_session_definition_invalidation');
1032          $this->assertInstanceOf(cache_session::class, $cache);
1033          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1034          $this->assertEquals('test data 1', $cache->get('testkey1'));
1035          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1036          $this->assertEquals('test data 2', $cache->get('testkey2'));
1037  
1038          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(), 'testkey1');
1039  
1040          $this->assertFalse($cache->get('testkey1'));
1041          $this->assertEquals('test data 2', $cache->get('testkey2'));
1042  
1043          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1044  
1045          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
1046                  array('testkey1'));
1047  
1048          $this->assertFalse($cache->get('testkey1'));
1049          $this->assertEquals('test data 2', $cache->get('testkey2'));
1050  
1051          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1052  
1053          cache_helper::invalidate_by_definition('phpunit', 'test_session_definition_invalidation', array(),
1054                  array('testkey1', 'testkey2'));
1055  
1056          $this->assertFalse($cache->get('testkey1'));
1057          $this->assertFalse($cache->get('testkey2'));
1058      }
1059  
1060      /**
1061       * Tests application cache event invalidation over a distributed setup.
1062       */
1063      public function test_distributed_application_event_invalidation() {
1064          global $CFG;
1065          // This is going to be an intense wee test.
1066          // We need to add data the to cache, invalidate it by event, manually force it back without MUC knowing to simulate a
1067          // disconnected/distributed setup (think load balanced server using local cache), instantiate the cache again and finally
1068          // check that it is not picked up.
1069          $instance = cache_config_testing::instance();
1070          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1071              'mode' => cache_store::MODE_APPLICATION,
1072              'component' => 'phpunit',
1073              'area' => 'eventinvalidationtest',
1074              'simplekeys' => true,
1075              'simpledata' => true,
1076              'invalidationevents' => array(
1077                  'crazyevent'
1078              )
1079          ));
1080          $cache = cache::make('phpunit', 'eventinvalidationtest');
1081          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1082          $this->assertEquals('test data 1', $cache->get('testkey1'));
1083  
1084          cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
1085  
1086          $this->assertFalse($cache->get('testkey1'));
1087  
1088          // OK data added, data invalidated, and invalidation time has been set.
1089          // Now we need to manually add back the data and adjust the invalidation time.
1090          $hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
1091          $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las-cache/lastinvalidation-$hash.cache";
1092          // Make sure the file is correct.
1093          $this->assertTrue(file_exists($timefile));
1094          $timecont = serialize(cache::now(true) - 60); // Back 60sec in the past to force it to re-invalidate.
1095          make_writable_directory(dirname($timefile));
1096          file_put_contents($timefile, $timecont);
1097          $this->assertTrue(file_exists($timefile));
1098  
1099          $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes-cache/testkey1-$hash.cache";
1100          $datacont = serialize("test data 1");
1101          make_writable_directory(dirname($datafile));
1102          file_put_contents($datafile, $datacont);
1103          $this->assertTrue(file_exists($datafile));
1104  
1105          // Test 1: Rebuild without the event and test its there.
1106          cache_factory::reset();
1107          $instance = cache_config_testing::instance();
1108          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1109              'mode' => cache_store::MODE_APPLICATION,
1110              'component' => 'phpunit',
1111              'area' => 'eventinvalidationtest',
1112              'simplekeys' => true,
1113              'simpledata' => true,
1114          ));
1115          $cache = cache::make('phpunit', 'eventinvalidationtest');
1116          $this->assertEquals('test data 1', $cache->get('testkey1'));
1117  
1118          // Test 2: Rebuild and test the invalidation of the event via the invalidation cache.
1119          cache_factory::reset();
1120  
1121          $instance = cache_config_testing::instance();
1122          $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
1123              'mode' => cache_store::MODE_APPLICATION,
1124              'component' => 'phpunit',
1125              'area' => 'eventinvalidationtest',
1126              'simplekeys' => true,
1127              'simpledata' => true,
1128              'invalidationevents' => array(
1129                  'crazyevent'
1130              )
1131          ));
1132  
1133          $cache = cache::make('phpunit', 'eventinvalidationtest');
1134          $this->assertFalse($cache->get('testkey1'));
1135  
1136          // Test 3: Verify that an existing lastinvalidation cache file is updated when needed.
1137  
1138          // Make a new cache class.  This should should invalidate testkey2.
1139          $cache = cache::make('phpunit', 'eventinvalidationtest');
1140  
1141          // Invalidation token should have been reset.
1142          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1143  
1144          // Set testkey2 data.
1145          $cache->set('testkey2', 'test data 2');
1146  
1147          // Backdate the event invalidation time by 30 seconds.
1148          $invalidationcache = cache::make('core', 'eventinvalidation');
1149          $invalidationcache->set('crazyevent', array('testkey2' => cache::now() - 30));
1150  
1151          // Lastinvalidation should already be cache::now().
1152          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1153  
1154          // Set it to 15 seconds ago so that we know if it changes.
1155          $pasttime = cache::now(true) - 15;
1156          $cache->set('lastinvalidation', $pasttime);
1157  
1158          // Make a new cache class.  This should not invalidate anything.
1159          cache_factory::instance()->reset_cache_instances();
1160          $cache = cache::make('phpunit', 'eventinvalidationtest');
1161  
1162          // Lastinvalidation shouldn't change since it was already newer than invalidation event.
1163          $this->assertEquals($pasttime, $cache->get('lastinvalidation'));
1164  
1165          // Now set the event invalidation to newer than the lastinvalidation time.
1166          $invalidationcache->set('crazyevent', array('testkey2' => cache::now() - 5));
1167          // Make a new cache class.  This should should invalidate testkey2.
1168          cache_factory::instance()->reset_cache_instances();
1169          $cache = cache::make('phpunit', 'eventinvalidationtest');
1170          // Lastinvalidation timestamp should have updated to cache::now().
1171          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1172  
1173          // Now simulate a purge_by_event 5 seconds ago.
1174          $invalidationcache = cache::make('core', 'eventinvalidation');
1175          $invalidationcache->set('crazyevent', array('purged' => cache::now(true) - 5));
1176          // Set our lastinvalidation timestamp to 15 seconds ago.
1177          $cache->set('lastinvalidation', cache::now(true) - 15);
1178          // Make a new cache class.  This should invalidate the cache.
1179          cache_factory::instance()->reset_cache_instances();
1180          $cache = cache::make('phpunit', 'eventinvalidationtest');
1181          // Lastinvalidation timestamp should have updated to cache::now().
1182          $this->assertEquals(cache::get_purge_token(), $cache->get('lastinvalidation'));
1183  
1184      }
1185  
1186      /**
1187       * Tests application cache event purge
1188       */
1189      public function test_application_event_purge() {
1190          $instance = cache_config_testing::instance();
1191          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
1192              'mode' => cache_store::MODE_APPLICATION,
1193              'component' => 'phpunit',
1194              'area' => 'eventpurgetest',
1195              'invalidationevents' => array(
1196                  'crazyevent'
1197              )
1198          ));
1199          $instance->phpunit_add_definition('phpunit/eventpurgetestaccelerated', array(
1200              'mode' => cache_store::MODE_APPLICATION,
1201              'component' => 'phpunit',
1202              'area' => 'eventpurgetestaccelerated',
1203              'staticacceleration' => true,
1204              'invalidationevents' => array(
1205                  'crazyevent'
1206              )
1207          ));
1208          $cache = cache::make('phpunit', 'eventpurgetest');
1209  
1210          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1211          $this->assertEquals('test data 1', $cache->get('testkey1'));
1212          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1213          $this->assertEquals('test data 2', $cache->get('testkey2'));
1214  
1215          // Purge the event.
1216          cache_helper::purge_by_event('crazyevent');
1217  
1218          // Check things have been removed.
1219          $this->assertFalse($cache->get('testkey1'));
1220          $this->assertFalse($cache->get('testkey2'));
1221  
1222          // Now test the static acceleration array.
1223          $cache = cache::make('phpunit', 'eventpurgetestaccelerated');
1224          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1225          $this->assertEquals('test data 1', $cache->get('testkey1'));
1226          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1227          $this->assertEquals('test data 2', $cache->get('testkey2'));
1228  
1229          // Purge the event.
1230          cache_helper::purge_by_event('crazyevent');
1231  
1232          // Check things have been removed.
1233          $this->assertFalse($cache->get('testkey1'));
1234          $this->assertFalse($cache->get('testkey2'));
1235      }
1236  
1237      /**
1238       * Tests session cache event purge
1239       */
1240      public function test_session_event_purge() {
1241          $instance = cache_config_testing::instance();
1242          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
1243              'mode' => cache_store::MODE_SESSION,
1244              'component' => 'phpunit',
1245              'area' => 'eventpurgetest',
1246              'invalidationevents' => array(
1247                  'crazyevent'
1248              )
1249          ));
1250          $instance->phpunit_add_definition('phpunit/eventpurgetestaccelerated', array(
1251              'mode' => cache_store::MODE_SESSION,
1252              'component' => 'phpunit',
1253              'area' => 'eventpurgetestaccelerated',
1254              'staticacceleration' => true,
1255              'invalidationevents' => array(
1256                  'crazyevent'
1257              )
1258          ));
1259          $cache = cache::make('phpunit', 'eventpurgetest');
1260  
1261          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1262          $this->assertEquals('test data 1', $cache->get('testkey1'));
1263          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1264          $this->assertEquals('test data 2', $cache->get('testkey2'));
1265  
1266          // Purge the event.
1267          cache_helper::purge_by_event('crazyevent');
1268  
1269          // Check things have been removed.
1270          $this->assertFalse($cache->get('testkey1'));
1271          $this->assertFalse($cache->get('testkey2'));
1272  
1273          // Now test the static acceleration array.
1274          $cache = cache::make('phpunit', 'eventpurgetestaccelerated');
1275          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1276          $this->assertEquals('test data 1', $cache->get('testkey1'));
1277          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1278          $this->assertEquals('test data 2', $cache->get('testkey2'));
1279  
1280          // Purge the event.
1281          cache_helper::purge_by_event('crazyevent');
1282  
1283          // Check things have been removed.
1284          $this->assertFalse($cache->get('testkey1'));
1285          $this->assertFalse($cache->get('testkey2'));
1286      }
1287  
1288      /**
1289       * Tests application cache definition purge
1290       */
1291      public function test_application_definition_purge() {
1292          $instance = cache_config_testing::instance();
1293          $instance->phpunit_add_definition('phpunit/definitionpurgetest', array(
1294              'mode' => cache_store::MODE_APPLICATION,
1295              'component' => 'phpunit',
1296              'area' => 'definitionpurgetest',
1297              'invalidationevents' => array(
1298                  'crazyevent'
1299              )
1300          ));
1301          $cache = cache::make('phpunit', 'definitionpurgetest');
1302  
1303          $this->assertTrue($cache->set('testkey1', 'test data 1'));
1304          $this->assertEquals('test data 1', $cache->get('testkey1'));
1305          $this->assertTrue($cache->set('testkey2', 'test data 2'));
1306          $this->assertEquals('test data 2', $cache->get('testkey2'));
1307  
1308          // Purge the event.
1309          cache_helper::purge_by_definition('phpunit', 'definitionpurgetest');
1310  
1311          // Check things have been removed.
1312          $this->assertFalse($cache->get('testkey1'));
1313          $this->assertFalse($cache->get('testkey2'));
1314      }
1315  
1316      /**
1317       * Test the use of an alt path.
1318       * If we can generate a config instance we are done :)
1319       */
1320      public function test_alt_cache_path() {
1321          global $CFG;
1322          if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
1323              $this->markTestSkipped('Skipped testing alt cache path as it is already being used.');
1324          }
1325          $this->resetAfterTest();
1326          $CFG->altcacheconfigpath = $CFG->dataroot.'/cache/altcacheconfigpath';
1327          $instance = cache_config_testing::instance();
1328          $this->assertInstanceOf(cache_config::class, $instance);
1329      }
1330  
1331      /**
1332       * Test disabling the cache stores.
1333       */
1334      public function test_disable_stores() {
1335          $instance = cache_config_testing::instance();
1336          $instance->phpunit_add_definition('phpunit/disabletest1', array(
1337              'mode' => cache_store::MODE_APPLICATION,
1338              'component' => 'phpunit',
1339              'area' => 'disabletest1'
1340          ));
1341          $instance->phpunit_add_definition('phpunit/disabletest2', array(
1342              'mode' => cache_store::MODE_SESSION,
1343              'component' => 'phpunit',
1344              'area' => 'disabletest2'
1345          ));
1346          $instance->phpunit_add_definition('phpunit/disabletest3', array(
1347              'mode' => cache_store::MODE_REQUEST,
1348              'component' => 'phpunit',
1349              'area' => 'disabletest3'
1350          ));
1351  
1352          $caches = array(
1353              'disabletest1' => cache::make('phpunit', 'disabletest1'),
1354              'disabletest2' => cache::make('phpunit', 'disabletest2'),
1355              'disabletest3' => cache::make('phpunit', 'disabletest3')
1356          );
1357  
1358          $this->assertInstanceOf(cache_phpunit_application::class, $caches['disabletest1']);
1359          $this->assertInstanceOf(cache_phpunit_session::class, $caches['disabletest2']);
1360          $this->assertInstanceOf(cache_phpunit_request::class, $caches['disabletest3']);
1361  
1362          $this->assertEquals('cachestore_file', $caches['disabletest1']->phpunit_get_store_class());
1363          $this->assertEquals('cachestore_session', $caches['disabletest2']->phpunit_get_store_class());
1364          $this->assertEquals('cachestore_static', $caches['disabletest3']->phpunit_get_store_class());
1365  
1366          foreach ($caches as $cache) {
1367              $this->assertFalse($cache->get('test'));
1368              $this->assertTrue($cache->set('test', 'test'));
1369              $this->assertEquals('test', $cache->get('test'));
1370          }
1371  
1372          cache_factory::disable_stores();
1373  
1374          $caches = array(
1375              'disabletest1' => cache::make('phpunit', 'disabletest1'),
1376              'disabletest2' => cache::make('phpunit', 'disabletest2'),
1377              'disabletest3' => cache::make('phpunit', 'disabletest3')
1378          );
1379  
1380          $this->assertInstanceOf(cache_phpunit_application::class, $caches['disabletest1']);
1381          $this->assertInstanceOf(cache_phpunit_session::class, $caches['disabletest2']);
1382          $this->assertInstanceOf(cache_phpunit_request::class, $caches['disabletest3']);
1383  
1384          $this->assertEquals('cachestore_dummy', $caches['disabletest1']->phpunit_get_store_class());
1385          $this->assertEquals('cachestore_dummy', $caches['disabletest2']->phpunit_get_store_class());
1386          $this->assertEquals('cachestore_dummy', $caches['disabletest3']->phpunit_get_store_class());
1387  
1388          foreach ($caches as $cache) {
1389              $this->assertFalse($cache->get('test'));
1390              $this->assertTrue($cache->set('test', 'test'));
1391              $this->assertEquals('test', $cache->get('test'));
1392          }
1393      }
1394  
1395      /**
1396       * Test disabling the cache.
1397       */
1398      public function test_disable() {
1399          global $CFG;
1400  
1401          if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
1402              // We can't run this test as it requires us to delete the cache configuration script which we just
1403              // cant do with a custom path in play.
1404              $this->markTestSkipped('Skipped testing cache disable functionality as alt cache path is being used.');
1405          }
1406  
1407          $configfile = $CFG->dataroot.'/muc/config.php';
1408  
1409          // The config file will not exist yet as we've not done anything with the cache.
1410          // reset_all_data removes the file and without a call to create a configuration it doesn't exist
1411          // as yet.
1412          $this->assertFileDoesNotExist($configfile);
1413  
1414          // Disable the cache
1415          cache_phpunit_factory::phpunit_disable();
1416  
1417          // Check we get the expected disabled factory.
1418          $factory = cache_factory::instance();
1419          $this->assertInstanceOf(cache_factory_disabled::class, $factory);
1420  
1421          // Check we get the expected disabled config.
1422          $config = $factory->create_config_instance();
1423          $this->assertInstanceOf(cache_config_disabled::class, $config);
1424  
1425          // Check we get the expected disabled caches.
1426          $cache = cache::make('core', 'string');
1427          $this->assertInstanceOf(cache_disabled::class, $cache);
1428  
1429          // Test an application cache.
1430          $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'disable');
1431          $this->assertInstanceOf(cache_disabled::class, $cache);
1432  
1433          $this->assertFalse($cache->get('test'));
1434          $this->assertFalse($cache->get_versioned('v', 1));
1435          $this->assertFalse($cache->set('test', 'test'));
1436          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1437          $this->assertFalse($cache->delete('test'));
1438          $this->assertTrue($cache->purge());
1439          // Checking a lock should always report that we have one.
1440          // Acquiring or releasing a lock should always report success.
1441          $this->assertTrue($cache->check_lock_state('test'));
1442          $this->assertTrue($cache->acquire_lock('test'));
1443          $this->assertTrue($cache->acquire_lock('test'));
1444          $this->assertTrue($cache->check_lock_state('test'));
1445          $this->assertTrue($cache->release_lock('test'));
1446          $this->assertTrue($cache->release_lock('test'));
1447          $this->assertTrue($cache->check_lock_state('test'));
1448  
1449          // Test a session cache.
1450          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'disable');
1451          $this->assertInstanceOf(cache_disabled::class, $cache);
1452  
1453          $this->assertFalse($cache->get('test'));
1454          $this->assertFalse($cache->get_versioned('v', 1));
1455          $this->assertFalse($cache->set('test', 'test'));
1456          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1457          $this->assertFalse($cache->delete('test'));
1458          $this->assertTrue($cache->purge());
1459  
1460          // Finally test a request cache.
1461          $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'phpunit', 'disable');
1462          $this->assertInstanceOf(cache_disabled::class, $cache);
1463  
1464          $this->assertFalse($cache->get('test'));
1465          $this->assertFalse($cache->get_versioned('v', 1));
1466          $this->assertFalse($cache->set('test', 'test'));
1467          $this->assertFalse($cache->set_versioned('v', 1, 'data'));
1468          $this->assertFalse($cache->delete('test'));
1469          $this->assertTrue($cache->purge());
1470  
1471          cache_factory::reset();
1472  
1473          $factory = cache_factory::instance(true);
1474          $config = $factory->create_config_instance();
1475          $this->assertEquals('cache_config_testing', get_class($config));
1476      }
1477  
1478      /**
1479       * Test that multiple application loaders work ok.
1480       */
1481      public function test_multiple_application_loaders() {
1482          $instance = cache_config_testing::instance(true);
1483          $instance->phpunit_add_file_store('phpunittest1');
1484          $instance->phpunit_add_file_store('phpunittest2');
1485          $instance->phpunit_add_definition('phpunit/multi_loader', array(
1486              'mode' => cache_store::MODE_APPLICATION,
1487              'component' => 'phpunit',
1488              'area' => 'multi_loader'
1489          ));
1490          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest1', 3);
1491          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest2', 2);
1492  
1493          $cache = cache::make('phpunit', 'multi_loader');
1494          $this->assertInstanceOf(cache_application::class, $cache);
1495          $this->assertFalse($cache->get('test'));
1496          $this->assertTrue($cache->set('test', 'test'));
1497          $this->assertEquals('test', $cache->get('test'));
1498          $this->assertTrue($cache->delete('test'));
1499          $this->assertFalse($cache->get('test'));
1500          $this->assertTrue($cache->set('test', 'test'));
1501          $this->assertTrue($cache->purge());
1502          $this->assertFalse($cache->get('test'));
1503  
1504          // Test the many commands.
1505          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
1506          $result = $cache->get_many(array('a', 'b', 'c'));
1507          $this->assertIsArray($result);
1508          $this->assertCount(3, $result);
1509          $this->assertArrayHasKey('a', $result);
1510          $this->assertArrayHasKey('b', $result);
1511          $this->assertArrayHasKey('c', $result);
1512          $this->assertEquals('A', $result['a']);
1513          $this->assertEquals('B', $result['b']);
1514          $this->assertEquals('C', $result['c']);
1515          $this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
1516          $this->assertEquals(2, $cache->delete_many(array('a', 'c')));
1517          $result = $cache->get_many(array('a', 'b', 'c'));
1518          $this->assertIsArray($result);
1519          $this->assertCount(3, $result);
1520          $this->assertArrayHasKey('a', $result);
1521          $this->assertArrayHasKey('b', $result);
1522          $this->assertArrayHasKey('c', $result);
1523          $this->assertFalse($result['a']);
1524          $this->assertEquals('B', $result['b']);
1525          $this->assertFalse($result['c']);
1526  
1527          // Test non-recursive deletes.
1528          $this->assertTrue($cache->set('test', 'test'));
1529          $this->assertSame('test', $cache->get('test'));
1530          $this->assertTrue($cache->delete('test', false));
1531          // We should still have it on a deeper loader.
1532          $this->assertSame('test', $cache->get('test'));
1533          // Test non-recusive with many functions.
1534          $this->assertSame(3, $cache->set_many(array(
1535              'one' => 'one',
1536              'two' => 'two',
1537              'three' => 'three'
1538          )));
1539          $this->assertSame('one', $cache->get('one'));
1540          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1541          $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false));
1542          $this->assertSame('one', $cache->get('one'));
1543          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1544      }
1545  
1546      /**
1547       * Data provider to try using a TTL or non-TTL cache.
1548       *
1549       * @return array
1550       */
1551      public function ttl_or_not(): array {
1552          return [[false], [true]];
1553      }
1554  
1555      /**
1556       * Data provider to try using a TTL or non-TTL cache, and static acceleration or not.
1557       *
1558       * @return array
1559       */
1560      public function ttl_and_static_acceleration_or_not(): array {
1561          return [[false, false], [false, true], [true, false], [true, true]];
1562      }
1563  
1564      /**
1565       * Data provider to try using a TTL or non-TTL cache, and simple data on or off.
1566       *
1567       * @return array
1568       */
1569      public function ttl_and_simple_data_or_not(): array {
1570          // Same values as for ttl and static acceleration (two booleans).
1571          return $this->ttl_and_static_acceleration_or_not();
1572      }
1573  
1574      /**
1575       * Shared code to set up a two or three-layer versioned cache for testing.
1576       *
1577       * @param bool $ttl If true, sets TTL in the definition
1578       * @param bool $threelayer If true, uses a 3-layer instead of 2-layer cache
1579       * @param bool $staticacceleration If true, enables static acceleration
1580       * @param bool $simpledata If true, enables simple data
1581       * @return \cache_application Cache
1582       */
1583      protected function create_versioned_cache(bool $ttl, bool $threelayer = false,
1584              bool $staticacceleration = false, bool $simpledata = false): \cache_application {
1585          $instance = cache_config_testing::instance(true);
1586          $instance->phpunit_add_file_store('a', false);
1587          $instance->phpunit_add_file_store('b', false);
1588          if ($threelayer) {
1589              $instance->phpunit_add_file_store('c', false);
1590          }
1591          $defarray = [
1592              'mode' => cache_store::MODE_APPLICATION,
1593              'component' => 'phpunit',
1594              'area' => 'multi_loader'
1595          ];
1596          if ($ttl) {
1597              $defarray['ttl'] = '600';
1598          }
1599          if ($staticacceleration) {
1600              $defarray['staticacceleration'] = true;
1601              $defarray['staticaccelerationsize'] = 10;
1602          }
1603          if ($simpledata) {
1604              $defarray['simpledata'] = true;
1605          }
1606          $instance->phpunit_add_definition('phpunit/multi_loader', $defarray, false);
1607          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'a', 1);
1608          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'b', 2);
1609          if ($threelayer) {
1610              $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'c', 3);
1611          }
1612  
1613          $multicache = cache::make('phpunit', 'multi_loader');
1614          return $multicache;
1615      }
1616  
1617      /**
1618       * Tests basic use of versioned cache.
1619       *
1620       * @dataProvider ttl_and_simple_data_or_not
1621       * @param bool $ttl If true, uses a TTL cache.
1622       * @param bool $simpledata If true, turns on simple data flag
1623       * @covers ::set_versioned
1624       * @covers ::get_versioned
1625       */
1626      public function test_versioned_cache_basic(bool $ttl, bool $simpledata): void {
1627          $multicache = $this->create_versioned_cache($ttl, false, false, $simpledata);
1628  
1629          $this->assertTrue($multicache->set_versioned('game', 1, 'Pooh-sticks'));
1630  
1631          $result = $multicache->get_versioned('game', 1, IGNORE_MISSING, $actualversion);
1632          $this->assertEquals('Pooh-sticks', $result);
1633          $this->assertEquals(1, $actualversion);
1634      }
1635  
1636      /**
1637       * Tests versioned cache with objects.
1638       *
1639       * @dataProvider ttl_and_static_acceleration_or_not
1640       * @param bool $ttl If true, uses a TTL cache.
1641       * @param bool $staticacceleration If true, enables static acceleration
1642       * @covers ::set_versioned
1643       * @covers ::get_versioned
1644       */
1645      public function test_versioned_cache_objects(bool $ttl, bool $staticacceleration): void {
1646          $multicache = $this->create_versioned_cache($ttl, false, $staticacceleration);
1647  
1648          // Set an object value.
1649          $data = (object)['game' => 'Pooh-sticks'];
1650          $this->assertTrue($multicache->set_versioned('game', 1, $data));
1651  
1652          // Get it.
1653          $result = $multicache->get_versioned('game', 1);
1654          $this->assertEquals('Pooh-sticks', $result->game);
1655  
1656          // Mess about with the value in the returned object.
1657          $result->game = 'Tag';
1658  
1659          // Get it again and confirm the cached object has not been affected.
1660          $result = $multicache->get_versioned('game', 1);
1661          $this->assertEquals('Pooh-sticks', $result->game);
1662      }
1663  
1664      /**
1665       * Tests requesting a version that doesn't exist.
1666       *
1667       * @dataProvider ttl_or_not
1668       * @param bool $ttl If true, uses a TTL cache.
1669       * @covers ::set_versioned
1670       * @covers ::get_versioned
1671       */
1672      public function test_versioned_cache_not_exist(bool $ttl): void {
1673          $multicache = $this->create_versioned_cache($ttl);
1674  
1675          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1676  
1677          // Exists but with wrong version.
1678          $this->assertFalse($multicache->get_versioned('game', 2));
1679  
1680          // Doesn't exist at all.
1681          $this->assertFalse($multicache->get_versioned('frog', 0));
1682      }
1683  
1684      /**
1685       * Tests attempts to use get after set_version or get_version after set.
1686       *
1687       * @dataProvider ttl_or_not
1688       * @param bool $ttl If true, uses a TTL cache.
1689       * @covers ::set_versioned
1690       * @covers ::get_versioned
1691       */
1692      public function test_versioned_cache_incompatible_versioning(bool $ttl): void {
1693          $multicache = $this->create_versioned_cache($ttl);
1694  
1695          // What if you use get on a get_version cache?
1696          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1697          try {
1698              $multicache->get('game');
1699              $this->fail();
1700          } catch (\coding_exception $e) {
1701              $this->assertStringContainsString('Unexpectedly found versioned cache entry', $e->getMessage());
1702          }
1703  
1704          // Or get_version on a get cache?
1705          $multicache->set('toy', 'Train set');
1706          try {
1707              $multicache->get_versioned('toy', 1);
1708              $this->fail();
1709          } catch (\coding_exception $e) {
1710              $this->assertStringContainsString('Unexpectedly found non-versioned cache entry', $e->getMessage());
1711          }
1712      }
1713  
1714      /**
1715       * Versions are only stored once, so if you set a newer version you will always get it even
1716       * if you ask for the lower version number.
1717       *
1718       * @dataProvider ttl_or_not
1719       * @param bool $ttl If true, uses a TTL cache.
1720       * @covers ::set_versioned
1721       * @covers ::get_versioned
1722       */
1723      public function test_versioned_cache_single_copy(bool $ttl): void {
1724          $multicache = $this->create_versioned_cache($ttl);
1725  
1726          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1727          $multicache->set_versioned('game', 2, 'Tag');
1728          $this->assertEquals('Tag', $multicache->get_versioned('game', 1, IGNORE_MISSING, $actualversion));
1729  
1730          // The reported version number matches the one returned, not requested.
1731          $this->assertEquals(2, $actualversion);
1732      }
1733  
1734      /**
1735       * If the first (local) store has an outdated copy but the second (shared) store has a newer
1736       * one, then it should automatically be retrieved.
1737       *
1738       * @dataProvider ttl_or_not
1739       * @param bool $ttl If true, uses a TTL cache.
1740       * @covers ::set_versioned
1741       * @covers ::get_versioned
1742       */
1743      public function test_versioned_cache_outdated_local(bool $ttl): void {
1744          $multicache = $this->create_versioned_cache($ttl);
1745  
1746          // Set initial value to version 2, 'Tag', in both stores.
1747          $multicache->set_versioned('game', 2, 'Tag');
1748  
1749          // Get the two separate cache stores for the multi-level cache.
1750          $factory = cache_factory::instance();
1751          $definition = $factory->create_definition('phpunit', 'multi_loader');
1752          [0 => $storea, 1 => $storeb] = $factory->get_store_instances_in_use($definition);
1753  
1754          // Simulate what happens if the shared cache is updated with a new version but the
1755          // local one still has an old version.
1756          $hashgame = cache_helper::hash_key('game', $definition);
1757          $data = 'British Bulldog';
1758          if ($ttl) {
1759              $data = new \cache_ttl_wrapper($data, 600);
1760          }
1761          $storeb->set($hashgame, new \core_cache\version_wrapper($data, 3));
1762  
1763          // If we ask for the old one we'll get it straight off from local cache.
1764          $this->assertEquals('Tag', $multicache->get_versioned('game', 2));
1765  
1766          // But if we ask for the new one it will still get it via the shared cache.
1767          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 3));
1768  
1769          // Also, now it will have been updated in the local cache as well.
1770          $localvalue = $storea->get($hashgame);
1771          if ($ttl) {
1772              // In case the time has changed slightly since the first set, we can't do an exact
1773              // compare, so check it ignoring the time field.
1774              $this->assertEquals(3, $localvalue->version);
1775              $ttldata = $localvalue->data;
1776              $this->assertInstanceOf('cache_ttl_wrapper', $ttldata);
1777              $this->assertEquals('British Bulldog', $ttldata->data);
1778          } else {
1779              $this->assertEquals(new \core_cache\version_wrapper('British Bulldog', 3), $localvalue);
1780          }
1781      }
1782  
1783      /**
1784       * When we request a newer version, older ones are automatically deleted in every level of the
1785       * cache (to save I/O if there are multiple requests, as if there is another request it will
1786       * not have to retrieve the values to find out that they're old).
1787       *
1788       * @dataProvider ttl_or_not
1789       * @param bool $ttl If true, uses a TTL cache.
1790       * @covers ::set_versioned
1791       * @covers ::get_versioned
1792       */
1793      public function test_versioned_cache_deleting_outdated(bool $ttl): void {
1794          $multicache = $this->create_versioned_cache($ttl);
1795  
1796          // Set initial value to version 2, 'Tag', in both stores.
1797          $multicache->set_versioned('game', 2, 'Tag');
1798  
1799          // Get the two separate cache stores for the multi-level cache.
1800          $factory = cache_factory::instance();
1801          $definition = $factory->create_definition('phpunit', 'multi_loader');
1802          [0 => $storea, 1 => $storeb] = $factory->get_store_instances_in_use($definition);
1803  
1804          // If we request a newer version, then any older version should be deleted in each
1805          // cache level.
1806          $this->assertFalse($multicache->get_versioned('game', 4));
1807          $hashgame = cache_helper::hash_key('game', $definition);
1808          $this->assertFalse($storea->get($hashgame));
1809          $this->assertFalse($storeb->get($hashgame));
1810      }
1811  
1812      /**
1813       * Tests a versioned cache when using static cache.
1814       *
1815       * @covers ::set_versioned
1816       * @covers ::get_versioned
1817       */
1818      public function test_versioned_cache_static(): void {
1819          $staticcache = $this->create_versioned_cache(false, false, true);
1820  
1821          // Set a value in the cache, version 1. This will store it in static acceleration.
1822          $staticcache->set_versioned('game', 1, 'Pooh-sticks');
1823  
1824          // Get the first cache store (we don't need the second one for this test).
1825          $factory = cache_factory::instance();
1826          $definition = $factory->create_definition('phpunit', 'multi_loader');
1827          [0 => $storea] = $factory->get_store_instances_in_use($definition);
1828  
1829          // Hack a newer version into cache store without directly calling set (now the static
1830          // has v1, store has v2). This simulates another client updating the cache.
1831          $hashgame = cache_helper::hash_key('game', $definition);
1832          $storea->set($hashgame, new \core_cache\version_wrapper('Tag', 2));
1833  
1834          // Get the key from the cache, v1. This will use static acceleration.
1835          $this->assertEquals('Pooh-sticks', $staticcache->get_versioned('game', 1));
1836  
1837          // Now if we ask for a newer version, it should not use the static cached one.
1838          $this->assertEquals('Tag', $staticcache->get_versioned('game', 2));
1839  
1840          // This get should have updated static acceleration, so it will be used next time without
1841          // a store request.
1842          $storea->set($hashgame, new \core_cache\version_wrapper('British Bulldog', 3));
1843          $this->assertEquals('Tag', $staticcache->get_versioned('game', 2));
1844  
1845          // Requesting the higher version will get rid of static acceleration again.
1846          $this->assertEquals('British Bulldog', $staticcache->get_versioned('game', 3));
1847  
1848          // Finally ask for a version that doesn't exist anywhere, just to confirm it returns null.
1849          $this->assertFalse($staticcache->get_versioned('game', 4));
1850      }
1851  
1852      /**
1853       * Tests basic use of 3-layer versioned caches.
1854       *
1855       * @covers ::set_versioned
1856       * @covers ::get_versioned
1857       */
1858      public function test_versioned_cache_3_layers_basic(): void {
1859          $multicache = $this->create_versioned_cache(false, true);
1860  
1861          // Basic use of set_versioned and get_versioned.
1862          $multicache->set_versioned('game', 1, 'Pooh-sticks');
1863          $this->assertEquals('Pooh-sticks', $multicache->get_versioned('game', 1));
1864  
1865          // What if you ask for a version that doesn't exist?
1866          $this->assertFalse($multicache->get_versioned('game', 2));
1867  
1868          // Setting a new version wipes out the old version; if you request it, you get the new one.
1869          $multicache->set_versioned('game', 2, 'Tag');
1870          $this->assertEquals('Tag', $multicache->get_versioned('game', 1));
1871      }
1872  
1873      /**
1874       * Tests use of 3-layer versioned caches where the 3 layers currently have different versions.
1875       *
1876       * @covers ::set_versioned
1877       * @covers ::get_versioned
1878       */
1879      public function test_versioned_cache_3_layers_different_data(): void {
1880          // Set version 2 using normal method.
1881          $multicache = $this->create_versioned_cache(false, true);
1882          $multicache->set_versioned('game', 2, 'Tag');
1883  
1884          // Get the three separate cache stores for the multi-level cache.
1885          $factory = cache_factory::instance();
1886          $definition = $factory->create_definition('phpunit', 'multi_loader');
1887          [0 => $storea, 1 => $storeb, 2 => $storec] = $factory->get_store_instances_in_use($definition);
1888  
1889          // Set up two other versions so every level has a different version.
1890          $hashgame = cache_helper::hash_key('game', $definition);
1891          $storeb->set($hashgame, new \core_cache\version_wrapper('British Bulldog', 3));
1892          $storec->set($hashgame, new \core_cache\version_wrapper('Hopscotch', 4));
1893  
1894          // First request can be satisfied from A; second request requires B...
1895          $this->assertEquals('Tag', $multicache->get_versioned('game', 2));
1896          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 3));
1897  
1898          // And should update the data in A.
1899          $this->assertEquals(new \core_cache\version_wrapper('British Bulldog', 3), $storea->get($hashgame));
1900          $this->assertEquals('British Bulldog', $multicache->get_versioned('game', 1));
1901  
1902          // But newer data should still be in C.
1903          $this->assertEquals('Hopscotch', $multicache->get_versioned('game', 4));
1904          // Now it's stored in A and B too.
1905          $this->assertEquals(new \core_cache\version_wrapper('Hopscotch', 4), $storea->get($hashgame));
1906          $this->assertEquals(new \core_cache\version_wrapper('Hopscotch', 4), $storeb->get($hashgame));
1907      }
1908  
1909      /**
1910       * Test that multiple application loaders work ok.
1911       */
1912      public function test_multiple_session_loaders() {
1913          /* @var cache_config_testing $instance */
1914          $instance = cache_config_testing::instance(true);
1915          $instance->phpunit_add_session_store('phpunittest1');
1916          $instance->phpunit_add_session_store('phpunittest2');
1917          $instance->phpunit_add_definition('phpunit/multi_loader', array(
1918              'mode' => cache_store::MODE_SESSION,
1919              'component' => 'phpunit',
1920              'area' => 'multi_loader'
1921          ));
1922          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest1', 3);
1923          $instance->phpunit_add_definition_mapping('phpunit/multi_loader', 'phpunittest2', 2);
1924  
1925          $cache = cache::make('phpunit', 'multi_loader');
1926          $this->assertInstanceOf(cache_session::class, $cache);
1927          $this->assertFalse($cache->get('test'));
1928          $this->assertTrue($cache->set('test', 'test'));
1929          $this->assertEquals('test', $cache->get('test'));
1930          $this->assertTrue($cache->delete('test'));
1931          $this->assertFalse($cache->get('test'));
1932          $this->assertTrue($cache->set('test', 'test'));
1933          $this->assertTrue($cache->purge());
1934          $this->assertFalse($cache->get('test'));
1935  
1936          // Test the many commands.
1937          $this->assertEquals(3, $cache->set_many(array('a' => 'A', 'b' => 'B', 'c' => 'C')));
1938          $result = $cache->get_many(array('a', 'b', 'c'));
1939          $this->assertIsArray($result);
1940          $this->assertCount(3, $result);
1941          $this->assertArrayHasKey('a', $result);
1942          $this->assertArrayHasKey('b', $result);
1943          $this->assertArrayHasKey('c', $result);
1944          $this->assertEquals('A', $result['a']);
1945          $this->assertEquals('B', $result['b']);
1946          $this->assertEquals('C', $result['c']);
1947          $this->assertEquals($result, $cache->get_many(array('a', 'b', 'c')));
1948          $this->assertEquals(2, $cache->delete_many(array('a', 'c')));
1949          $result = $cache->get_many(array('a', 'b', 'c'));
1950          $this->assertIsArray($result);
1951          $this->assertCount(3, $result);
1952          $this->assertArrayHasKey('a', $result);
1953          $this->assertArrayHasKey('b', $result);
1954          $this->assertArrayHasKey('c', $result);
1955          $this->assertFalse($result['a']);
1956          $this->assertEquals('B', $result['b']);
1957          $this->assertFalse($result['c']);
1958  
1959          // Test non-recursive deletes.
1960          $this->assertTrue($cache->set('test', 'test'));
1961          $this->assertSame('test', $cache->get('test'));
1962          $this->assertTrue($cache->delete('test', false));
1963          // We should still have it on a deeper loader.
1964          $this->assertSame('test', $cache->get('test'));
1965          // Test non-recusive with many functions.
1966          $this->assertSame(3, $cache->set_many(array(
1967              'one' => 'one',
1968              'two' => 'two',
1969              'three' => 'three'
1970          )));
1971          $this->assertSame('one', $cache->get('one'));
1972          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1973          $this->assertSame(3, $cache->delete_many(array('one', 'two', 'three'), false));
1974          $this->assertSame('one', $cache->get('one'));
1975          $this->assertSame(array('two' => 'two', 'three' => 'three'), $cache->get_many(array('two', 'three')));
1976      }
1977  
1978      /**
1979       * Test switching users with session caches.
1980       */
1981      public function test_session_cache_switch_user() {
1982          $this->resetAfterTest(true);
1983          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache');
1984          $user1 = $this->getDataGenerator()->create_user();
1985          $user2 = $this->getDataGenerator()->create_user();
1986  
1987          // Log in as the first user.
1988          $this->setUser($user1);
1989          $sesskey1 = sesskey();
1990  
1991          // Set a basic value in the cache.
1992          $cache->set('var', 1);
1993          $this->assertTrue($cache->has('var'));
1994          $this->assertEquals(1, $cache->get('var'));
1995  
1996          // Change to the second user.
1997          $this->setUser($user2);
1998          $sesskey2 = sesskey();
1999  
2000          // Make sure the cache doesn't give us the data for the last user.
2001          $this->assertNotEquals($sesskey1, $sesskey2);
2002          $this->assertFalse($cache->has('var'));
2003          $this->assertEquals(false, $cache->get('var'));
2004      }
2005  
2006      /**
2007       * Test switching users with session caches.
2008       */
2009      public function test_session_cache_switch_user_application_mapping() {
2010          $this->resetAfterTest(true);
2011          $instance = cache_config_testing::instance(true);
2012          $instance->phpunit_add_file_store('testfilestore');
2013          $instance->phpunit_add_definition('phpunit/testappsession', array(
2014              'mode' => cache_store::MODE_SESSION,
2015              'component' => 'phpunit',
2016              'area' => 'testappsession'
2017          ));
2018          $instance->phpunit_add_definition_mapping('phpunit/testappsession', 'testfilestore', 3);
2019          $cache = cache::make('phpunit', 'testappsession');
2020          $user1 = $this->getDataGenerator()->create_user();
2021          $user2 = $this->getDataGenerator()->create_user();
2022  
2023          // Log in as the first user.
2024          $this->setUser($user1);
2025          $sesskey1 = sesskey();
2026  
2027          // Set a basic value in the cache.
2028          $cache->set('var', 1);
2029          $this->assertTrue($cache->has('var'));
2030          $this->assertEquals(1, $cache->get('var'));
2031  
2032          // Change to the second user.
2033          $this->setUser($user2);
2034          $sesskey2 = sesskey();
2035  
2036          // Make sure the cache doesn't give us the data for the last user.
2037          $this->assertNotEquals($sesskey1, $sesskey2);
2038          $this->assertFalse($cache->has('var'));
2039          $this->assertEquals(false, $cache->get('var'));
2040      }
2041  
2042      /**
2043       * Test two session caches being used at once to confirm collisions don't occur.
2044       */
2045      public function test_dual_session_caches() {
2046          $instance = cache_config_testing::instance(true);
2047          $instance->phpunit_add_definition('phpunit/testsess1', array(
2048              'mode' => cache_store::MODE_SESSION,
2049              'component' => 'phpunit',
2050              'area' => 'testsess1'
2051          ));
2052          $instance->phpunit_add_definition('phpunit/testsess2', array(
2053              'mode' => cache_store::MODE_SESSION,
2054              'component' => 'phpunit',
2055              'area' => 'testsess2'
2056          ));
2057          $cache1 = cache::make('phpunit', 'testsess1');
2058          $cache2 = cache::make('phpunit', 'testsess2');
2059  
2060          $this->assertFalse($cache1->has('test'));
2061          $this->assertFalse($cache2->has('test'));
2062  
2063          $this->assertTrue($cache1->set('test', '1'));
2064  
2065          $this->assertTrue($cache1->has('test'));
2066          $this->assertFalse($cache2->has('test'));
2067  
2068          $this->assertTrue($cache2->set('test', '2'));
2069  
2070          $this->assertEquals(1, $cache1->get('test'));
2071          $this->assertEquals(2, $cache2->get('test'));
2072  
2073          $this->assertTrue($cache1->delete('test'));
2074      }
2075  
2076      /**
2077       * Test multiple session caches when switching user.
2078       */
2079      public function test_session_cache_switch_user_multiple() {
2080          $this->resetAfterTest(true);
2081          $cache1 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache1');
2082          $cache2 = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'sessioncache2');
2083          $user1 = $this->getDataGenerator()->create_user();
2084          $user2 = $this->getDataGenerator()->create_user();
2085  
2086          // Log in as the first user.
2087          $this->setUser($user1);
2088          $sesskey1 = sesskey();
2089  
2090          // Set a basic value in the caches.
2091          $cache1->set('var', 1);
2092          $cache2->set('var', 2);
2093          $this->assertEquals(1, $cache1->get('var'));
2094          $this->assertEquals(2, $cache2->get('var'));
2095  
2096          // Change to the second user.
2097          $this->setUser($user2);
2098          $sesskey2 = sesskey();
2099  
2100          // Make sure the cache doesn't give us the data for the last user.
2101          // Also make sure that switching the user has lead to both caches being purged.
2102          $this->assertNotEquals($sesskey1, $sesskey2);
2103          $this->assertEquals(false, $cache1->get('var'));
2104          $this->assertEquals(false, $cache2->get('var'));
2105      }
2106  
2107      /**
2108       * The application locking feature should work with caches that support multiple identifiers
2109       * (static cache and MongoDB with a specific setting).
2110       *
2111       * @covers \cache_application
2112       */
2113      public function test_application_locking_multiple_identifier_cache() {
2114          // Get an arbitrary definition (modinfo).
2115          $instance = cache_config_testing::instance(true);
2116          $definitions = $instance->get_definitions();
2117          $definition = \cache_definition::load('phpunit', $definitions['core/coursemodinfo']);
2118  
2119          // Set up a static cache using that definition, wrapped in cache_application so we can do
2120          // locking.
2121          $store = new \cachestore_static('test');
2122          $store->initialise($definition);
2123          $cache = new cache_application($definition, $store);
2124  
2125          // Test the three locking functions.
2126          $cache->acquire_lock('frog');
2127          $this->assertTrue($cache->check_lock_state('frog'));
2128          $cache->release_lock('frog');
2129      }
2130  
2131      /**
2132       * Test requiring a lock before attempting to set a key.
2133       *
2134       * @covers ::set_implementation
2135       */
2136      public function test_application_locking_before_write() {
2137          $instance = cache_config_testing::instance(true);
2138          $instance->phpunit_add_definition('phpunit/test_application_locking', array(
2139              'mode' => cache_store::MODE_APPLICATION,
2140              'component' => 'phpunit',
2141              'area' => 'test_application_locking',
2142              'staticacceleration' => true,
2143              'staticaccelerationsize' => 1,
2144              'requirelockingbeforewrite' => true
2145          ));
2146          $cache = cache::make('phpunit', 'test_application_locking');
2147          $this->assertInstanceOf(cache_application::class, $cache);
2148  
2149          $cache->acquire_lock('a');
2150          try {
2151              // Set with lock.
2152              $this->assertTrue($cache->set('a', 'A'));
2153  
2154              // Set without lock.
2155              try {
2156                  $cache->set('b', 'B');
2157                  $this->fail();
2158              } catch (\coding_exception $e) {
2159                  $this->assertStringContainsString(
2160                          'Attempted to set cache key "b" without a lock. ' .
2161                          'Locking before writes is required for phpunit/test_application_locking',
2162                          $e->getMessage());
2163              }
2164  
2165              // Set many without full lock.
2166              try {
2167                  $cache->set_many(['a' => 'AA', 'b' => 'BB']);
2168                  $this->fail();
2169              } catch (\coding_exception $e) {
2170                  $this->assertStringContainsString(
2171                          'Attempted to set cache key "b" without a lock.',
2172                          $e->getMessage());
2173              }
2174  
2175              // Check it didn't set key a either.
2176              $this->assertEquals('A', $cache->get('a'));
2177  
2178              // Set many with full lock.
2179              $cache->acquire_lock('b');
2180              try {
2181                  $this->assertEquals(2, $cache->set_many(['a' => 'AA', 'b' => 'BB']));
2182                  $this->assertEquals('AA', $cache->get('a'));
2183                  $this->assertEquals('BB', $cache->get('b'));
2184              } finally {
2185                  $cache->release_lock('b');
2186              }
2187  
2188              // Delete key with lock.
2189              $this->assertTrue($cache->delete('a'));
2190              $this->assertFalse($cache->get('a'));
2191  
2192              // Delete key without lock.
2193              try {
2194                  $cache->delete('b');
2195                  $this->fail();
2196              } catch (\coding_exception $e) {
2197                  $this->assertStringContainsString(
2198                          'Attempted to delete cache key "b" without a lock.',
2199                          $e->getMessage());
2200              }
2201  
2202              // Delete many without full lock.
2203              $cache->set('a', 'AAA');
2204              try {
2205                  $cache->delete_many(['a', 'b']);
2206                  $this->fail();
2207              } catch (\coding_exception $e) {
2208                  $this->assertStringContainsString(
2209                          'Attempted to delete cache key "b" without a lock.',
2210                          $e->getMessage());
2211              }
2212              // Nothing was deleted.
2213              $this->assertEquals('AAA', $cache->get('a'));
2214  
2215              // Delete many with full lock.
2216              $cache->acquire_lock('b');
2217              try {
2218                  $this->assertEquals(2, $cache->delete_many(['a', 'b']));
2219              } finally {
2220                  $cache->release_lock('b');
2221              }
2222              $this->assertFalse($cache->get('a'));
2223              $this->assertFalse($cache->get('b'));
2224          } finally {
2225              $cache->release_lock('a');
2226          }
2227      }
2228  
2229      /**
2230       * Test that locking before write works when writing across multiple layers.
2231       *
2232       * @covers \cache_loader
2233       * @return void
2234       */
2235      public function test_application_locking_multiple_layers() {
2236  
2237          $instance = cache_config_testing::instance(true);
2238          $instance->phpunit_add_definition('phpunit/test_application_locking', array(
2239              'mode' => cache_store::MODE_APPLICATION,
2240              'component' => 'phpunit',
2241              'area' => 'test_application_locking',
2242              'staticacceleration' => true,
2243              'staticaccelerationsize' => 1,
2244              'requirelockingbeforewrite' => true
2245          ), false);
2246          $instance->phpunit_add_file_store('phpunittest1');
2247          $instance->phpunit_add_file_store('phpunittest2');
2248          $instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest1', 1);
2249          $instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest2', 2);
2250  
2251          $cache = cache::make('phpunit', 'test_application_locking');
2252          $this->assertInstanceOf(cache_application::class, $cache);
2253  
2254          // Check that we can set a key across multiple layers.
2255          $cache->acquire_lock('a');
2256          $this->assertTrue($cache->set('a', 'A'));
2257  
2258          // Delete from the current layer.
2259          $cache->delete('a', false);
2260          $cache->release_lock('a');
2261  
2262          // Check that we can get the value from the deeper layer, which will also re-set it in the current one.
2263          $this->assertEquals('A', $cache->get('a'));
2264  
2265          // Try set/delete/get_many.
2266          $cache->acquire_lock('x');
2267          $cache->acquire_lock('y');
2268          $cache->acquire_lock('z');
2269          $this->assertEquals(3, $cache->set_many(['x' => 'X', 'y' => 'Y', 'z' => 'Z']));
2270          $cache->delete_many(['x', 'y', 'z'], false);
2271          $cache->release_lock('x');
2272          $cache->release_lock('y');
2273          $cache->release_lock('z');
2274  
2275          $this->assertEquals(['x' => 'X', 'y' => 'Y', 'z' => 'Z'], $cache->get_many(['x', 'y', 'z']));
2276  
2277          $cache->purge();
2278  
2279          // Try the tests again with a third layer.
2280          $instance->phpunit_add_file_store('phpunittest3');
2281          $instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest3', 3);
2282          $cache = cache::make('phpunit', 'test_application_locking');
2283          $this->assertInstanceOf(cache_application::class, $cache);
2284  
2285          // Check that we can set a key across multiple layers.
2286          $cache->acquire_lock('a');
2287          $this->assertTrue($cache->set('a', 'A'));
2288  
2289          // Delete from the current layer.
2290          $cache->delete('a', false);
2291          $cache->release_lock('a');
2292  
2293          // Check that we can get the value from the deeper layer, which will also re-set it in the current one.
2294          $this->assertEquals('A', $cache->get('a'));
2295  
2296          // Try set/delete/get_many.
2297          $cache->acquire_lock('x');
2298          $cache->acquire_lock('y');
2299          $cache->acquire_lock('z');
2300          $this->assertEquals(3, $cache->set_many(['x' => 'X', 'y' => 'Y', 'z' => 'Z']));
2301          $cache->delete_many(['x', 'y', 'z'], false);
2302          $cache->release_lock('x');
2303          $cache->release_lock('y');
2304          $cache->release_lock('z');
2305  
2306          $this->assertEquals(['x' => 'X', 'y' => 'Y', 'z' => 'Z'], $cache->get_many(['x', 'y', 'z']));
2307      }
2308  
2309      /**
2310       * Tests that locking fails correctly when either layer of a 2-layer cache has a lock already.
2311       *
2312       * @covers \cache_loader
2313       */
2314      public function test_application_locking_multiple_layers_failures(): void {
2315  
2316          $instance = cache_config_testing::instance(true);
2317          $instance->phpunit_add_definition('phpunit/test_application_locking', array(
2318              'mode' => cache_store::MODE_APPLICATION,
2319              'component' => 'phpunit',
2320              'area' => 'test_application_locking',
2321              'staticacceleration' => true,
2322              'staticaccelerationsize' => 1,
2323              'requirelockingbeforewrite' => true
2324          ), false);
2325          $instance->phpunit_add_file_store('phpunittest1');
2326          $instance->phpunit_add_file_store('phpunittest2');
2327          $instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest1', 1);
2328          $instance->phpunit_add_definition_mapping('phpunit/test_application_locking', 'phpunittest2', 2);
2329  
2330          $cache = cache::make('phpunit', 'test_application_locking');
2331  
2332          // We need to get the individual stores so as to set up the right behaviour here.
2333          $ref = new \ReflectionClass('\cache');
2334          $definitionprop = $ref->getProperty('definition');
2335          $definitionprop->setAccessible(true);
2336          $storeprop = $ref->getProperty('store');
2337          $storeprop->setAccessible(true);
2338          $loaderprop = $ref->getProperty('loader');
2339          $loaderprop->setAccessible(true);
2340  
2341          $definition = $definitionprop->getValue($cache);
2342          $localstore = $storeprop->getValue($cache);
2343          $sharedcache = $loaderprop->getValue($cache);
2344          $sharedstore = $storeprop->getValue($sharedcache);
2345  
2346          // Set the lock waiting time to 1 second so it doesn't take forever to run the test.
2347          $ref = new \ReflectionClass('\cachestore_file');
2348          $lockwaitprop = $ref->getProperty('lockwait');
2349          $lockwaitprop->setAccessible(true);
2350  
2351          $lockwaitprop->setValue($localstore, 1);
2352          $lockwaitprop->setValue($sharedstore, 1);
2353  
2354          // Get key details and the cache identifier.
2355          $hashedkey = cache_helper::hash_key('apple', $definition);
2356          $localidentifier = $cache->get_identifier();
2357          $sharedidentifier = $sharedcache->get_identifier();
2358  
2359          // 1. Local cache is not locked but parent cache is locked.
2360          $sharedstore->acquire_lock($hashedkey, 'somebodyelse');
2361          try {
2362              try {
2363                  $cache->acquire_lock('apple');
2364                  $this->fail();
2365              } catch (\moodle_exception $e) {
2366              }
2367  
2368              // Neither store is locked by us, shared store still locked.
2369              $this->assertFalse((bool)$localstore->check_lock_state($hashedkey, $localidentifier));
2370              $this->assertFalse((bool)$sharedstore->check_lock_state($hashedkey, $sharedidentifier));
2371              $this->assertTrue((bool)$sharedstore->check_lock_state($hashedkey, 'somebodyelse'));
2372          } finally {
2373              $sharedstore->release_lock($hashedkey, 'somebodyelse');
2374          }
2375  
2376          // 2. Local cache is locked, parent cache is not locked.
2377          $localstore->acquire_lock($hashedkey, 'somebodyelse');
2378          try {
2379              try {
2380                  $cache->acquire_lock('apple');
2381                  $this->fail();
2382              } catch (\moodle_exception $e) {
2383  
2384              }
2385  
2386              // Neither store is locked by us, local store still locked.
2387              $this->assertFalse((bool)$localstore->check_lock_state($hashedkey, $localidentifier));
2388              $this->assertFalse((bool)$sharedstore->check_lock_state($hashedkey, $sharedidentifier));
2389              $this->assertTrue((bool)$localstore->check_lock_state($hashedkey, 'somebodyelse'));
2390          } finally {
2391              $localstore->release_lock($hashedkey, 'somebodyelse');
2392          }
2393  
2394          // 3. Just for completion, test what happens if we do lock it.
2395          $this->assertTrue($cache->acquire_lock('apple'));
2396          try {
2397              $this->assertTrue((bool)$localstore->check_lock_state($hashedkey, $localidentifier));
2398              $this->assertTrue((bool)$sharedstore->check_lock_state($hashedkey, $sharedidentifier));
2399          } finally {
2400              $cache->release_lock('apple');
2401          }
2402      }
2403  
2404      /**
2405       * Test the static cache_helper method purge_stores_used_by_definition.
2406       */
2407      public function test_purge_stores_used_by_definition() {
2408          $instance = cache_config_testing::instance(true);
2409          $instance->phpunit_add_definition('phpunit/test_purge_stores_used_by_definition', array(
2410              'mode' => cache_store::MODE_APPLICATION,
2411              'component' => 'phpunit',
2412              'area' => 'test_purge_stores_used_by_definition'
2413          ));
2414          $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition');
2415          $this->assertInstanceOf(cache_application::class, $cache);
2416          $this->assertTrue($cache->set('test', 'test'));
2417          unset($cache);
2418  
2419          cache_helper::purge_stores_used_by_definition('phpunit', 'test_purge_stores_used_by_definition');
2420  
2421          $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition');
2422          $this->assertInstanceOf(cache_application::class, $cache);
2423          $this->assertFalse($cache->get('test'));
2424      }
2425  
2426      /**
2427       * Test purge routines.
2428       */
2429      public function test_purge_routines() {
2430          $instance = cache_config_testing::instance(true);
2431          $instance->phpunit_add_definition('phpunit/purge1', array(
2432              'mode' => cache_store::MODE_APPLICATION,
2433              'component' => 'phpunit',
2434              'area' => 'purge1'
2435          ));
2436          $instance->phpunit_add_definition('phpunit/purge2', array(
2437              'mode' => cache_store::MODE_APPLICATION,
2438              'component' => 'phpunit',
2439              'area' => 'purge2',
2440              'requireidentifiers' => array(
2441                  'id'
2442              )
2443          ));
2444  
2445          $factory = cache_factory::instance();
2446          $definition = $factory->create_definition('phpunit', 'purge1');
2447          $this->assertFalse($definition->has_required_identifiers());
2448          $cache = $factory->create_cache($definition);
2449          $this->assertInstanceOf(cache_application::class, $cache);
2450          $this->assertTrue($cache->set('test', 'test'));
2451          $this->assertTrue($cache->has('test'));
2452          cache_helper::purge_by_definition('phpunit', 'purge1');
2453          $this->assertFalse($cache->has('test'));
2454  
2455          $factory = cache_factory::instance();
2456          $definition = $factory->create_definition('phpunit', 'purge2');
2457          $this->assertTrue($definition->has_required_identifiers());
2458          $cache = $factory->create_cache($definition);
2459          $this->assertInstanceOf(cache_application::class, $cache);
2460          $this->assertTrue($cache->set('test', 'test'));
2461          $this->assertTrue($cache->has('test'));
2462          cache_helper::purge_stores_used_by_definition('phpunit', 'purge2');
2463          $this->assertFalse($cache->has('test'));
2464  
2465          try {
2466              cache_helper::purge_by_definition('phpunit', 'purge2');
2467              $this->fail('Should not be able to purge a definition required identifiers without providing them.');
2468          } catch (\coding_exception $ex) {
2469              $this->assertStringContainsString('Identifier required for cache has not been provided', $ex->getMessage());
2470          }
2471      }
2472  
2473      /**
2474       * Tests that ad-hoc caches are correctly purged with a purge_all call.
2475       */
2476      public function test_purge_all_with_adhoc_caches() {
2477          $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'core_cache', 'test');
2478          $cache->set('test', 123);
2479          cache_helper::purge_all();
2480          $this->assertFalse($cache->get('test'));
2481      }
2482  
2483      /**
2484       * Test that the default stores all support searching.
2485       */
2486      public function test_defaults_support_searching() {
2487          $instance = cache_config_testing::instance(true);
2488          $instance->phpunit_add_definition('phpunit/search1', array(
2489              'mode' => cache_store::MODE_APPLICATION,
2490              'component' => 'phpunit',
2491              'area' => 'search1',
2492              'requiresearchable' => true
2493          ));
2494          $instance->phpunit_add_definition('phpunit/search2', array(
2495              'mode' => cache_store::MODE_SESSION,
2496              'component' => 'phpunit',
2497              'area' => 'search2',
2498              'requiresearchable' => true
2499          ));
2500          $instance->phpunit_add_definition('phpunit/search3', array(
2501              'mode' => cache_store::MODE_REQUEST,
2502              'component' => 'phpunit',
2503              'area' => 'search3',
2504              'requiresearchable' => true
2505          ));
2506          $factory = cache_factory::instance();
2507  
2508          // Test application cache is searchable.
2509          $definition = $factory->create_definition('phpunit', 'search1');
2510          $this->assertInstanceOf(cache_definition::class, $definition);
2511          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2512          $cache = $factory->create_cache($definition);
2513          $this->assertInstanceOf(cache_application::class, $cache);
2514          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2515  
2516          // Test session cache is searchable.
2517          $definition = $factory->create_definition('phpunit', 'search2');
2518          $this->assertInstanceOf(cache_definition::class, $definition);
2519          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2520          $cache = $factory->create_cache($definition);
2521          $this->assertInstanceOf(cache_session::class, $cache);
2522          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2523  
2524          // Test request cache is searchable.
2525          $definition = $factory->create_definition('phpunit', 'search3');
2526          $this->assertInstanceOf(cache_definition::class, $definition);
2527          $this->assertEquals(cache_store::IS_SEARCHABLE, $definition->get_requirements_bin() & cache_store::IS_SEARCHABLE);
2528          $cache = $factory->create_cache($definition);
2529          $this->assertInstanceOf(cache_request::class, $cache);
2530          $this->assertArrayHasKey('cache_is_searchable', $cache->phpunit_get_store_implements());
2531      }
2532  
2533      /**
2534       * Test static acceleration
2535       *
2536       * Note: All the assertGreaterThanOrEqual() in this test should be assertGreaterThan() be because of some microtime()
2537       * resolution problems under some OSs / PHP versions, we are accepting equal as valid outcome. For more info see MDL-57147.
2538       */
2539      public function test_static_acceleration() {
2540          $instance = cache_config_testing::instance();
2541          $instance->phpunit_add_definition('phpunit/accelerated', array(
2542              'mode' => cache_store::MODE_APPLICATION,
2543              'component' => 'phpunit',
2544              'area' => 'accelerated',
2545              'staticacceleration' => true,
2546              'staticaccelerationsize' => 3,
2547          ));
2548          $instance->phpunit_add_definition('phpunit/accelerated2', array(
2549              'mode' => cache_store::MODE_APPLICATION,
2550              'component' => 'phpunit',
2551              'area' => 'accelerated2',
2552              'staticacceleration' => true,
2553              'staticaccelerationsize' => 3,
2554          ));
2555          $instance->phpunit_add_definition('phpunit/accelerated3', array(
2556              'mode' => cache_store::MODE_APPLICATION,
2557              'component' => 'phpunit',
2558              'area' => 'accelerated3',
2559              'staticacceleration' => true,
2560              'staticaccelerationsize' => 3,
2561          ));
2562          $instance->phpunit_add_definition('phpunit/accelerated4', array(
2563              'mode' => cache_store::MODE_APPLICATION,
2564              'component' => 'phpunit',
2565              'area' => 'accelerated4',
2566              'staticacceleration' => true,
2567              'staticaccelerationsize' => 4,
2568          ));
2569          $instance->phpunit_add_definition('phpunit/simpledataarea1', array(
2570              'mode' => cache_store::MODE_APPLICATION,
2571              'component' => 'phpunit',
2572              'area' => 'simpledataarea1',
2573              'staticacceleration' => true,
2574              'simpledata' => false
2575          ));
2576          $instance->phpunit_add_definition('phpunit/simpledataarea2', array(
2577              'mode' => cache_store::MODE_APPLICATION,
2578              'component' => 'phpunit',
2579              'area' => 'simpledataarea2',
2580              'staticacceleration' => true,
2581              'simpledata' => true
2582          ));
2583  
2584          $cache = cache::make('phpunit', 'accelerated');
2585          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2586  
2587          // Set and get three elements.
2588          $this->assertTrue($cache->set('a', 'A'));
2589          $this->assertTrue($cache->set('b', 'B'));
2590          $this->assertTrue($cache->set('c', 'C'));
2591          $this->assertEquals('A', $cache->get('a'));
2592          $this->assertEquals(array('b' => 'B', 'c' => 'C'), $cache->get_many(array('b', 'c')));
2593  
2594          // Make sure all items are in static acceleration array.
2595          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2596          $this->assertEquals('B', $cache->phpunit_static_acceleration_get('b'));
2597          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2598  
2599          // Add new value and make sure it is in cache and it is in array.
2600          $this->assertTrue($cache->set('d', 'D'));
2601          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2602          $this->assertEquals('D', $cache->get('d'));
2603  
2604          // Now the least recent accessed item (a) is no longer in acceleration array.
2605          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2606          $this->assertEquals('B', $cache->phpunit_static_acceleration_get('b'));
2607          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2608  
2609          // Adding and deleting element.
2610          $this->assertTrue($cache->set('a', 'A'));
2611          $this->assertTrue($cache->delete('a'));
2612          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2613          $this->assertFalse($cache->has('a'));
2614  
2615          // Make sure "purge" deletes from the array as well.
2616          $cache->purge();
2617          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2618          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2619          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2620          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2621          $this->assertFalse($cache->phpunit_static_acceleration_get('e'));
2622  
2623          // Check that the array holds the last accessed items by get/set.
2624          $this->assertTrue($cache->set('a', 'A'));
2625          $this->assertTrue($cache->set('b', 'B'));
2626          $this->assertTrue($cache->set('c', 'C'));
2627          $this->assertTrue($cache->set('d', 'D'));
2628          $this->assertTrue($cache->set('e', 'E'));
2629          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2630          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2631          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2632          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2633          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2634  
2635          // Store a cacheable_object, get many times and ensure each time wake_for_cache is used.
2636          // Both get and get_many are tested.  Two cache entries are used to ensure the times aren't
2637          // confused with multiple calls to get()/get_many().
2638          $startmicrotime = microtime(true);
2639          $cacheableobject = new cache_phpunit_dummy_object(1, 1, $startmicrotime);
2640          $cacheableobject2 = new cache_phpunit_dummy_object(2, 2, $startmicrotime);
2641          $this->assertTrue($cache->set('a', $cacheableobject));
2642          $this->assertTrue($cache->set('b', $cacheableobject2));
2643          $staticaccelerationreturntime = $cache->phpunit_static_acceleration_get('a')->propertytime;
2644          $staticaccelerationreturntimeb = $cache->phpunit_static_acceleration_get('b')->propertytime;
2645          $this->assertGreaterThanOrEqual($startmicrotime, $staticaccelerationreturntime, 'Restore time of static must be newer.');
2646  
2647          // Reset the static cache without resetting backing store.
2648          $cache->phpunit_static_acceleration_purge();
2649  
2650          // Get the value from the backend store, populating the static cache.
2651          $cachevalue = $cache->get('a');
2652          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $cachevalue);
2653          $this->assertGreaterThanOrEqual($staticaccelerationreturntime, $cachevalue->propertytime);
2654          $backingstorereturntime = $cachevalue->propertytime;
2655  
2656          $results = $cache->get_many(array('b'));
2657          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $results['b']);
2658          $this->assertGreaterThanOrEqual($staticaccelerationreturntimeb, $results['b']->propertytime);
2659          $backingstorereturntimeb = $results['b']->propertytime;
2660  
2661          // Obtain the value again and confirm that static cache is using wake_from_cache.
2662          // Upon failure, the times are not adjusted as wake_from_cache is skipped as the
2663          // value is stored serialized in the static acceleration cache.
2664          $cachevalue = $cache->phpunit_static_acceleration_get('a');
2665          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $cachevalue);
2666          $this->assertGreaterThanOrEqual($backingstorereturntime, $cachevalue->propertytime);
2667  
2668          $results = $cache->get_many(array('b'));
2669          $this->assertInstanceOf(cache_phpunit_dummy_object::class, $results['b']);
2670          $this->assertGreaterThanOrEqual($backingstorereturntimeb, $results['b']->propertytime);
2671  
2672          /** @var cache_phpunit_application $cache */
2673          $cache = cache::make('phpunit', 'accelerated2');
2674          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2675  
2676          // Check that the array holds the last accessed items by get/set.
2677          $this->assertTrue($cache->set('a', 'A'));
2678          $this->assertTrue($cache->set('b', 'B'));
2679          $this->assertTrue($cache->set('c', 'C'));
2680          $this->assertTrue($cache->set('d', 'D'));
2681          $this->assertTrue($cache->set('e', 'E'));
2682          // Current keys in the array: c, d, e.
2683          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2684          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2685          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2686          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2687          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2688  
2689          $this->assertEquals('A', $cache->get('a'));
2690          // Current keys in the array: d, e, a.
2691          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2692          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2693          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2694          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2695          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2696  
2697          // Current keys in the array: d, e, a.
2698          $this->assertEquals(array('c' => 'C'), $cache->get_many(array('c')));
2699          // Current keys in the array: e, a, c.
2700          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2701          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2702          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2703          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2704          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2705  
2706  
2707          $cache = cache::make('phpunit', 'accelerated3');
2708          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2709  
2710          // Check that the array holds the last accessed items by get/set.
2711          $this->assertTrue($cache->set('a', 'A'));
2712          $this->assertTrue($cache->set('b', 'B'));
2713          $this->assertTrue($cache->set('c', 'C'));
2714          $this->assertTrue($cache->set('d', 'D'));
2715          $this->assertTrue($cache->set('e', 'E'));
2716          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2717          $this->assertFalse($cache->phpunit_static_acceleration_get('b'));
2718          $this->assertEquals('C', $cache->phpunit_static_acceleration_get('c'));
2719          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2720          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2721  
2722          $this->assertTrue($cache->set('b', 'B2'));
2723          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2724          $this->assertEquals('B2', $cache->phpunit_static_acceleration_get('b'));
2725          $this->assertFalse($cache->phpunit_static_acceleration_get('c'));
2726          $this->assertEquals('D', $cache->phpunit_static_acceleration_get('d'));
2727          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2728  
2729          $this->assertEquals(2, $cache->set_many(array('b' => 'B3', 'c' => 'C3')));
2730          $this->assertFalse($cache->phpunit_static_acceleration_get('a'));
2731          $this->assertEquals('B3', $cache->phpunit_static_acceleration_get('b'));
2732          $this->assertEquals('C3', $cache->phpunit_static_acceleration_get('c'));
2733          $this->assertFalse($cache->phpunit_static_acceleration_get('d'));
2734          $this->assertEquals('E', $cache->phpunit_static_acceleration_get('e'));
2735  
2736          $cache = cache::make('phpunit', 'accelerated4');
2737          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
2738          $this->assertTrue($cache->set('a', 'A'));
2739          $this->assertTrue($cache->set('a', 'A'));
2740          $this->assertTrue($cache->set('a', 'A'));
2741          $this->assertTrue($cache->set('a', 'A'));
2742          $this->assertTrue($cache->set('a', 'A'));
2743          $this->assertTrue($cache->set('a', 'A'));
2744          $this->assertTrue($cache->set('a', 'A'));
2745          $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
2746          $this->assertEquals('A', $cache->get('a'));
2747  
2748          // Setting simpledata to false objects are cloned when retrieving data.
2749          $cache = cache::make('phpunit', 'simpledataarea1');
2750          $notreallysimple = new \stdClass();
2751          $notreallysimple->name = 'a';
2752          $cache->set('a', $notreallysimple);
2753          $returnedinstance1 = $cache->get('a');
2754          $returnedinstance2 = $cache->get('a');
2755          $returnedinstance1->name = 'b';
2756          $this->assertEquals('a', $returnedinstance2->name);
2757  
2758          // Setting simpledata to true we assume that data does not contain references.
2759          $cache = cache::make('phpunit', 'simpledataarea2');
2760          $notreallysimple = new \stdClass();
2761          $notreallysimple->name = 'a';
2762          $cache->set('a', $notreallysimple);
2763          $returnedinstance1 = $cache->get('a');
2764          $returnedinstance2 = $cache->get('a');
2765          $returnedinstance1->name = 'b';
2766          $this->assertEquals('b', $returnedinstance2->name);
2767      }
2768  
2769      public function test_identifiers_have_separate_caches() {
2770          $cachepg = cache::make('core', 'databasemeta', array('dbfamily' => 'pgsql'));
2771          $cachepg->set(1, 'here');
2772          $cachemy = cache::make('core', 'databasemeta', array('dbfamily' => 'mysql'));
2773          $cachemy->set(2, 'there');
2774          $this->assertEquals('here', $cachepg->get(1));
2775          $this->assertEquals('there', $cachemy->get(2));
2776          $this->assertFalse($cachemy->get(1));
2777      }
2778  
2779      public function test_performance_debug() {
2780          global $CFG;
2781          $this->resetAfterTest(true);
2782          $CFG->perfdebug = 15;
2783  
2784          $instance = cache_config_testing::instance();
2785          $applicationid = 'phpunit/applicationperf';
2786          $instance->phpunit_add_definition($applicationid, array(
2787              'mode' => cache_store::MODE_APPLICATION,
2788              'component' => 'phpunit',
2789              'area' => 'applicationperf'
2790          ));
2791          $sessionid = 'phpunit/sessionperf';
2792          $instance->phpunit_add_definition($sessionid, array(
2793              'mode' => cache_store::MODE_SESSION,
2794              'component' => 'phpunit',
2795              'area' => 'sessionperf'
2796          ));
2797          $requestid = 'phpunit/requestperf';
2798          $instance->phpunit_add_definition($requestid, array(
2799              'mode' => cache_store::MODE_REQUEST,
2800              'component' => 'phpunit',
2801              'area' => 'requestperf'
2802          ));
2803  
2804          $application = cache::make('phpunit', 'applicationperf');
2805          $session = cache::make('phpunit', 'sessionperf');
2806          $request = cache::make('phpunit', 'requestperf');
2807  
2808          // Check that no stats are recorded for these definitions yet.
2809          $stats = cache_helper::get_stats();
2810          $this->assertArrayNotHasKey($applicationid, $stats);
2811          $this->assertArrayHasKey($sessionid, $stats);       // Session cache sets a key on construct.
2812          $this->assertArrayNotHasKey($requestid, $stats);
2813  
2814          // Check that stores register misses.
2815          $this->assertFalse($application->get('missMe'));
2816          $this->assertFalse($application->get('missMe'));
2817          $this->assertFalse($session->get('missMe'));
2818          $this->assertFalse($session->get('missMe'));
2819          $this->assertFalse($session->get('missMe'));
2820          $this->assertFalse($request->get('missMe'));
2821          $this->assertFalse($request->get('missMe'));
2822          $this->assertFalse($request->get('missMe'));
2823          $this->assertFalse($request->get('missMe'));
2824  
2825          $endstats = cache_helper::get_stats();
2826          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['misses']);
2827          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits']);
2828          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets']);
2829          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['misses']);
2830          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits']);
2831          $this->assertEquals(1, $endstats[$sessionid]['stores']['default_session']['sets']);
2832          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['misses']);
2833          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits']);
2834          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets']);
2835  
2836          $startstats = cache_helper::get_stats();
2837  
2838          // Check that stores register sets.
2839          $this->assertTrue($application->set('setMe1', 1));
2840          $this->assertTrue($application->set('setMe2', 2));
2841          $this->assertTrue($session->set('setMe1', 1));
2842          $this->assertTrue($session->set('setMe2', 2));
2843          $this->assertTrue($session->set('setMe3', 3));
2844          $this->assertTrue($request->set('setMe1', 1));
2845          $this->assertTrue($request->set('setMe2', 2));
2846          $this->assertTrue($request->set('setMe3', 3));
2847          $this->assertTrue($request->set('setMe4', 4));
2848  
2849          $endstats = cache_helper::get_stats();
2850          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2851                               $startstats[$applicationid]['stores']['default_application']['misses']);
2852          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
2853                               $startstats[$applicationid]['stores']['default_application']['hits']);
2854          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
2855                               $startstats[$applicationid]['stores']['default_application']['sets']);
2856          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2857                               $startstats[$sessionid]['stores']['default_session']['misses']);
2858          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
2859                               $startstats[$sessionid]['stores']['default_session']['hits']);
2860          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
2861                               $startstats[$sessionid]['stores']['default_session']['sets']);
2862          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2863                               $startstats[$requestid]['stores']['default_request']['misses']);
2864          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
2865                               $startstats[$requestid]['stores']['default_request']['hits']);
2866          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
2867                               $startstats[$requestid]['stores']['default_request']['sets']);
2868  
2869          $startstats = cache_helper::get_stats();
2870  
2871          // Check that stores register hits.
2872          $this->assertEquals($application->get('setMe1'), 1);
2873          $this->assertEquals($application->get('setMe2'), 2);
2874          $this->assertEquals($session->get('setMe1'), 1);
2875          $this->assertEquals($session->get('setMe2'), 2);
2876          $this->assertEquals($session->get('setMe3'), 3);
2877          $this->assertEquals($request->get('setMe1'), 1);
2878          $this->assertEquals($request->get('setMe2'), 2);
2879          $this->assertEquals($request->get('setMe3'), 3);
2880          $this->assertEquals($request->get('setMe4'), 4);
2881  
2882          $endstats = cache_helper::get_stats();
2883          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2884                               $startstats[$applicationid]['stores']['default_application']['misses']);
2885          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
2886                               $startstats[$applicationid]['stores']['default_application']['hits']);
2887          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
2888                               $startstats[$applicationid]['stores']['default_application']['sets']);
2889          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2890                               $startstats[$sessionid]['stores']['default_session']['misses']);
2891          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
2892                               $startstats[$sessionid]['stores']['default_session']['hits']);
2893          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
2894                               $startstats[$sessionid]['stores']['default_session']['sets']);
2895          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2896                               $startstats[$requestid]['stores']['default_request']['misses']);
2897          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
2898                               $startstats[$requestid]['stores']['default_request']['hits']);
2899          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
2900                               $startstats[$requestid]['stores']['default_request']['sets']);
2901  
2902          $startstats = cache_helper::get_stats();
2903  
2904          // Check that stores register through get_many.
2905          $application->get_many(array('setMe1', 'setMe2'));
2906          $session->get_many(array('setMe1', 'setMe2', 'setMe3'));
2907          $request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
2908  
2909          $endstats = cache_helper::get_stats();
2910          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2911                               $startstats[$applicationid]['stores']['default_application']['misses']);
2912          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
2913                               $startstats[$applicationid]['stores']['default_application']['hits']);
2914          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
2915                               $startstats[$applicationid]['stores']['default_application']['sets']);
2916          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2917                               $startstats[$sessionid]['stores']['default_session']['misses']);
2918          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
2919                               $startstats[$sessionid]['stores']['default_session']['hits']);
2920          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
2921                               $startstats[$sessionid]['stores']['default_session']['sets']);
2922          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2923                               $startstats[$requestid]['stores']['default_request']['misses']);
2924          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
2925                               $startstats[$requestid]['stores']['default_request']['hits']);
2926          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
2927                               $startstats[$requestid]['stores']['default_request']['sets']);
2928  
2929          $startstats = cache_helper::get_stats();
2930  
2931          // Check that stores register through set_many.
2932          $this->assertEquals(2, $application->set_many(['setMe1' => 1, 'setMe2' => 2]));
2933          $this->assertEquals(3, $session->set_many(['setMe1' => 1, 'setMe2' => 2, 'setMe3' => 3]));
2934          $this->assertEquals(4, $request->set_many(['setMe1' => 1, 'setMe2' => 2, 'setMe3' => 3, 'setMe4' => 4]));
2935  
2936          $endstats = cache_helper::get_stats();
2937  
2938          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
2939              $startstats[$applicationid]['stores']['default_application']['misses']);
2940          $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
2941              $startstats[$applicationid]['stores']['default_application']['hits']);
2942          $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
2943              $startstats[$applicationid]['stores']['default_application']['sets']);
2944          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
2945              $startstats[$sessionid]['stores']['default_session']['misses']);
2946          $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
2947              $startstats[$sessionid]['stores']['default_session']['hits']);
2948          $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
2949              $startstats[$sessionid]['stores']['default_session']['sets']);
2950          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
2951              $startstats[$requestid]['stores']['default_request']['misses']);
2952          $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
2953              $startstats[$requestid]['stores']['default_request']['hits']);
2954          $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
2955              $startstats[$requestid]['stores']['default_request']['sets']);
2956      }
2957  
2958      /**
2959       * Data provider for static acceleration performance tests.
2960       *
2961       * @return array
2962       */
2963      public function static_acceleration_performance_provider(): array {
2964          // Note: These are the delta values, not the absolute values.
2965          // Also note that the set will actually store the valuein the static cache immediately.
2966          $validfirst = [
2967              'default_application' => [
2968                  'hits' => 1,
2969                  'misses' => 0,
2970              ],
2971              cache_store::STATIC_ACCEL => [
2972                  'hits' => 0,
2973                  'misses' => 1,
2974              ],
2975          ];
2976  
2977          $validsecond = [
2978              'default_application' => [
2979                  'hits' => 0,
2980                  'misses' => 0,
2981              ],
2982              cache_store::STATIC_ACCEL => [
2983                  'hits' => 1,
2984                  'misses' => 0,
2985              ],
2986          ];
2987  
2988          $invalidfirst = [
2989              'default_application' => [
2990                  'hits' => 0,
2991                  'misses' => 1,
2992              ],
2993              cache_store::STATIC_ACCEL => [
2994                  'hits' => 0,
2995                  'misses' => 1,
2996              ],
2997          ];
2998          $invalidsecond = [
2999              'default_application' => [
3000                  'hits' => 0,
3001                  'misses' => 1,
3002              ],
3003              cache_store::STATIC_ACCEL => [
3004                  'hits' => 0,
3005                  'misses' => 1,
3006              ],
3007          ];;
3008  
3009          return [
3010              'Truthy' => [
3011                  true,
3012                  $validfirst,
3013                  $validsecond,
3014              ],
3015              'Null' => [
3016                  null,
3017                  $validfirst,
3018                  $validsecond,
3019              ],
3020              'Empty Array' => [
3021                  [],
3022                  $validfirst,
3023                  $validsecond,
3024              ],
3025              'Empty String' => [
3026                  '',
3027                  $validfirst,
3028                  $validsecond,
3029              ],
3030              'False' => [
3031                  false,
3032                  $invalidfirst,
3033                  $invalidsecond,
3034              ],
3035          ];
3036      }
3037  
3038      /**
3039       * Test performance of static acceleration caches with values which are frequently confused with missing values.
3040       *
3041       * @dataProvider static_acceleration_performance_provider
3042       * @param mixed $value The value to test
3043       * @param array $firstfetchstats The expected stats on the first fetch
3044       * @param array $secondfetchstats The expected stats on the subsequent fetch
3045       */
3046      public function test_static_acceleration_values_performance(
3047          $value,
3048          array $firstfetchstats,
3049          array $secondfetchstats,
3050      ): void {
3051          // Note: We need to modify perfdebug to test this.
3052          global $CFG;
3053          $this->resetAfterTest(true);
3054          $CFG->perfdebug = 15;
3055  
3056          $instance = cache_config_testing::instance();
3057          $instance->phpunit_add_definition('phpunit/accelerated', [
3058              'mode' => cache_store::MODE_APPLICATION,
3059              'component' => 'phpunit',
3060              'area' => 'accelerated',
3061              'staticacceleration' => true,
3062              'staticaccelerationsize' => 1,
3063          ]);
3064  
3065          $cache = cache::make('phpunit', 'accelerated');
3066          $this->assertInstanceOf(cache_phpunit_application::class, $cache);
3067  
3068          $this->assertTrue($cache->set('value', $value));
3069  
3070          $checkstats = function(
3071              array $start,
3072              array $expectedstats,
3073          ): array {
3074              $applicationid = 'phpunit/accelerated';
3075              $endstats = cache_helper::get_stats();
3076  
3077              $start = $start[$applicationid]['stores'];
3078              $end = $endstats[$applicationid]['stores'];
3079  
3080              foreach ($expectedstats as $cachename => $expected) {
3081                  foreach ($expected as $type => $value) {
3082                      $startvalue = array_key_exists($cachename, $start) ? $start[$cachename][$type] : 0;
3083                      $endvalue = array_key_exists($cachename, $end) ? $end[$cachename][$type] : 0;
3084                      $diff = $endvalue - $startvalue;
3085                      $this->assertEquals(
3086                          $value,
3087                          $diff,
3088                          "Expected $cachename $type to be $value, got $diff",
3089                      );
3090                  }
3091              }
3092  
3093              return $endstats;
3094          };
3095  
3096          // Reset the cache factory so that we can get the stats from a fresh instance.
3097          $factory = cache_factory::instance();
3098          $factory->reset_cache_instances();
3099          $cache = cache::make('phpunit', 'accelerated');
3100  
3101          // Get the initial stats.
3102          $startstats = cache_helper::get_stats();
3103  
3104          // Fetching the value the first time should seed the static cache from the application cache.
3105          $this->assertEquals($value, $cache->get('value'));
3106          $startstats = $checkstats($startstats, $firstfetchstats);
3107  
3108          // Fetching the value should only hit the static cache.
3109          $this->assertEquals($value, $cache->get('value'));
3110          $checkstats($startstats, $secondfetchstats);
3111      }
3112  
3113  
3114      public function test_static_cache() {
3115          global $CFG;
3116          $this->resetAfterTest(true);
3117          $CFG->perfdebug = 15;
3118  
3119          // Create cache store with static acceleration.
3120          $instance = cache_config_testing::instance();
3121          $applicationid = 'phpunit/applicationperf';
3122          $instance->phpunit_add_definition($applicationid, array(
3123              'mode' => cache_store::MODE_APPLICATION,
3124              'component' => 'phpunit',
3125              'area' => 'applicationperf',
3126              'simplekeys' => true,
3127              'staticacceleration' => true,
3128              'staticaccelerationsize' => 3
3129          ));
3130  
3131          $application = cache::make('phpunit', 'applicationperf');
3132  
3133          // Check that stores register sets.
3134          $this->assertTrue($application->set('setMe1', 1));
3135          $this->assertTrue($application->set('setMe2', 0));
3136          $this->assertTrue($application->set('setMe3', array()));
3137          $this->assertTrue($application->get('setMe1') !== false);
3138          $this->assertTrue($application->get('setMe2') !== false);
3139          $this->assertTrue($application->get('setMe3') !== false);
3140  
3141          // Check that the static acceleration worked, even on empty arrays and the number 0.
3142          $endstats = cache_helper::get_stats();
3143          $this->assertEquals(0, $endstats[$applicationid]['stores']['** static accel. **']['misses']);
3144          $this->assertEquals(3, $endstats[$applicationid]['stores']['** static accel. **']['hits']);
3145      }
3146  
3147      public function test_performance_debug_off() {
3148          global $CFG;
3149          $this->resetAfterTest(true);
3150          $CFG->perfdebug = 7;
3151  
3152          $instance = cache_config_testing::instance();
3153          $applicationid = 'phpunit/applicationperfoff';
3154          $instance->phpunit_add_definition($applicationid, array(
3155              'mode' => cache_store::MODE_APPLICATION,
3156              'component' => 'phpunit',
3157              'area' => 'applicationperfoff'
3158          ));
3159          $sessionid = 'phpunit/sessionperfoff';
3160          $instance->phpunit_add_definition($sessionid, array(
3161              'mode' => cache_store::MODE_SESSION,
3162              'component' => 'phpunit',
3163              'area' => 'sessionperfoff'
3164          ));
3165          $requestid = 'phpunit/requestperfoff';
3166          $instance->phpunit_add_definition($requestid, array(
3167              'mode' => cache_store::MODE_REQUEST,
3168              'component' => 'phpunit',
3169              'area' => 'requestperfoff'
3170          ));
3171  
3172          $application = cache::make('phpunit', 'applicationperfoff');
3173          $session = cache::make('phpunit', 'sessionperfoff');
3174          $request = cache::make('phpunit', 'requestperfoff');
3175  
3176          // Check that no stats are recorded for these definitions yet.
3177          $stats = cache_helper::get_stats();
3178          $this->assertArrayNotHasKey($applicationid, $stats);
3179          $this->assertArrayNotHasKey($sessionid, $stats);
3180          $this->assertArrayNotHasKey($requestid, $stats);
3181  
3182          // Trigger cache misses, cache sets and cache hits.
3183          $this->assertFalse($application->get('missMe'));
3184          $this->assertTrue($application->set('setMe', 1));
3185          $this->assertEquals(1, $application->get('setMe'));
3186          $this->assertFalse($session->get('missMe'));
3187          $this->assertTrue($session->set('setMe', 3));
3188          $this->assertEquals(3, $session->get('setMe'));
3189          $this->assertFalse($request->get('missMe'));
3190          $this->assertTrue($request->set('setMe', 4));
3191          $this->assertEquals(4, $request->get('setMe'));
3192  
3193          // Check that no stats are being recorded for these definitions.
3194          $endstats = cache_helper::get_stats();
3195          $this->assertArrayNotHasKey($applicationid, $endstats);
3196          $this->assertArrayNotHasKey($sessionid, $endstats);
3197          $this->assertArrayNotHasKey($requestid, $endstats);
3198      }
3199  
3200      /**
3201       * Tests session cache event purge and subsequent visit in the same request.
3202       *
3203       * This test simulates a cache being created, a value being set, then the value being purged.
3204       * A subsequent use of the same cache is started in the same request which fills the cache.
3205       * A new request is started a short time later.
3206       * The cache should be filled.
3207       */
3208      public function test_session_event_purge_same_second() {
3209          $instance = cache_config_testing::instance();
3210          $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
3211              'mode' => cache_store::MODE_SESSION,
3212              'component' => 'phpunit',
3213              'area' => 'eventpurgetest',
3214              'invalidationevents' => array(
3215                  'crazyevent',
3216              )
3217          ));
3218  
3219          // Create the cache, set a value, and immediately purge it by event.
3220          $cache = cache::make('phpunit', 'eventpurgetest');
3221          $cache->set('testkey1', 'test data 1');
3222          $this->assertEquals('test data 1', $cache->get('testkey1'));
3223          cache_helper::purge_by_event('crazyevent');
3224          $this->assertFalse($cache->get('testkey1'));
3225  
3226          // Set up the cache again in the same request and add a new value back in.
3227          $factory = cache_factory::instance();
3228          $factory->reset_cache_instances();
3229          $cache = cache::make('phpunit', 'eventpurgetest');
3230          $cache->set('testkey1', 'test data 2');
3231          $this->assertEquals('test data 2', $cache->get('testkey1'));
3232  
3233          // Trick the cache into thinking that this is a new request.
3234          cache_phpunit_cache::simulate_new_request();
3235          $factory = cache_factory::instance();
3236          $factory->reset_cache_instances();
3237  
3238          // Set up the cache again.
3239          // This is a subsequent request at a new time, so we instead the invalidation time will be checked.
3240          // The invalidation time should match the last purged time and the cache will not be re-purged.
3241          $cache = cache::make('phpunit', 'eventpurgetest');
3242          $this->assertEquals('test data 2', $cache->get('testkey1'));
3243      }
3244  
3245      /**
3246       * Test that values set in different sessions are stored with different key prefixes.
3247       */
3248      public function test_session_distinct_storage_key() {
3249          $this->resetAfterTest();
3250  
3251          // Prepare a dummy session cache configuration.
3252          $config = cache_config_testing::instance();
3253          $config->phpunit_add_definition('phpunit/test_session_distinct_storage_key', array(
3254              'mode' => cache_store::MODE_SESSION,
3255              'component' => 'phpunit',
3256              'area' => 'test_session_distinct_storage_key'
3257          ));
3258  
3259          // First anonymous user's session cache.
3260          cache_phpunit_session::phpunit_mockup_session_id('foo');
3261          $this->setUser(0);
3262          $cache1 = cache::make('phpunit', 'test_session_distinct_storage_key');
3263  
3264          // Reset cache instances to emulate a new request.
3265          cache_factory::instance()->reset_cache_instances();
3266  
3267          // Another anonymous user's session cache.
3268          cache_phpunit_session::phpunit_mockup_session_id('bar');
3269          $this->setUser(0);
3270          $cache2 = cache::make('phpunit', 'test_session_distinct_storage_key');
3271  
3272          cache_factory::instance()->reset_cache_instances();
3273  
3274          // Guest user's session cache.
3275          cache_phpunit_session::phpunit_mockup_session_id('baz');
3276          $this->setGuestUser();
3277          $cache3 = cache::make('phpunit', 'test_session_distinct_storage_key');
3278  
3279          cache_factory::instance()->reset_cache_instances();
3280  
3281          // Same guest user's session cache but in another browser window.
3282          cache_phpunit_session::phpunit_mockup_session_id('baz');
3283          $this->setGuestUser();
3284          $cache4 = cache::make('phpunit', 'test_session_distinct_storage_key');
3285  
3286          // Assert that different PHP session implies different key prefix for storing values.
3287          $this->assertNotEquals($cache1->phpunit_get_key_prefix(), $cache2->phpunit_get_key_prefix());
3288  
3289          // Assert that same PHP session implies same key prefix for storing values.
3290          $this->assertEquals($cache3->phpunit_get_key_prefix(), $cache4->phpunit_get_key_prefix());
3291      }
3292  }