1<?php 2 3use Wikimedia\TestingAccessWrapper; 4 5/** 6 * @group BagOStuff 7 */ 8class HashBagOStuffTest extends PHPUnit\Framework\TestCase { 9 use MediaWikiCoversValidator; 10 11 /** 12 * @covers HashBagOStuff::__construct 13 */ 14 public function testConstruct() { 15 $this->assertInstanceOf( 16 HashBagOStuff::class, 17 new HashBagOStuff() 18 ); 19 } 20 21 /** 22 * @covers HashBagOStuff::__construct 23 */ 24 public function testQoS() { 25 $bag = new HashBagOStuff(); 26 27 $this->assertSame( 28 BagOStuff::QOS_DURABILITY_SCRIPT, 29 $bag->getQoS( BagOStuff::ATTR_DURABILITY ) 30 ); 31 32 $this->assertSame( 33 BagOStuff::QOS_LOCALITY_PROC, 34 $bag->getQoS( BagOStuff::ATTR_LOCALITY ) 35 ); 36 } 37 38 /** 39 * @covers HashBagOStuff::__construct 40 */ 41 public function testConstructBadZero() { 42 $this->expectException( InvalidArgumentException::class ); 43 $cache = new HashBagOStuff( [ 'maxKeys' => 0 ] ); 44 } 45 46 /** 47 * @covers HashBagOStuff::__construct 48 */ 49 public function testConstructBadNeg() { 50 $this->expectException( InvalidArgumentException::class ); 51 $cache = new HashBagOStuff( [ 'maxKeys' => -1 ] ); 52 } 53 54 /** 55 * @covers HashBagOStuff::__construct 56 */ 57 public function testConstructBadType() { 58 $this->expectException( InvalidArgumentException::class ); 59 $cache = new HashBagOStuff( [ 'maxKeys' => 'x' ] ); 60 } 61 62 /** 63 * @covers HashBagOStuff::delete 64 */ 65 public function testDelete() { 66 $cache = new HashBagOStuff(); 67 for ( $i = 0; $i < 10; $i++ ) { 68 $cache->set( "key$i", 1 ); 69 $this->assertSame( 1, $cache->get( "key$i" ) ); 70 $cache->delete( "key$i" ); 71 $this->assertFalse( $cache->get( "key$i" ) ); 72 } 73 } 74 75 /** 76 * @covers HashBagOStuff::clear 77 */ 78 public function testClear() { 79 $cache = new HashBagOStuff(); 80 for ( $i = 0; $i < 10; $i++ ) { 81 $cache->set( "key$i", 1 ); 82 $this->assertSame( 1, $cache->get( "key$i" ) ); 83 } 84 $cache->clear(); 85 for ( $i = 0; $i < 10; $i++ ) { 86 $this->assertFalse( $cache->get( "key$i" ) ); 87 } 88 } 89 90 /** 91 * @covers HashBagOStuff::doGet 92 * @covers HashBagOStuff::expire 93 */ 94 public function testExpire() { 95 $cache = new HashBagOStuff(); 96 $cacheInternal = TestingAccessWrapper::newFromObject( $cache ); 97 $cache->set( 'foo', 1 ); 98 $cache->set( 'bar', 1, 10 ); 99 $cache->set( 'baz', 1, -10 ); 100 101 $this->assertSame( 0, $cacheInternal->bag['foo'][$cache::KEY_EXP], 'Indefinite' ); 102 // 2 seconds tolerance 103 $this->assertEqualsWithDelta( 104 time() + 10, 105 $cacheInternal->bag['bar'][$cache::KEY_EXP], 106 2, 107 'Future' 108 ); 109 $this->assertEqualsWithDelta( 110 time() - 10, 111 $cacheInternal->bag['baz'][$cache::KEY_EXP], 112 2, 113 'Past' 114 ); 115 116 $this->assertSame( 1, $cache->get( 'bar' ), 'Key not expired' ); 117 $this->assertFalse( $cache->get( 'baz' ), 'Key expired' ); 118 } 119 120 /** 121 * Ensure maxKeys eviction prefers keeping new keys. 122 * 123 * @covers HashBagOStuff::set 124 */ 125 public function testEvictionAdd() { 126 $cache = new HashBagOStuff( [ 'maxKeys' => 10 ] ); 127 for ( $i = 0; $i < 10; $i++ ) { 128 $cache->set( "key$i", 1 ); 129 $this->assertSame( 1, $cache->get( "key$i" ) ); 130 } 131 for ( $i = 10; $i < 20; $i++ ) { 132 $cache->set( "key$i", 1 ); 133 $this->assertSame( 1, $cache->get( "key$i" ) ); 134 $this->assertFalse( $cache->get( "key" . ( $i - 10 ) ) ); 135 } 136 } 137 138 /** 139 * Ensure maxKeys eviction prefers recently set keys 140 * even if the keys pre-exist. 141 * 142 * @covers HashBagOStuff::set 143 */ 144 public function testEvictionSet() { 145 $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] ); 146 147 foreach ( [ 'foo', 'bar', 'baz' ] as $key ) { 148 $cache->set( $key, 1 ); 149 } 150 151 // Set existing key 152 $cache->set( 'foo', 1 ); 153 154 // Add a 4th key (beyond the allowed maximum) 155 $cache->set( 'quux', 1 ); 156 157 // Foo's life should have been extended over Bar 158 foreach ( [ 'foo', 'baz', 'quux' ] as $key ) { 159 $this->assertSame( 1, $cache->get( $key ), "Kept $key" ); 160 } 161 $this->assertFalse( $cache->get( 'bar' ), 'Evicted bar' ); 162 } 163 164 /** 165 * Ensure maxKeys eviction prefers recently retrieved keys (LRU). 166 * 167 * @covers HashBagOStuff::doGet 168 * @covers HashBagOStuff::hasKey 169 */ 170 public function testEvictionGet() { 171 $cache = new HashBagOStuff( [ 'maxKeys' => 3 ] ); 172 173 foreach ( [ 'foo', 'bar', 'baz' ] as $key ) { 174 $cache->set( $key, 1 ); 175 } 176 177 // Get existing key 178 $cache->get( 'foo', 1 ); 179 180 // Add a 4th key (beyond the allowed maximum) 181 $cache->set( 'quux', 1 ); 182 183 // Foo's life should have been extended over Bar 184 foreach ( [ 'foo', 'baz', 'quux' ] as $key ) { 185 $this->assertSame( 1, $cache->get( $key ), "Kept $key" ); 186 } 187 $this->assertFalse( $cache->get( 'bar' ), 'Evicted bar' ); 188 } 189} 190