aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Memcache
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Memcache')
-rw-r--r--tests/lib/Memcache/APCuTest.php39
-rw-r--r--tests/lib/Memcache/ArrayCacheTest.php21
-rw-r--r--tests/lib/Memcache/Cache.php157
-rw-r--r--tests/lib/Memcache/CasTraitTest.php64
-rw-r--r--tests/lib/Memcache/FactoryTest.php139
-rw-r--r--tests/lib/Memcache/MemcachedTest.php57
-rw-r--r--tests/lib/Memcache/RedisTest.php87
7 files changed, 564 insertions, 0 deletions
diff --git a/tests/lib/Memcache/APCuTest.php b/tests/lib/Memcache/APCuTest.php
new file mode 100644
index 00000000000..199bdf298f6
--- /dev/null
+++ b/tests/lib/Memcache/APCuTest.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\APCu;
+
+/**
+ * @group Memcache
+ * @group APCu
+ */
+class APCuTest extends Cache {
+ protected function setUp(): void {
+ parent::setUp();
+
+ if (!APCu::isAvailable()) {
+ $this->markTestSkipped('The APCu extension is not available.');
+ return;
+ }
+ $this->instance = new APCu($this->getUniqueID());
+ }
+
+ public function testCasIntChanged(): void {
+ $this->instance->set('foo', 1);
+ $this->assertTrue($this->instance->cas('foo', 1, 2));
+ $this->assertEquals(2, $this->instance->get('foo'));
+ }
+
+ public function testCasIntNotChanged(): void {
+ $this->instance->set('foo', 1);
+ $this->assertFalse($this->instance->cas('foo', 2, 3));
+ $this->assertEquals(1, $this->instance->get('foo'));
+ }
+}
diff --git a/tests/lib/Memcache/ArrayCacheTest.php b/tests/lib/Memcache/ArrayCacheTest.php
new file mode 100644
index 00000000000..e71c821729c
--- /dev/null
+++ b/tests/lib/Memcache/ArrayCacheTest.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\ArrayCache;
+
+/**
+ * @group Memcache
+ */
+class ArrayCacheTest extends Cache {
+ protected function setUp(): void {
+ parent::setUp();
+ $this->instance = new ArrayCache('');
+ }
+}
diff --git a/tests/lib/Memcache/Cache.php b/tests/lib/Memcache/Cache.php
new file mode 100644
index 00000000000..b48f5557faa
--- /dev/null
+++ b/tests/lib/Memcache/Cache.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Memcache;
+
+use OCP\IMemcache;
+
+abstract class Cache extends \Test\Cache\TestCache {
+ /**
+ * @var IMemcache cache;
+ */
+ protected $instance;
+
+ public function testExistsAfterSet(): void {
+ $this->assertFalse($this->instance->hasKey('foo'));
+ $this->instance->set('foo', 'bar');
+ $this->assertTrue($this->instance->hasKey('foo'));
+ }
+
+ public function testGetAfterSet(): void {
+ $this->assertNull($this->instance->get('foo'));
+ $this->instance->set('foo', 'bar');
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ }
+
+ public function testGetArrayAfterSet(): void {
+ $this->assertNull($this->instance->get('foo'));
+ $this->instance->set('foo', ['bar']);
+ $this->assertEquals(['bar'], $this->instance->get('foo'));
+ }
+
+ public function testDoesNotExistAfterRemove(): void {
+ $this->instance->set('foo', 'bar');
+ $this->instance->remove('foo');
+ $this->assertFalse($this->instance->hasKey('foo'));
+ }
+
+ public function testRemoveNonExisting(): void {
+ $this->instance->remove('foo');
+ $this->assertFalse($this->instance->hasKey('foo'));
+ }
+
+ public function testArrayAccessSet(): void {
+ $this->instance['foo'] = 'bar';
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ }
+
+ public function testArrayAccessGet(): void {
+ $this->instance->set('foo', 'bar');
+ $this->assertEquals('bar', $this->instance['foo']);
+ }
+
+ public function testArrayAccessExists(): void {
+ $this->assertFalse(isset($this->instance['foo']));
+ $this->instance->set('foo', 'bar');
+ $this->assertTrue(isset($this->instance['foo']));
+ }
+
+ public function testArrayAccessUnset(): void {
+ $this->instance->set('foo', 'bar');
+ unset($this->instance['foo']);
+ $this->assertFalse($this->instance->hasKey('foo'));
+ }
+
+ public function testAdd(): void {
+ $this->assertTrue($this->instance->add('foo', 'bar'));
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ $this->assertFalse($this->instance->add('foo', 'asd'));
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ }
+
+ public function testInc(): void {
+ $this->assertEquals(1, $this->instance->inc('foo'));
+ $this->assertEquals(1, $this->instance->get('foo'));
+ $this->assertEquals(2, $this->instance->inc('foo'));
+ $this->assertEquals(2, $this->instance->get('foo'));
+ $this->assertEquals(12, $this->instance->inc('foo', 10));
+ $this->assertEquals(12, $this->instance->get('foo'));
+
+ $this->instance->set('foo', 'bar');
+ $this->assertFalse($this->instance->inc('foo'));
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ }
+
+ public function testDec(): void {
+ $this->assertFalse($this->instance->dec('foo'));
+ $this->instance->set('foo', 20);
+ $this->assertEquals(19, $this->instance->dec('foo'));
+ $this->assertEquals(19, $this->instance->get('foo'));
+ $this->assertEquals(9, $this->instance->dec('foo', 10));
+
+ $this->instance->set('foo', 'bar');
+ $this->assertFalse($this->instance->dec('foo'));
+ $this->assertEquals('bar', $this->instance->get('foo'));
+ }
+
+ public function testCasNotChanged(): void {
+ $this->instance->set('foo', 'bar');
+ $this->assertTrue($this->instance->cas('foo', 'bar', 'asd'));
+ $this->assertEquals('asd', $this->instance->get('foo'));
+ }
+
+ public function testCasChanged(): void {
+ $this->instance->set('foo', 'bar1');
+ $this->assertFalse($this->instance->cas('foo', 'bar', 'asd'));
+ $this->assertEquals('bar1', $this->instance->get('foo'));
+ }
+
+ public function testCasNotSet(): void {
+ $this->assertFalse($this->instance->cas('foo', 'bar', 'asd'));
+ }
+
+ public function testCadNotChanged(): void {
+ $this->instance->set('foo', 'bar');
+ $this->assertTrue($this->instance->cad('foo', 'bar'));
+ $this->assertFalse($this->instance->hasKey('foo'));
+ }
+
+ public function testCadChanged(): void {
+ $this->instance->set('foo', 'bar1');
+ $this->assertFalse($this->instance->cad('foo', 'bar'));
+ $this->assertTrue($this->instance->hasKey('foo'));
+ }
+
+ public function testCadNotSet(): void {
+ $this->assertFalse($this->instance->cad('foo', 'bar'));
+ }
+
+ public function testNcadNotChanged(): void {
+ $this->instance->set('foo', 'bar');
+ $this->assertFalse($this->instance->ncad('foo', 'bar'));
+ $this->assertTrue($this->instance->hasKey('foo'));
+ }
+
+ public function testNcadChanged(): void {
+ $this->instance->set('foo', 'bar1');
+ $this->assertTrue($this->instance->ncad('foo', 'bar'));
+ $this->assertFalse($this->instance->hasKey('foo'));
+ }
+
+ public function testNcadNotSet(): void {
+ $this->assertFalse($this->instance->ncad('foo', 'bar'));
+ }
+
+ protected function tearDown(): void {
+ if ($this->instance) {
+ $this->instance->clear();
+ }
+
+ parent::tearDown();
+ }
+}
diff --git a/tests/lib/Memcache/CasTraitTest.php b/tests/lib/Memcache/CasTraitTest.php
new file mode 100644
index 00000000000..9de04fa2726
--- /dev/null
+++ b/tests/lib/Memcache/CasTraitTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\ArrayCache;
+use Test\TestCase;
+
+/**
+ * @group Memcache
+ */
+class CasTraitTest extends TestCase {
+ /**
+ * @return \OC\Memcache\CasTrait
+ */
+ private function getCache() {
+ $sourceCache = new ArrayCache();
+ $mock = $this->getMockForTrait('\OC\Memcache\CasTrait');
+
+ $mock->expects($this->any())
+ ->method('set')
+ ->willReturnCallback(function ($key, $value, $ttl) use ($sourceCache) {
+ return $sourceCache->set($key, $value, $ttl);
+ });
+
+ $mock->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($key) use ($sourceCache) {
+ return $sourceCache->get($key);
+ });
+
+ $mock->expects($this->any())
+ ->method('add')
+ ->willReturnCallback(function ($key, $value, $ttl) use ($sourceCache) {
+ return $sourceCache->add($key, $value, $ttl);
+ });
+
+ $mock->expects($this->any())
+ ->method('remove')
+ ->willReturnCallback(function ($key) use ($sourceCache) {
+ return $sourceCache->remove($key);
+ });
+ return $mock;
+ }
+
+ public function testCasNotChanged(): void {
+ $cache = $this->getCache();
+ $cache->set('foo', 'bar');
+ $this->assertTrue($cache->cas('foo', 'bar', 'asd'));
+ $this->assertEquals('asd', $cache->get('foo'));
+ }
+
+ public function testCasChanged(): void {
+ $cache = $this->getCache();
+ $cache->set('foo', 'bar1');
+ $this->assertFalse($cache->cas('foo', 'bar', 'asd'));
+ $this->assertEquals('bar1', $cache->get('foo'));
+ }
+}
diff --git a/tests/lib/Memcache/FactoryTest.php b/tests/lib/Memcache/FactoryTest.php
new file mode 100644
index 00000000000..e16e079349f
--- /dev/null
+++ b/tests/lib/Memcache/FactoryTest.php
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\Factory;
+use OC\Memcache\NullCache;
+use OCP\HintException;
+use OCP\Profiler\IProfiler;
+use Psr\Log\LoggerInterface;
+
+class Test_Factory_Available_Cache1 extends NullCache {
+ public function __construct($prefix = '') {
+ }
+
+ public static function isAvailable(): bool {
+ return true;
+ }
+}
+
+class Test_Factory_Available_Cache2 extends NullCache {
+ public function __construct($prefix = '') {
+ }
+
+ public static function isAvailable(): bool {
+ return true;
+ }
+}
+
+class Test_Factory_Unavailable_Cache1 extends NullCache {
+ public function __construct($prefix = '') {
+ }
+
+ public static function isAvailable(): bool {
+ return false;
+ }
+}
+
+class Test_Factory_Unavailable_Cache2 extends NullCache {
+ public function __construct($prefix = '') {
+ }
+
+ public static function isAvailable(): bool {
+ return false;
+ }
+}
+
+/**
+ * @group Memcache
+ */
+class FactoryTest extends \Test\TestCase {
+ public const AVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Available_Cache1';
+ public const AVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Available_Cache2';
+ public const UNAVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache1';
+ public const UNAVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache2';
+
+ public static function cacheAvailabilityProvider(): array {
+ return [
+ [
+ // local and distributed available
+ self::AVAILABLE1, self::AVAILABLE2, null,
+ self::AVAILABLE1, self::AVAILABLE2, Factory::NULL_CACHE
+ ],
+ [
+ // local and distributed null
+ null, null, null,
+ Factory::NULL_CACHE, Factory::NULL_CACHE, Factory::NULL_CACHE
+ ],
+ [
+ // local available, distributed null (most common scenario)
+ self::AVAILABLE1, null, null,
+ self::AVAILABLE1, self::AVAILABLE1, Factory::NULL_CACHE
+ ],
+ [
+ // locking cache available
+ null, null, self::AVAILABLE1,
+ Factory::NULL_CACHE, Factory::NULL_CACHE, self::AVAILABLE1
+ ],
+ [
+ // locking cache unavailable: no exception here in the factory
+ null, null, self::UNAVAILABLE1,
+ Factory::NULL_CACHE, Factory::NULL_CACHE, Factory::NULL_CACHE
+ ]
+ ];
+ }
+
+ public static function cacheUnavailableProvider(): array {
+ return [
+ [
+ // local available, distributed unavailable
+ self::AVAILABLE1, self::UNAVAILABLE1
+ ],
+ [
+ // local unavailable, distributed available
+ self::UNAVAILABLE1, self::AVAILABLE1
+ ],
+ [
+ // local and distributed unavailable
+ self::UNAVAILABLE1, self::UNAVAILABLE2
+ ],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('cacheAvailabilityProvider')]
+ public function testCacheAvailability($localCache, $distributedCache, $lockingCache,
+ $expectedLocalCache, $expectedDistributedCache, $expectedLockingCache): void {
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $profiler = $this->getMockBuilder(IProfiler::class)->getMock();
+ $factory = new Factory(fn () => 'abc', $logger, $profiler, $localCache, $distributedCache, $lockingCache);
+ $this->assertTrue(is_a($factory->createLocal(), $expectedLocalCache));
+ $this->assertTrue(is_a($factory->createDistributed(), $expectedDistributedCache));
+ $this->assertTrue(is_a($factory->createLocking(), $expectedLockingCache));
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('cacheUnavailableProvider')]
+ public function testCacheNotAvailableException($localCache, $distributedCache): void {
+ $this->expectException(HintException::class);
+
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $profiler = $this->getMockBuilder(IProfiler::class)->getMock();
+ new Factory(fn () => 'abc', $logger, $profiler, $localCache, $distributedCache);
+ }
+
+ public function testCreateInMemory(): void {
+ $logger = $this->getMockBuilder(LoggerInterface::class)->getMock();
+ $profiler = $this->getMockBuilder(IProfiler::class)->getMock();
+ $factory = new Factory(fn () => 'abc', $logger, $profiler, null, null, null);
+
+ $cache = $factory->createInMemory();
+ $cache->set('test', 48);
+
+ self::assertSame(48, $cache->get('test'));
+ }
+}
diff --git a/tests/lib/Memcache/MemcachedTest.php b/tests/lib/Memcache/MemcachedTest.php
new file mode 100644
index 00000000000..61e2f42e3d6
--- /dev/null
+++ b/tests/lib/Memcache/MemcachedTest.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\Memcached;
+
+/**
+ * @group Memcache
+ * @group Memcached
+ */
+class MemcachedTest extends Cache {
+ public static function setUpBeforeClass(): void {
+ parent::setUpBeforeClass();
+
+ if (!Memcached::isAvailable()) {
+ self::markTestSkipped('The memcached extension is not available.');
+ }
+ $instance = new Memcached(self::getUniqueID());
+ if ($instance->set(self::getUniqueID(), self::getUniqueID()) === false) {
+ self::markTestSkipped('memcached server seems to be down.');
+ }
+ }
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->instance = new Memcached($this->getUniqueID());
+ }
+
+ public function testClear(): void {
+ // Memcached is sometimes broken with clear(), so we don't test it thoroughly
+ $value = 'ipsum lorum';
+ $this->instance->set('1_value1', $value);
+ $this->instance->set('1_value2', $value);
+ $this->instance->set('2_value1', $value);
+ $this->instance->set('3_value1', $value);
+
+ $this->assertTrue($this->instance->clear('1_'));
+
+ $this->assertFalse($this->instance->hasKey('1_value1'));
+ $this->assertFalse($this->instance->hasKey('1_value2'));
+ //$this->assertTrue($this->instance->hasKey('2_value1'));
+ //$this->assertTrue($this->instance->hasKey('3_value1'));
+
+ $this->assertTrue($this->instance->clear());
+
+ $this->assertFalse($this->instance->hasKey('1_value1'));
+ $this->assertFalse($this->instance->hasKey('1_value2'));
+ $this->assertFalse($this->instance->hasKey('2_value1'));
+ $this->assertFalse($this->instance->hasKey('3_value1'));
+ }
+}
diff --git a/tests/lib/Memcache/RedisTest.php b/tests/lib/Memcache/RedisTest.php
new file mode 100644
index 00000000000..c1dcc954925
--- /dev/null
+++ b/tests/lib/Memcache/RedisTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Memcache;
+
+use OC\Memcache\Redis;
+use OCP\IConfig;
+use OCP\Server;
+
+/**
+ * @group Memcache
+ * @group Redis
+ */
+class RedisTest extends Cache {
+ /**
+ * @var Redis cache;
+ */
+ protected $instance;
+
+ public static function setUpBeforeClass(): void {
+ parent::setUpBeforeClass();
+
+ if (!Redis::isAvailable()) {
+ self::markTestSkipped('The redis extension is not available.');
+ }
+
+ if (Server::get(IConfig::class)->getSystemValue('redis', []) === []) {
+ self::markTestSkipped('Redis not configured in config.php');
+ }
+
+ $errorOccurred = false;
+ set_error_handler(
+ function ($errno, $errstr): void {
+ throw new \RuntimeException($errstr, 123456789);
+ },
+ E_WARNING
+ );
+ $instance = null;
+ try {
+ $instance = new Redis(self::getUniqueID());
+ } catch (\RuntimeException $e) {
+ $errorOccurred = $e->getCode() === 123456789 ? $e->getMessage() : false;
+ }
+ restore_error_handler();
+ if ($errorOccurred !== false) {
+ self::markTestSkipped($errorOccurred);
+ }
+
+ if ($instance === null) {
+ throw new \Exception('redis server is not reachable');
+ }
+
+ if ($instance->set(self::getUniqueID(), self::getUniqueID()) === false) {
+ self::markTestSkipped('redis server seems to be down.');
+ }
+ }
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->instance = new Redis($this->getUniqueID());
+ }
+
+ public function testScriptHashes(): void {
+ foreach (Redis::LUA_SCRIPTS as $script) {
+ $this->assertEquals(sha1($script[0]), $script[1]);
+ }
+ }
+
+ public function testCasTtlNotChanged(): void {
+ $this->instance->set('foo', 'bar', 50);
+ $this->assertTrue($this->instance->compareSetTTL('foo', 'bar', 100));
+ // allow for 1s of inaccuracy due to time moving forward
+ $this->assertLessThan(1, 100 - $this->instance->getTTL('foo'));
+ }
+
+ public function testCasTtlChanged(): void {
+ $this->instance->set('foo', 'bar1', 50);
+ $this->assertFalse($this->instance->compareSetTTL('foo', 'bar', 100));
+ // allow for 1s of inaccuracy due to time moving forward
+ $this->assertLessThan(1, 50 - $this->instance->getTTL('foo'));
+ }
+}