class VariationCacheTest in VariationCache 8
@coversDefaultClass \Drupal\variationcache\Cache\VariationCache @group Cache
Hierarchy
- class \Drupal\Tests\UnitTestCase extends \PHPUnit\Framework\TestCase uses PhpunitCompatibilityTrait
- class \Drupal\Tests\variationcache\Unit\VariationCacheTest
Expanded class hierarchy of VariationCacheTest
File
- tests/
src/ Unit/ VariationCacheTest.php, line 19
Namespace
Drupal\Tests\variationcache\UnitView source
class VariationCacheTest extends UnitTestCase {
/**
* The prophesized request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack|\Prophecy\Prophecy\ProphecyInterface
*/
protected $requestStack;
/**
* The backend used by the variation cache.
*
* @var \Drupal\Core\Cache\MemoryBackend
*/
protected $memoryBackend;
/**
* The prophesized cache contexts manager.
*
* @var \Drupal\Core\Cache\Context\CacheContextsManager|\Prophecy\Prophecy\ProphecyInterface
*/
protected $cacheContextsManager;
/**
* The variation cache instance.
*
* @var \Drupal\variationcache\Cache\VariationCacheInterface
*/
protected $variationCache;
/**
* The cache keys this test will store things under.
*
* @var string[]
*/
protected $cacheKeys = [
'your',
'housing',
'situation',
];
/**
* The cache ID for the cache keys, without taking contexts into account.
*
* @var string
*/
protected $cacheIdBase = 'your:housing:situation';
/**
* The simulated current user's housing type.
*
* For use in tests with cache contexts.
*
* @var string
*/
protected $housingType;
/**
* The cacheability for something that only varies per housing type.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $housingTypeCacheability;
/**
* The simulated current user's garden type.
*
* For use in tests with cache contexts.
*
* @var string
*/
protected $gardenType;
/**
* The cacheability for something that varies per housing and garden type.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $gardenTypeCacheability;
/**
* The simulated current user's house's orientation.
*
* For use in tests with cache contexts.
*
* @var string
*/
protected $houseOrientation;
/**
* The cacheability for varying per housing, garden and orientation.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $houseOrientationCacheability;
/**
* The simulated current user's solar panel type.
*
* For use in tests with cache contexts.
*
* @var string
*/
protected $solarType;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->requestStack = $this
->prophesize(RequestStack::class);
$this->memoryBackend = new MemoryBackend();
$this->cacheContextsManager = $this
->prophesize(CacheContextsManager::class);
$housing_type =& $this->housingType;
$garden_type =& $this->gardenType;
$house_orientation =& $this->houseOrientation;
$solar_type =& $this->solarType;
$this->cacheContextsManager
->convertTokensToKeys(Argument::any())
->will(function ($args) use (&$housing_type, &$garden_type, &$house_orientation, &$solar_type) {
$keys = [];
foreach ($args[0] as $context_id) {
switch ($context_id) {
case 'house.type':
$keys[] = "ht.{$housing_type}";
break;
case 'garden.type':
$keys[] = "gt.{$garden_type}";
break;
case 'house.orientation':
$keys[] = "ho.{$house_orientation}";
break;
case 'solar.type':
$keys[] = "st.{$solar_type}";
break;
default:
$keys[] = $context_id;
}
}
return new ContextCacheKeys($keys);
});
$this->variationCache = new VariationCache($this->requestStack
->reveal(), $this->memoryBackend, $this->cacheContextsManager
->reveal());
$this->housingTypeCacheability = (new CacheableMetadata())
->setCacheTags([
'foo',
])
->setCacheContexts([
'house.type',
]);
$this->gardenTypeCacheability = (new CacheableMetadata())
->setCacheTags([
'bar',
])
->setCacheContexts([
'house.type',
'garden.type',
]);
$this->houseOrientationCacheability = (new CacheableMetadata())
->setCacheTags([
'baz',
])
->setCacheContexts([
'house.type',
'garden.type',
'house.orientation',
]);
}
/**
* Tests a cache item that has no variations.
*
* @covers ::get
* @covers ::set
*/
public function testNoVariations() {
$data = 'You have a nice house!';
$cacheability = (new CacheableMetadata())
->setCacheTags([
'bar',
'foo',
]);
$initial_cacheability = (new CacheableMetadata())
->setCacheTags([
'foo',
]);
$this
->setVariationCacheItem($data, $cacheability, $initial_cacheability);
$this
->assertVariationCacheItem($data, $cacheability, $initial_cacheability);
}
/**
* Tests a cache item that only ever varies by one context.
*
* @covers ::get
* @covers ::set
*/
public function testSingleVariation() {
$cacheability = $this->housingTypeCacheability;
$house_data = [
'apartment' => 'You have a nice apartment',
'house' => 'You have a nice house',
];
foreach ($house_data as $housing_type => $data) {
$this->housingType = $housing_type;
$this
->assertVariationCacheMiss($cacheability);
$this
->setVariationCacheItem($data, $cacheability, $cacheability);
$this
->assertVariationCacheItem($data, $cacheability, $cacheability);
$this
->assertCacheBackendItem("{$this->cacheIdBase}:ht.{$housing_type}", $data, $cacheability);
}
}
/**
* Tests a cache item that has nested variations.
*
* @covers ::get
* @covers ::set
*/
public function testNestedVariations() {
// We are running this scenario in the best possible outcome: The redirects
// are stored in expanding order, meaning the simplest one is stored first
// and the nested ones are stored in subsequent ::set() calls. This means no
// self-healing takes place where overly specific redirects are overwritten
// with simpler ones.
$possible_outcomes = [
'apartment' => 'You have a nice apartment!',
'house|no-garden' => 'You have a nice house!',
'house|garden|east' => 'You have a nice house with an east-facing garden!',
'house|garden|south' => 'You have a nice house with a south-facing garden!',
'house|garden|west' => 'You have a nice house with a west-facing garden!',
'house|garden|north' => 'You have a nice house with a north-facing garden!',
];
foreach ($possible_outcomes as $cache_context_values => $data) {
list($this->housingType, $this->gardenType, $this->houseOrientation) = explode('|', $cache_context_values . '||');
$cacheability = $this->housingTypeCacheability;
if (!empty($this->houseOrientation)) {
$cacheability = $this->houseOrientationCacheability;
}
elseif (!empty($this->gardenType)) {
$cacheability = $this->gardenTypeCacheability;
}
$this
->assertVariationCacheMiss($this->housingTypeCacheability);
$this
->setVariationCacheItem($data, $cacheability, $this->housingTypeCacheability);
$this
->assertVariationCacheItem($data, $cacheability, $this->housingTypeCacheability);
$cache_id = "{$this->cacheIdBase}:ht.{$this->housingType}";
if (!empty($this->gardenType)) {
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($this->gardenTypeCacheability));
$cache_id .= ":gt.{$this->gardenType}";
}
if (!empty($this->houseOrientation)) {
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($this->houseOrientationCacheability));
$cache_id .= ":ho.{$this->houseOrientation}";
}
$this
->assertCacheBackendItem($cache_id, $data, $cacheability);
}
}
/**
* Tests a cache item that has nested variations that trigger self-healing.
*
* @covers ::get
* @covers ::set
*
* @depends testNestedVariations
*/
public function testNestedVariationsSelfHealing() {
// This is the worst possible scenario: A very specific item was stored
// first, followed by a less specific one. This means an overly specific
// cache redirect was stored that needs to be dumbed down. After this
// process, the first ::get() for the more specific item will fail as we
// have effectively destroyed the path to said item. Setting an item of the
// same specificity will restore the path for all items of said specificity.
$cache_id = "{$this->cacheIdBase}:ht.house";
$possible_outcomes = [
'house|garden|east' => 'You have a nice house with an east-facing garden!',
'house|garden|south' => 'You have a nice house with a south-facing garden!',
'house|garden|west' => 'You have a nice house with a west-facing garden!',
'house|garden|north' => 'You have a nice house with a north-facing garden!',
];
foreach ($possible_outcomes as $cache_context_values => $data) {
list($this->housingType, $this->gardenType, $this->houseOrientation) = explode('|', $cache_context_values . '||');
$this
->setVariationCacheItem($data, $this->houseOrientationCacheability, $this->housingTypeCacheability);
}
// Verify that the overly specific redirect is stored at the first possible
// redirect location, i.e.: The base cache ID.
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($this->houseOrientationCacheability));
// Store a simpler variation and verify that the first cache redirect is now
// the one redirecting to the simplest known outcome.
list($this->housingType, $this->gardenType, $this->houseOrientation) = [
'house',
'no-garden',
NULL,
];
$this
->setVariationCacheItem('You have a nice house', $this->gardenTypeCacheability, $this->housingTypeCacheability);
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($this->gardenTypeCacheability));
// Verify that the previously set outcomes are all inaccessible now.
foreach ($possible_outcomes as $cache_context_values => $data) {
list($this->housingType, $this->gardenType, $this->houseOrientation) = explode('|', $cache_context_values . '||');
$this
->assertVariationCacheMiss($this->housingTypeCacheability);
}
// Set at least one more specific item in the cache again.
$this
->setVariationCacheItem($data, $this->houseOrientationCacheability, $this->housingTypeCacheability);
// Verify that the previously set outcomes are all accessible again.
foreach ($possible_outcomes as $cache_context_values => $data) {
list($this->housingType, $this->gardenType, $this->houseOrientation) = explode('|', $cache_context_values . '||');
$this
->assertVariationCacheItem($data, $this->houseOrientationCacheability, $this->housingTypeCacheability);
}
// Verify that the more specific cache redirect is now stored one step after
// the less specific one.
$this
->assertCacheBackendItem("{$cache_id}:gt.garden", new CacheRedirect($this->houseOrientationCacheability));
}
/**
* Tests self-healing for a cache item that has split variations.
*
* @covers ::get
* @covers ::set
*/
public function testSplitVariationsSelfHealing() {
// This is an edge case. Something varies by AB where some values of B
// trigger the whole to vary by either C, D or nothing extra. But due to an
// unfortunate series of requests, only ABC and ABD variations were cached.
//
// In this case, the cache should be smart enough to generate a redirect for
// AB, followed by redirects for ABC and ABD.
//
// For the sake of this test, we'll vary by housing and orientation, but:
// - Only vary by garden type for south-facing houses.
// - Only vary by solar panel type for north-facing houses.
$cache_id = "{$this->cacheIdBase}:ht.house";
$this->housingType = 'house';
$this->gardenType = 'garden';
$this->solarType = 'solar';
$initial_cacheability = (new CacheableMetadata())
->setCacheTags([
'foo',
])
->setCacheContexts([
'house.type',
]);
$south_cacheability = (new CacheableMetadata())
->setCacheTags([
'foo',
])
->setCacheContexts([
'house.type',
'house.orientation',
'garden.type',
]);
$north_cacheability = (new CacheableMetadata())
->setCacheTags([
'foo',
])
->setCacheContexts([
'house.type',
'house.orientation',
'solar.type',
]);
$common_cacheability = (new CacheableMetadata())
->setCacheContexts([
'house.type',
'house.orientation',
]);
// Set the first scenario.
$this->houseOrientation = 'south';
$this
->setVariationCacheItem('You have a south-facing house with a garden!', $south_cacheability, $initial_cacheability);
// Verify that the overly specific redirect is stored at the first possible
// redirect location, i.e.: The base cache ID.
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($south_cacheability));
// Store a split variation, and verify that the common contexts are now used
// for the first cache redirect and the actual contexts for the next step of
// the redirect chain.
$this->houseOrientation = 'north';
$this
->setVariationCacheItem('You have a north-facing house with solar panels!', $north_cacheability, $initial_cacheability);
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($common_cacheability));
$this
->assertCacheBackendItem("{$cache_id}:ho.north", new CacheRedirect($north_cacheability));
// Verify that the initially set scenario is inaccessible now.
$this->houseOrientation = 'south';
$this
->assertVariationCacheMiss($initial_cacheability);
// Reset the initial scenario and verify that its redirects are accessible.
$this
->setVariationCacheItem('You have a south-facing house with a garden!', $south_cacheability, $initial_cacheability);
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($common_cacheability));
$this
->assertCacheBackendItem("{$cache_id}:ho.south", new CacheRedirect($south_cacheability));
// Double-check that the split scenario redirects are left untouched.
$this->houseOrientation = 'north';
$this
->assertCacheBackendItem($cache_id, new CacheRedirect($common_cacheability));
$this
->assertCacheBackendItem("{$cache_id}:ho.north", new CacheRedirect($north_cacheability));
}
/**
* Tests exception for a cache item that has incompatible variations.
*
* @covers ::get
* @covers ::set
*/
public function testIncompatibleVariationsException() {
// This should never happen. When someone first stores something in the
// cache using context A and then tries to store something using context B,
// something is wrong. There should always be at least one shared context at
// the top level or else the cache cannot do its job.
$this
->setExpectedException(\LogicException::class, "The complete set of cache contexts for a variation cache item must contain all of the initial cache contexts.");
$this->housingType = 'house';
$house_cacheability = (new CacheableMetadata())
->setCacheContexts([
'house.type',
]);
$this->gardenType = 'garden';
$garden_cacheability = (new CacheableMetadata())
->setCacheContexts([
'garden.type',
]);
$this
->setVariationCacheItem('You have a nice garden!', $garden_cacheability, $garden_cacheability);
$this
->setVariationCacheItem('You have a nice house!', $house_cacheability, $garden_cacheability);
}
/**
* Stores an item in the variation cache.
*
* @param mixed $data
* The data that should be stored.
* @param \Drupal\Core\Cache\CacheableMetadata $cacheability
* The cacheability that should be used.
* @param \Drupal\Core\Cache\CacheableMetadata $initial_cacheability
* The initial cacheability that should be used.
*/
protected function setVariationCacheItem($data, CacheableMetadata $cacheability, CacheableMetadata $initial_cacheability) {
$this->variationCache
->set($this->cacheKeys, $data, $cacheability, $initial_cacheability);
}
/**
* Asserts that an item was properly stored in the variation cache.
*
* @param mixed $data
* The data that should have been stored.
* @param \Drupal\Core\Cache\CacheableMetadata $cacheability
* The cacheability that should have been used.
* @param \Drupal\Core\Cache\CacheableMetadata $initial_cacheability
* The initial cacheability that should be used.
*/
protected function assertVariationCacheItem($data, CacheableMetadata $cacheability, CacheableMetadata $initial_cacheability) {
$cache_item = $this->variationCache
->get($this->cacheKeys, $initial_cacheability);
$this
->assertNotFalse($cache_item, 'Variable data was stored and retrieved successfully.');
$this
->assertEquals($data, $cache_item->data, 'Variable cache item contains the right data.');
$this
->assertSame($cacheability
->getCacheTags(), $cache_item->tags, 'Variable cache item uses the right cache tags.');
}
/**
* Asserts that an item could not be retrieved from the variation cache.
*
* @param \Drupal\Core\Cache\CacheableMetadata $initial_cacheability
* The initial cacheability that should be used.
*/
protected function assertVariationCacheMiss(CacheableMetadata $initial_cacheability) {
$this
->assertFalse($this->variationCache
->get($this->cacheKeys, $initial_cacheability), 'Nothing could be retrieved for the active cache contexts.');
}
/**
* Asserts that an item was properly stored in the cache backend.
*
* @param string $cid
* The cache ID that should have been used.
* @param mixed $data
* The data that should have been stored.
* @param \Drupal\Core\Cache\CacheableMetadata|null $cacheability
* (optional) The cacheability that should have been used. Does not apply
* when checking for cache redirects.
*/
protected function assertCacheBackendItem($cid, $data, CacheableMetadata $cacheability = NULL) {
$cache_backend_item = $this->memoryBackend
->get($cid);
$this
->assertNotFalse($cache_backend_item, 'The data was stored and retrieved successfully.');
$this
->assertEquals($data, $cache_backend_item->data, 'Cache item contains the right data.');
if ($data instanceof CacheRedirect) {
$this
->assertSame([], $cache_backend_item->tags, 'A cache redirect does not use cache tags.');
$this
->assertSame(-1, $cache_backend_item->expire, 'A cache redirect is stored indefinitely.');
}
else {
$this
->assertSame($cacheability
->getCacheTags(), $cache_backend_item->tags, 'Cache item uses the right cache tags.');
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
PhpunitCompatibilityTrait:: |
public | function | Returns a mock object for the specified class using the available method. | |
PhpunitCompatibilityTrait:: |
public | function | Compatibility layer for PHPUnit 6 to support PHPUnit 4 code. | |
UnitTestCase:: |
protected | property | The random generator. | |
UnitTestCase:: |
protected | property | The app root. | 1 |
UnitTestCase:: |
protected | function | Asserts if two arrays are equal by sorting them first. | |
UnitTestCase:: |
protected | function | Mocks a block with a block plugin. | 1 |
UnitTestCase:: |
protected | function | Returns a stub class resolver. | |
UnitTestCase:: |
public | function | Returns a stub config factory that behaves according to the passed array. | |
UnitTestCase:: |
public | function | Returns a stub config storage that returns the supplied configuration. | |
UnitTestCase:: |
protected | function | Sets up a container with a cache tags invalidator. | |
UnitTestCase:: |
protected | function | Gets the random generator for the utility methods. | |
UnitTestCase:: |
public | function | Returns a stub translation manager that just returns the passed string. | |
UnitTestCase:: |
public | function | Generates a unique random string containing letters and numbers. | |
VariationCacheTest:: |
protected | property | The prophesized cache contexts manager. | |
VariationCacheTest:: |
protected | property | The cache ID for the cache keys, without taking contexts into account. | |
VariationCacheTest:: |
protected | property | The cache keys this test will store things under. | |
VariationCacheTest:: |
protected | property | The simulated current user's garden type. | |
VariationCacheTest:: |
protected | property | The cacheability for something that varies per housing and garden type. | |
VariationCacheTest:: |
protected | property | The simulated current user's house's orientation. | |
VariationCacheTest:: |
protected | property | The cacheability for varying per housing, garden and orientation. | |
VariationCacheTest:: |
protected | property | The simulated current user's housing type. | |
VariationCacheTest:: |
protected | property | The cacheability for something that only varies per housing type. | |
VariationCacheTest:: |
protected | property | The backend used by the variation cache. | |
VariationCacheTest:: |
protected | property | The prophesized request stack. | |
VariationCacheTest:: |
protected | property | The simulated current user's solar panel type. | |
VariationCacheTest:: |
protected | property | The variation cache instance. | |
VariationCacheTest:: |
protected | function | Asserts that an item was properly stored in the cache backend. | |
VariationCacheTest:: |
protected | function | Asserts that an item was properly stored in the variation cache. | |
VariationCacheTest:: |
protected | function | Asserts that an item could not be retrieved from the variation cache. | |
VariationCacheTest:: |
public | function |
Overrides UnitTestCase:: |
|
VariationCacheTest:: |
protected | function | Stores an item in the variation cache. | |
VariationCacheTest:: |
public | function | Tests exception for a cache item that has incompatible variations. | |
VariationCacheTest:: |
public | function | Tests a cache item that has nested variations. | |
VariationCacheTest:: |
public | function | Tests a cache item that has nested variations that trigger self-healing. | |
VariationCacheTest:: |
public | function | Tests a cache item that has no variations. | |
VariationCacheTest:: |
public | function | Tests a cache item that only ever varies by one context. | |
VariationCacheTest:: |
public | function | Tests self-healing for a cache item that has split variations. |