Fixed bug #11903: Use separate tables for tags in the caching framework
[Packages/TYPO3.CMS.git] / tests / t3lib / cache / backend / t3lib_cache_backend_dbbackendtestcase.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Ingo Renner <ingo@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25
26 // TODO implement autoloading so that we only require stuff we really need
27 require_once(PATH_t3lib . 'class.t3lib_cache.php');
28
29 require_once(PATH_t3lib . 'cache/backend/interfaces/interface.t3lib_cache_backend_backend.php');
30 require_once(PATH_t3lib . 'cache/frontend/interfaces/interface.t3lib_cache_frontend_frontend.php');
31
32 require_once(PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_abstractbackend.php');
33 require_once(PATH_t3lib . 'cache/frontend/class.t3lib_cache_frontend_abstractfrontend.php');
34 require_once(PATH_t3lib . 'cache/class.t3lib_cache_exception.php');
35 require_once(PATH_t3lib . 'cache/class.t3lib_cache_factory.php');
36 require_once(PATH_t3lib . 'cache/class.t3lib_cache_manager.php');
37 require_once(PATH_t3lib . 'cache/frontend/class.t3lib_cache_frontend_variablefrontend.php');
38
39 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_classalreadyloaded.php');
40 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_duplicateidentifier.php');
41 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_invalidbackend.php');
42 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_invalidcache.php');
43 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_invaliddata.php');
44 require_once(PATH_t3lib . 'cache/exception/class.t3lib_cache_exception_nosuchcache.php');
45
46 require_once(PATH_t3lib . 'cache/backend/class.t3lib_cache_backend_dbbackend.php');
47
48 /**
49 * Testcase for the DB cache backend
50 *
51 * @author Ingo Renner <ingo@typo3.org>
52 * @package TYPO3
53 * @subpackage tests
54 * @version $Id$
55 */
56 class t3lib_cache_backend_DbBackendTestCase extends tx_phpunit_testcase {
57
58 /**
59 * If set, the tearDown() method will clean up the cache table used by this unit test.
60 *
61 * @var t3lib_cache_backend_DbBackend
62 */
63 protected $backend;
64
65 protected $testingCacheTable;
66 protected $testingTagsTable;
67
68 /**
69 * Sets up this testcase
70 *
71 * @return void
72 * @author Ingo Renner <ingo@typo3.org>
73 */
74 public function setUp() {
75 $this->testingCacheTable = 'test_cache_dbbackend';
76 $this->testingTagsTable = 'test_cache_dbbackend_tags';
77
78 $GLOBALS['TYPO3_DB']->sql_query('CREATE TABLE ' . $this->testingCacheTable . ' (
79 id int(11) unsigned NOT NULL auto_increment,
80 identifier varchar(250) DEFAULT \'\' NOT NULL,
81 crdate int(11) unsigned DEFAULT \'0\' NOT NULL,
82 content mediumtext,
83 tags mediumtext,
84 lifetime int(11) unsigned DEFAULT \'0\' NOT NULL,
85 PRIMARY KEY (id),
86 KEY cache_id (identifier)
87 ) ENGINE=InnoDB;
88 ');
89
90 $GLOBALS['TYPO3_DB']->sql_query('CREATE TABLE ' . $this->testingTagsTable . ' (
91 id int(11) unsigned NOT NULL auto_increment,
92 identifier varchar(128) DEFAULT \'\' NOT NULL,
93 tag varchar(128) DEFAULT \'\' NOT NULL,
94 PRIMARY KEY (id),
95 KEY cache_id (identifier),
96 KEY cache_tag (tag)
97 ) ENGINE=InnoDB;
98 ');
99
100 $this->backend = t3lib_div::makeInstance(
101 't3lib_cache_backend_DbBackend',
102 array(
103 'cacheTable' => $this->testingCacheTable,
104 'tagsTable' => $this->testingTagsTable,
105 )
106 );
107 }
108
109 /**
110 * @test
111 * @expectedException t3lib_cache_Exception
112 * @author Ingo Renner <ingo@typo3.org>
113 */
114 # deactivated as the according check in the DB backend causes trouble during TYPO3's initialization
115 # public function setCacheTableThrowsExceptionOnNonExistentTable() {
116 # $this->backend->setCacheTable('test_cache_non_existent_table');
117 # }
118
119 /**
120 * @test
121 * @author Ingo Renner <ingo@typo3.org>
122 */
123 public function getCacheTableReturnsThePreviouslySetTable() {
124 $this->backend->setCacheTable($this->testingCacheTable);
125 $this->assertEquals($this->testingCacheTable, $this->backend->getCacheTable(), 'getCacheTable() did not return the expected value.');
126 }
127
128 /**
129 * @test
130 * @expectedException t3lib_cache_exception_InvalidData
131 * @author Ingo Renner <ingo@typo3.org>
132 */
133 public function setThrowsExceptionIfDataIsNotAString() {
134 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
135 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
136 array(),
137 '',
138 FALSE
139 );
140
141 $data = array('Some data');
142 $entryIdentifier = 'BackendDbTest';
143
144 $this->backend->setCache($cache);
145
146 $this->backend->set($entryIdentifier, $data);
147 }
148
149 /**
150 * @test
151 * @author Ingo Renner <ingo@typo3.org>
152 */
153 public function setReallySavesToTheSpecifiedTable() {
154 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
155 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
156 array(),
157 '',
158 FALSE
159 );
160
161 $data = 'some data' . microtime();
162 $entryIdentifier = 'BackendDbTest';
163
164 $this->backend->setCache($cache);
165 $this->backend->set($entryIdentifier, $data);
166
167 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
168 '*',
169 $this->testingCacheTable,
170 'identifier = \'' . $entryIdentifier . '\''
171 );
172
173 $this->assertEquals(
174 $data,
175 $entriesFound[0]['content'],
176 'The original and the retrieved data don\'t match.'
177 );
178 }
179
180 /**
181 * @test
182 * @author Ingo Renner <ingo@typo3.org>
183 */
184 public function setRemovesAnAlreadyExistingCacheEntryForTheSameIdentifier() {
185 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
186 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
187 array(),
188 '',
189 FALSE
190 );
191
192 $data1 = 'some data' . microtime();
193 $data2 = 'some data' . microtime();
194 $entryIdentifier = 'BackendDbRemoveBeforeSetTest';
195
196 $this->backend->setCache($cache);
197 $this->backend->set($entryIdentifier, $data1, array(), 500);
198 // setting a second entry with the same identifier, but different
199 // data, this should _replace_ the existing one we set before
200 $this->backend->set($entryIdentifier, $data2, array(), 200);
201
202 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
203 '*',
204 $this->testingCacheTable,
205 'identifier = \'' . $entryIdentifier . '\''
206 );
207
208 $this->assertEquals(1, count($entriesFound), 'There was not exactly one cache entry.');
209 }
210
211 /**
212 * @test
213 * @author Ingo Renner <ingo@typo3.org>
214 */
215 public function setReallySavesSpecifiedTags() {
216 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
217 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
218 array(),
219 '',
220 FALSE
221 );
222
223 $data = 'some data' . microtime();
224 $entryIdentifier = 'BackendDbTest';
225
226 $this->backend->setCache($cache);
227 $this->backend->set($entryIdentifier, $data, array('UnitTestTag%tag1', 'UnitTestTag%tag2'));
228
229 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
230 '*',
231 $this->testingTagsTable,
232 'identifier = \'' . $entryIdentifier . '\''
233 );
234
235 $tags = array();
236
237 foreach ($entriesFound as $entry) {
238 $tags[] = $entry['tag'];
239 }
240
241 $this->assertTrue(count($tags) > 0, 'The tags do not exist.');
242 $this->assertTrue(in_array('UnitTestTag%tag1', $tags), 'Tag UnitTestTag%tag1 does not exist.');
243 $this->assertTrue(in_array('UnitTestTag%tag2', $tags), 'Tag UnitTestTag%tag2 does not exist.');
244 }
245
246 /**
247 * @test
248 * @author Ingo Renner <ingo@typo3.org>
249 */
250 public function getReturnsContentOfTheCorrectCacheEntry() {
251 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
252 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
253 array(),
254 '',
255 FALSE
256 );
257
258 $data = 'some data' . microtime();
259 $entryIdentifier = 'BackendDbTest';
260
261 $this->backend->setCache($cache);
262 $this->backend->set($entryIdentifier, $data, array(), 500);
263
264 $data = 'some other data' . microtime();
265 $this->backend->set($entryIdentifier, $data, array(), 100);
266
267 $loadedData = $this->backend->get($entryIdentifier);
268
269 $this->assertEquals($data, $loadedData, 'The original and the retrieved data don\'t match.');
270 }
271
272 /**
273 * @test
274 * @author Ingo Renner <ingo@typo3.org>
275 */
276 public function hasReturnsTheCorrectResult() {
277 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
278 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
279 array(),
280 '',
281 FALSE
282 );
283
284 $data = 'some data' . microtime();
285 $entryIdentifier = 'BackendDbTest';
286
287 $this->backend->setCache($cache);
288 $this->backend->set($entryIdentifier, $data);
289
290 $this->assertTrue($this->backend->has($entryIdentifier), 'has() did not return TRUE.');
291 $this->assertFalse($this->backend->has($entryIdentifier . 'Not'), 'has() did not return FALSE.');
292 }
293
294 /**
295 * @test
296 * @author Ingo Renner <ingo@typo3.org>
297 */
298 public function removeReallyRemovesACacheEntry() {
299 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
300 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
301 array(),
302 '',
303 FALSE
304 );
305
306 $data = 'some data' . microtime();
307 $entryIdentifier = 'BackendDbRemovalTest';
308
309 $this->backend->setCache($cache);
310 $this->backend->set($entryIdentifier, $data);
311
312 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
313 '*',
314 $this->testingCacheTable,
315 'identifier = \'' . $entryIdentifier . '\''
316 );
317
318 $this->assertTrue(is_array($entriesFound) && count($entriesFound) > 0, 'The cache entry does not exist.');
319
320 $this->backend->remove($entryIdentifier);
321
322 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
323 '*',
324 $this->testingCacheTable,
325 'identifier = \'' . $entryIdentifier . '\''
326 );
327
328 $this->assertTrue(count($entriesFound) == 0, 'The cache entry still exists.');
329 }
330
331 /**
332 * @test
333 * @author Ingo Renner <ingo@typo3.org>
334 */
335 public function collectGarbageReallyRemovesAnExpiredCacheEntry() {
336 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
337 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
338 array(),
339 '',
340 FALSE
341 );
342
343 $data = 'some data' . microtime();
344 $entryIdentifier = 'BackendDbRemovalTest';
345
346 $this->backend->setCache($cache);
347 $this->backend->set($entryIdentifier, $data, array(), 1);
348
349 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
350 '*',
351 $this->testingCacheTable,
352 'identifier = \'' . $entryIdentifier . '\''
353 );
354
355 $this->assertTrue(is_array($entriesFound) && count($entriesFound) > 0, 'The cache entry does not exist.');
356
357 sleep(2);
358 $GLOBALS['EXEC_TIME'] += 2;
359 $this->backend->collectGarbage();
360
361 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
362 '*',
363 $this->testingCacheTable,
364 'identifier = \'' . $entryIdentifier . '\''
365 );
366
367 $this->assertTrue(count($entriesFound) == 0, 'The cache entry still exists.');
368 }
369
370 /**
371 * @test
372 * @author Ingo Renner <ingo@typo3.org>
373 */
374 public function collectGarbageReallyRemovesAllExpiredCacheEntries() {
375 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
376 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
377 array(),
378 '',
379 FALSE
380 );
381
382 $data = 'some data' . microtime();
383 $entryIdentifier = 'BackendDbRemovalTest';
384
385 $this->backend->setCache($cache);
386
387 $this->backend->set($entryIdentifier . 'A', $data, array(), 1);
388 $this->backend->set($entryIdentifier . 'B', $data, array(), 1);
389 $this->backend->set($entryIdentifier . 'C', $data, array(), 1);
390
391 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
392 '*',
393 $this->testingCacheTable,
394 ''
395 );
396
397 $this->assertTrue(is_array($entriesFound) && count($entriesFound) > 0, 'The cache entries do not exist.');
398
399 sleep(2);
400 $GLOBALS['EXEC_TIME'] += 2;
401 $this->backend->collectGarbage();
402
403 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
404 '*',
405 $this->testingCacheTable,
406 ''
407 );
408
409 $this->assertTrue(count($entriesFound) == 0, 'The cache entries still exist.');
410 }
411
412 /**
413 * @test
414 * @author Ingo Renner <ingo@typo3.org>
415 */
416 public function findIdentifiersByTagFindsCacheEntriesWithSpecifiedTag() {
417 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
418 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
419 array(),
420 '',
421 FALSE
422 );
423
424 $this->backend->setCache($cache);
425
426 $data = 'some data' . microtime();
427 $this->backend->set('BackendDbTest1', $data, array('UnitTestTag%test', 'UnitTestTag%boring'));
428 $this->backend->set('BackendDbTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special'));
429 $this->backend->set('BackendDbTest3', $data, array('UnitTestTag%test'));
430
431 $expectedEntry = 'BackendDbTest2';
432
433 $actualEntries = $this->backend->findIdentifiersByTag('UnitTestTag%special');
434 $this->assertTrue(is_array($actualEntries), 'actualEntries is not an array.');
435
436 $this->assertEquals($expectedEntry, array_pop($actualEntries));
437 }
438
439 /**
440 * @test
441 * @author Ingo Renner <ingo@typo3.org>
442 */
443 public function flushRemovesAllCacheEntries() {
444 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
445 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
446 array(),
447 '',
448 FALSE
449 );
450
451 $this->backend->setCache($cache);
452
453 $data = 'some data' . microtime();
454 $this->backend->set('BackendDbTest1', $data, array('UnitTestTag%test'));
455 $this->backend->set('BackendDbTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special'));
456 $this->backend->set('BackendDbTest3', $data, array('UnitTestTag%test'));
457
458 $this->backend->flush();
459
460 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
461 '*',
462 $this->testingCacheTable,
463 ''
464 );
465
466 $this->assertTrue(count($entriesFound) == 0, 'Still entries in the cache table');
467 }
468
469 /**
470 * @test
471 * @author Ingo Renner <ingo@typo3.org>
472 */
473 public function flushByTagRemovesCacheEntriesWithSpecifiedTag() {
474 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
475 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
476 array(),
477 '',
478 FALSE
479 );
480
481 $this->backend->setCache($cache);
482
483 $data = 'some data' . microtime();
484 $this->backend->set('BackendDbTest1', $data, array('UnitTestTag%test', 'UnitTestTag%boring'));
485 $this->backend->set('BackendDbTest2', $data, array('UnitTestTag%test', 'UnitTestTag%special'));
486 $this->backend->set('BackendDbTest3', $data, array('UnitTestTag%test'));
487
488 $this->backend->flushByTag('UnitTestTag%special');
489
490 $this->assertTrue($this->backend->has('BackendDbTest1'), 'BackendDbTest1 does not exist anymore.');
491 $this->assertFalse($this->backend->has('BackendDbTest2'), 'BackendDbTest2 still exists.');
492 $this->assertTrue($this->backend->has('BackendDbTest3'), 'BackendDbTest3 does not exist anymore.');
493 }
494
495
496 /**
497 * @test
498 * @author Ingo Renner <ingo@typo3.org>
499 */
500 public function hasReturnsTheCorrectResultForEntryWithExceededLifetime() {
501 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
502 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
503 array(),
504 '',
505 FALSE
506 );
507
508 $data = 'some data' . microtime();
509 $entryIdentifier = 'BackendDbTest';
510
511 $this->backend->setCache($cache);
512 $this->backend->set($entryIdentifier, $data);
513
514 $expiredEntryIdentifier = 'ExpiredBackendDbTest';
515 $expiredData = 'some old data' . microtime();
516 $this->backend->set($expiredEntryIdentifier, $expiredData, array(), 1);
517
518 sleep(2);
519 $GLOBALS['EXEC_TIME'] += 2;
520
521 $this->assertFalse($this->backend->has($expiredEntryIdentifier), 'has() did not return FALSE.');
522 }
523
524 /**
525 * @test
526 * @author Ingo Renner <ingo@typo3.org>
527 */
528 public function getReturnsFalseForEntryWithExceededLifetime() {
529 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
530 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
531 array(),
532 '',
533 FALSE
534 );
535
536 $data = 'some data' . microtime();
537 $entryIdentifier = 'BackendDbTest';
538
539 $this->backend->setCache($cache);
540 $this->backend->set($entryIdentifier, $data);
541
542 $expiredEntryIdentifier = 'ExpiredBackendDbTest';
543 $expiredData = 'some old data' . microtime();
544 $this->backend->set($expiredEntryIdentifier, $expiredData, array(), 1);
545
546 sleep(2);
547 $GLOBALS['EXEC_TIME'] += 2;
548
549 $this->assertEquals($data, $this->backend->get($entryIdentifier), 'The original and the retrieved data don\'t match.');
550 $this->assertFalse($this->backend->get($expiredEntryIdentifier), 'The expired entry could be loaded.');
551 }
552
553 /**
554 * @test
555 * @author Ingo Renner <ingo@typo3.org>
556 */
557 public function findIdentifiersByTagReturnsEmptyArrayForEntryWithExceededLifetime() {
558 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
559 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
560 array(),
561 '',
562 FALSE
563 );
564
565 $this->backend->setCache($cache);
566 $this->backend->set('BackendDbTest', 'some data', array('UnitTestTag%special'), 1);
567
568 sleep(2);
569 $GLOBALS['EXEC_TIME'] += 2;
570 // Not required, but used to update the pre-calculated queries:
571 $this->backend->setTagsTable($this->testingTagsTable);
572
573 $this->assertEquals(array(), $this->backend->findIdentifiersByTag('UnitTestTag%special'));
574 }
575
576 /**
577 * @test
578 * @author Ingo Renner <ingo@typo3.org>
579 */
580 public function setWithUnlimitedLifetimeWritesCorrectEntry() {
581 $cache = $this->getMock('t3lib_cache_frontend_AbstractFrontend',
582 array('getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove'),
583 array(),
584 '',
585 FALSE
586 );
587
588 $data = 'some data' . microtime();
589 $entryIdentifier = 'BackendFileTest';
590
591 $this->backend->setCache($cache);
592 $this->backend->set($entryIdentifier, $data, array(), 0);
593
594 $entriesFound = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
595 '*',
596 $this->testingCacheTable,
597 ''
598 );
599
600 $this->assertTrue(is_array($entriesFound), 'entriesFound is not an array.');
601
602 $retrievedData = $entriesFound[0]['content'];
603 $this->assertEquals($data, $retrievedData, 'The original and the retrieved data don\'t match.');
604 }
605
606
607 /**
608 * @author Ingo Renner <ingo@typo3.org>
609 */
610 public function tearDown() {
611 $GLOBALS['TYPO3_DB']->sql_query(
612 'DROP TABLE ' . $this->testingCacheTable . ';'
613 );
614
615 $GLOBALS['TYPO3_DB']->sql_query(
616 'DROP TABLE ' . $this->testingTagsTable . ';'
617 );
618 }
619
620 }
621
622 ?>