Skip to content

Commit 888c0d3

Browse files
committed
Fix withGroup test
1 parent f59a7e1 commit 888c0d3

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

packages/cache/test/TaggedCachePoolTest.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Windwalker\Cache\CachePool;
1111
use Windwalker\Cache\TaggedCachePool;
1212
use Windwalker\Cache\Exception\RuntimeException;
13+
use Windwalker\Cache\Serializer\PhpSerializer;
1314
use Windwalker\Cache\Storage\ArrayStorage;
1415

1516
class TaggedCachePoolTest extends TestCase
@@ -1080,6 +1081,136 @@ public function testDeleteItemAlsoRemovesEnvelope(): void
10801081
self::assertFalse($tagStorage->has($envKeyWithoutTags), 'Envelope should be deleted with item');
10811082
}
10821083

1084+
// -----------------------------------------------------------------------
1085+
// withGroup propagation
1086+
// -----------------------------------------------------------------------
1087+
1088+
/**
1089+
* Regression: withGroup() must propagate the group to the tagPool even when the tagPool
1090+
* is a plain CachePool (not a TaggedCachePool).
1091+
*
1092+
* Before the fix the condition was `$new->tagPool instanceof self`, which was always false
1093+
* for a plain CachePool tagPool, so the group was never applied to it.
1094+
* As a result every group-scoped pool shared the *same* ungrouped tagPool, meaning that
1095+
* invalidating a tag in one group would silently bust items in all other groups.
1096+
*
1097+
* @see TaggedCachePool::withGroup
1098+
*/
1099+
public function testWithGroupPropagatesGroupToPlainCachePoolTagPool(): void
1100+
{
1101+
// Passing an ArrayStorage lets TaggedCachePool wrap it in a CachePool with PhpSerializer
1102+
// (via applyTagPool). That internal tagPool is a plain CachePool – not a TaggedCachePool –
1103+
// which is the case that was broken before the fix.
1104+
$pool = new TaggedCachePool(new ArrayStorage(0.0), tagPool: new ArrayStorage(0.0));
1105+
1106+
$g1 = $pool->withGroup('g1');
1107+
$g2 = $pool->withGroup('g2');
1108+
1109+
$calls = ['g1' => 0, 'g2' => 0];
1110+
1111+
// Populate both groups with tagged items.
1112+
$g1->fetch('item', function ($item) use (&$calls) {
1113+
$calls['g1']++;
1114+
$item->tags('users');
1115+
1116+
return 'G1_V1';
1117+
}, 3600, 0.0, false);
1118+
1119+
$g2->fetch('item', function ($item) use (&$calls) {
1120+
$calls['g2']++;
1121+
$item->tags('users');
1122+
1123+
return 'G2_V1';
1124+
}, 3600, 0.0, false);
1125+
1126+
self::assertSame(1, $calls['g1']);
1127+
self::assertSame(1, $calls['g2']);
1128+
1129+
// Invalidate the 'users' tag *only* in group g1.
1130+
$g1->invalidateTags('users');
1131+
1132+
// g1 must recompute (its tag was invalidated).
1133+
$result = $g1->fetch('item', function ($item) use (&$calls) {
1134+
$calls['g1']++;
1135+
$item->tags('users');
1136+
1137+
return 'G1_V2';
1138+
}, 3600, 0.0, false);
1139+
1140+
self::assertSame('G1_V2', $result, 'g1 item must be recomputed after tag invalidation');
1141+
self::assertSame(2, $calls['g1']);
1142+
1143+
// g2 must NOT be affected — its tagPool is independently scoped.
1144+
$result = $g2->fetch('item', function ($item) use (&$calls) {
1145+
$calls['g2']++;
1146+
$item->tags('users');
1147+
1148+
return 'G2_V2';
1149+
}, 3600, 0.0, false);
1150+
1151+
self::assertSame(
1152+
'G2_V1',
1153+
$result,
1154+
'g2 item must NOT be busted when the tag is invalidated in g1 only'
1155+
);
1156+
self::assertSame(
1157+
1,
1158+
$calls['g2'],
1159+
'g2 handler must not be called: group isolation requires a separate tagPool per group'
1160+
);
1161+
}
1162+
1163+
/**
1164+
* When the tagPool itself is a TaggedCachePool the group must still be propagated
1165+
* (the existing `instanceof self` path that was always correct).
1166+
*
1167+
* @see TaggedCachePool::withGroup
1168+
*/
1169+
public function testWithGroupPropagatesGroupWhenTagPoolIsTaggedCachePool(): void
1170+
{
1171+
// TaggedCachePool used as tagPool must use PhpSerializer so it can store
1172+
// the array tag-envelope produced by the outer TaggedCachePool.
1173+
$tagPool = new TaggedCachePool(new ArrayStorage(0.0), serializer: new PhpSerializer());
1174+
$pool = new TaggedCachePool(new ArrayStorage(0.0), tagPool: $tagPool);
1175+
$g1 = $pool->withGroup('g1');
1176+
$g2 = $pool->withGroup('g2');
1177+
1178+
$calls = ['g1' => 0, 'g2' => 0];
1179+
1180+
$g1->fetch('key', function ($item) use (&$calls) {
1181+
$calls['g1']++;
1182+
$item->tags('t');
1183+
1184+
return 'G1_V1';
1185+
}, 3600, 0.0, false);
1186+
1187+
$g2->fetch('key', function ($item) use (&$calls) {
1188+
$calls['g2']++;
1189+
$item->tags('t');
1190+
1191+
return 'G2_V1';
1192+
}, 3600, 0.0, false);
1193+
1194+
$g1->invalidateTags('t');
1195+
1196+
$g1->fetch('key', function ($item) use (&$calls) {
1197+
$calls['g1']++;
1198+
$item->tags('t');
1199+
1200+
return 'G1_V2';
1201+
}, 3600, 0.0, false);
1202+
1203+
$g2->fetch('key', function ($item) use (&$calls) {
1204+
$calls['g2']++;
1205+
$item->tags('t');
1206+
1207+
return 'G2_V2';
1208+
}, 3600, 0.0, false);
1209+
1210+
self::assertSame(2, $calls['g1'], 'g1 must recompute after tag invalidation');
1211+
self::assertSame(1, $calls['g2'], 'g2 must not be affected by g1 tag invalidation');
1212+
}
1213+
10831214
/** @see CachePool::getMultiple — respects missing envelopes */
10841215
public function testGetMultipleRespectsEnvelopeLogic(): void
10851216
{

0 commit comments

Comments
 (0)