Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

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