Merge pull request #7515 from nupplaphil/task/console_lock

New Console Command: Lock
This commit is contained in:
Hypolite Petovan 2019-08-15 10:31:34 -04:00 committed by GitHub
commit 48caf55cff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 743 additions and 41 deletions

185
src/Console/Lock.php Normal file
View file

@ -0,0 +1,185 @@
<?php
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
use Friendica\App;
use Friendica\Core\Lock\ILock;
use RuntimeException;
/**
* @brief tool to access the locks from the CLI
*
* With this script you can access the locks of your node from the CLI.
* You can read current locks and set/remove locks.
*
* @author Philipp Holzer <admin@philipp.info>, Hypolite Petovan <hypolite@mrpetovan.com>
*/
class Lock extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var App\Mode
*/
private $appMode;
/**
* @var ILock
*/
private $lock;
protected function getHelp()
{
$help = <<<HELP
console lock - Manage node locks
Synopsis
bin/console lock list [<prefix>] [-h|--help|-?] [-v]
bin/console lock set <lock> [<timeout> [<ttl>]] [-h|--help|-?] [-v]
bin/console lock del <lock> [-h|--help|-?] [-v]
bin/console lock clear [-h|--help|-?] [-v]
Description
bin/console lock list [<prefix>]
List all locks, optionally filtered by a prefix
bin/console lock set <lock> [<timeout> [<ttl>]]
Sets manually a lock, optionally with the provided TTL (time to live) with a default of five minutes.
bin/console lock del <lock>
Deletes a lock.
bin/console lock clear
Clears all locks
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(App\Mode $appMode, ILock $lock, array $argv = null)
{
parent::__construct($argv);
$this->appMode = $appMode;
$this->lock = $lock;
}
protected function doExecute()
{
if ($this->getOption('v')) {
$this->out('Executable: ' . $this->executable);
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (!$this->appMode->has(App\Mode::DBCONFIGAVAILABLE)) {
$this->out('Database isn\'t ready or populated yet, database cache won\'t be available');
}
if ($this->getOption('v')) {
$this->out('Lock Driver Name: ' . $this->lock->getName());
$this->out('Lock Driver Class: ' . get_class($this->lock));
}
switch ($this->getArgument(0)) {
case 'list':
$this->executeList();
break;
case 'set':
$this->executeSet();
break;
case 'del':
$this->executeDel();
break;
case 'clear':
$this->executeClear();
break;
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
return 0;
}
private function executeList()
{
$prefix = $this->getArgument(1, '');
$keys = $this->lock->getLocks($prefix);
if (empty($prefix)) {
$this->out('Listing all Locks:');
} else {
$this->out('Listing all Locks starting with "' . $prefix . '":');
}
$count = 0;
foreach ($keys as $key) {
$this->out($key);
$count++;
}
$this->out($count . ' locks found');
}
private function executeDel()
{
if (count($this->args) >= 2) {
$lock = $this->getArgument(1);
if ($this->lock->releaseLock($lock, true)) {
$this->out(sprintf('Lock \'%s\' released.', $lock));
} else {
$this->out(sprintf('Couldn\'t release Lock \'%s\'', $lock));
}
} else {
throw new CommandArgsException('Too few arguments for del.');
}
}
private function executeSet()
{
if (count($this->args) >= 2) {
$lock = $this->getArgument(1);
$timeout = intval($this->getArgument(2, false));
$ttl = intval($this->getArgument(3, false));
if ($this->lock->isLocked($lock)) {
throw new RuntimeException(sprintf('\'%s\' is already set.', $lock));
}
if (!empty($ttl) && !empty($timeout)) {
$result = $this->lock->acquireLock($lock, $timeout, $ttl);
} elseif (!empty($timeout)) {
$result = $this->lock->acquireLock($lock, $timeout);
} else {
$result = $this->lock->acquireLock($lock);
}
if ($result) {
$this->out(sprintf('Lock \'%s\' acquired.', $lock));
} else {
throw new RuntimeException(sprintf('Unable to lock \'%s\'.', $lock));
}
} else {
throw new CommandArgsException('Too few arguments for set.');
}
}
private function executeClear()
{
$result = $this->lock->releaseAll(true);
if ($result) {
$this->out('Locks successfully cleared.');
} else {
throw new RuntimeException('Unable to clear the locks.');
}
}
}

View file

@ -21,7 +21,7 @@ class ArrayCache extends Cache implements IMemoryCache
*/ */
public function getAllKeys($prefix = null) public function getAllKeys($prefix = null)
{ {
return $this->filterArrayKeysByPrefix($this->cachedData, $prefix); return $this->filterArrayKeysByPrefix(array_keys($this->cachedData), $prefix);
} }
/** /**

View file

@ -84,19 +84,19 @@ abstract class Cache implements ICache
* Filters the keys of an array with a given prefix * Filters the keys of an array with a given prefix
* Returns the filtered keys as an new array * Returns the filtered keys as an new array
* *
* @param array $array The array, which should get filtered * @param array $keys The keys, which should get filtered
* @param string|null $prefix The prefix (if null, all keys will get returned) * @param string|null $prefix The prefix (if null, all keys will get returned)
* *
* @return array The filtered array with just the keys * @return array The filtered array with just the keys
*/ */
protected function filterArrayKeysByPrefix($array, $prefix = null) protected function filterArrayKeysByPrefix(array $keys, string $prefix = null)
{ {
if (empty($prefix)) { if (empty($prefix)) {
return array_keys($array); return $keys;
} else { } else {
$result = []; $result = [];
foreach (array_keys($array) as $key) { foreach ($keys as $key) {
if (strpos($key, $prefix) === 0) { if (strpos($key, $prefix) === 0) {
array_push($result, $key); array_push($result, $key);
} }

View file

@ -27,6 +27,17 @@ class MemcachedCache extends Cache implements IMemoryCache
*/ */
private $logger; private $logger;
/**
* @var string First server address
*/
private $firstServer;
/**
* @var int First server port
*/
private $firstPort;
/** /**
* Due to limitations of the INI format, the expected configuration for Memcached servers is the following: * Due to limitations of the INI format, the expected configuration for Memcached servers is the following:
* array { * array {
@ -58,6 +69,9 @@ class MemcachedCache extends Cache implements IMemoryCache
} }
}); });
$this->firstServer = $memcached_hosts[0][0] ?? 'localhost';
$this->firstPort = $memcached_hosts[0][1] ?? 11211;
$this->memcached->addServers($memcached_hosts); $this->memcached->addServers($memcached_hosts);
if (count($this->memcached->getServerList()) == 0) { if (count($this->memcached->getServerList()) == 0) {
@ -70,14 +84,95 @@ class MemcachedCache extends Cache implements IMemoryCache
*/ */
public function getAllKeys($prefix = null) public function getAllKeys($prefix = null)
{ {
$keys = $this->getOriginalKeys($this->memcached->getAllKeys()); $keys = $this->getOriginalKeys($this->getMemcachedKeys());
if ($this->memcached->getResultCode() == Memcached::RES_SUCCESS) { return $this->filterArrayKeysByPrefix($keys, $prefix);
return $this->filterArrayKeysByPrefix($keys, $prefix); }
} else {
$this->logger->debug('Memcached \'getAllKeys\' failed', ['result' => $this->memcached->getResultMessage()]); /**
return []; * Get all memcached keys.
* Special function because getAllKeys() is broken since memcached 1.4.23.
*
* cleaned up version of code found on Stackoverflow.com by Maduka Jayalath
* @see https://stackoverflow.com/a/34724821
*
* @return array|int - all retrieved keys (or negative number on error)
*/
private function getMemcachedKeys()
{
$mem = @fsockopen($this->firstServer, $this->firstPort);
if ($mem === false) {
return -1;
} }
// retrieve distinct slab
$r = @fwrite($mem, 'stats items' . chr(10));
if ($r === false) {
return -2;
}
$slab = [];
while (($l = @fgets($mem, 1024)) !== false) {
// finished?
$l = trim($l);
if ($l == 'END') {
break;
}
$m = [];
// <STAT items:22:evicted_nonzero 0>
$r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m);
if ($r != 1) {
return -3;
}
$a_slab = $m[1];
if (!array_key_exists($a_slab, $slab)) {
$slab[$a_slab] = [];
}
}
reset($slab);
foreach ($slab as $a_slab_key => &$a_slab) {
$r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10));
if ($r === false) {
return -4;
}
while (($l = @fgets($mem, 1024)) !== false) {
// finished?
$l = trim($l);
if ($l == 'END') {
break;
}
$m = [];
// ITEM 42 [118 b; 1354717302 s]
$r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m);
if ($r != 1) {
return -5;
}
$a_key = $m[1];
$a_slab[] = $a_key;
}
}
// close the connection
@fclose($mem);
unset($mem);
$keys = [];
reset($slab);
foreach ($slab AS &$a_slab) {
reset($a_slab);
foreach ($a_slab AS &$a_key) {
$keys[] = $a_key;
}
}
unset($slab);
return $keys;
} }
/** /**

View file

@ -38,6 +38,7 @@ Commands:
archivecontact Archive a contact when you know that it isn't existing anymore archivecontact Archive a contact when you know that it isn't existing anymore
help Show help about a command, e.g (bin/console help config) help Show help about a command, e.g (bin/console help config)
autoinstall Starts automatic installation of friendica based on values from htconfig.php autoinstall Starts automatic installation of friendica based on values from htconfig.php
lock Edit site locks
maintenance Set maintenance mode for this node maintenance Set maintenance mode for this node
newpassword Set a new password for a given user newpassword Set a new password for a given user
php2po Generate a messages.po file from a strings.php file php2po Generate a messages.po file from a strings.php file
@ -65,6 +66,7 @@ HELP;
'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class, 'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class,
'archivecontact' => Friendica\Console\ArchiveContact::class, 'archivecontact' => Friendica\Console\ArchiveContact::class,
'autoinstall' => Friendica\Console\AutomaticInstallation::class, 'autoinstall' => Friendica\Console\AutomaticInstallation::class,
'lock' => Friendica\Console\Lock::class,
'maintenance' => Friendica\Console\Maintenance::class, 'maintenance' => Friendica\Console\Maintenance::class,
'newpassword' => Friendica\Console\NewPassword::class, 'newpassword' => Friendica\Console\NewPassword::class,
'php2po' => Friendica\Console\PhpToPo::class, 'php2po' => Friendica\Console\PhpToPo::class,

View file

@ -7,6 +7,11 @@ use Friendica\Core\Cache\IMemoryCache;
class CacheLock extends Lock class CacheLock extends Lock
{ {
/**
* @var string The static prefix of all locks inside the cache
*/
const CACHE_PREFIX = 'lock:';
/** /**
* @var \Friendica\Core\Cache\ICache; * @var \Friendica\Core\Cache\ICache;
*/ */
@ -25,7 +30,7 @@ class CacheLock extends Lock
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES) public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
{ {
$got_lock = false; $got_lock = false;
$start = time(); $start = time();
@ -85,6 +90,46 @@ class CacheLock extends Lock
return isset($lock) && ($lock !== false); return isset($lock) && ($lock !== false);
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->cache->getName();
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
$locks = $this->cache->getAllKeys(self::CACHE_PREFIX . $prefix);
array_walk($locks, function (&$lock, $key) {
$lock = substr($lock, strlen(self::CACHE_PREFIX));
});
return $locks;
}
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
{
$success = parent::releaseAll($override);
$locks = $this->getLocks();
foreach ($locks as $lock) {
if (!$this->releaseLock($lock, $override)) {
$success = false;
}
}
return $success;
}
/** /**
* @param string $key The original key * @param string $key The original key
* *
@ -92,6 +137,6 @@ class CacheLock extends Lock
*/ */
private static function getLockKey($key) private static function getLockKey($key)
{ {
return "lock:" . $key; return self::CACHE_PREFIX . $key;
} }
} }

View file

@ -92,9 +92,16 @@ class DatabaseLock extends Lock
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function releaseAll() public function releaseAll($override = false)
{ {
$return = $this->dba->delete('locks', ['pid' => $this->pid]); $success = parent::releaseAll($override);
if ($override) {
$where = ['1 = 1'];
} else {
$where = ['pid' => $this->pid];
}
$return = $this->dba->delete('locks', $where);
$this->acquiredLocks = []; $this->acquiredLocks = [];
@ -114,4 +121,34 @@ class DatabaseLock extends Lock
return false; return false;
} }
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_DATABASE;
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()];
} else {
$where = ['`expires` >= ? AND `name` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix];
}
$stmt = $this->dba->select('locks', ['name'], $where);
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['name']);
}
$this->dba->close($stmt);
return $keys;
}
} }

View file

@ -45,7 +45,25 @@ interface ILock
/** /**
* Releases all lock that were set by us * Releases all lock that were set by us
* *
* @param bool $override Override to release all locks
*
* @return boolean Was the unlock of all locks successful? * @return boolean Was the unlock of all locks successful?
*/ */
public function releaseAll(); public function releaseAll($override = false);
/**
* Returns the name of the current lock
*
* @return string
*/
public function getName();
/**
* Lists all locks
*
* @param string prefix optional a prefix to search
*
* @return array Empty if it isn't supported by the cache driver
*/
public function getLocks(string $prefix = '');
} }

View file

@ -2,6 +2,8 @@
namespace Friendica\Core\Lock; namespace Friendica\Core\Lock;
use Friendica\Core\Cache\Cache;
/** /**
* Class AbstractLock * Class AbstractLock
* *
@ -11,6 +13,9 @@ namespace Friendica\Core\Lock;
*/ */
abstract class Lock implements ILock abstract class Lock implements ILock
{ {
const TYPE_DATABASE = Cache::TYPE_DATABASE;
const TYPE_SEMAPHORE = 'semaphore';
/** /**
* @var array The local acquired locks * @var array The local acquired locks
*/ */
@ -49,16 +54,14 @@ abstract class Lock implements ILock
} }
/** /**
* Releases all lock that were set by us * {@inheritDoc}
*
* @return boolean Was the unlock of all locks successful?
*/ */
public function releaseAll() public function releaseAll($override = false)
{ {
$return = true; $return = true;
foreach ($this->acquiredLocks as $acquiredLock => $hasLock) { foreach ($this->acquiredLocks as $acquiredLock => $hasLock) {
if (!$this->releaseLock($acquiredLock)) { if (!$this->releaseLock($acquiredLock, $override)) {
$return = false; $return = false;
} }
} }

View file

@ -20,9 +20,7 @@ class SemaphoreLock extends Lock
*/ */
private static function semaphoreKey($key) private static function semaphoreKey($key)
{ {
$temp = get_temppath(); $file = self::keyToFile($key);
$file = $temp . '/' . $key . '.sem';
if (!file_exists($file)) { if (!file_exists($file)) {
file_put_contents($file, $key); file_put_contents($file, $key);
@ -31,10 +29,24 @@ class SemaphoreLock extends Lock
return ftok($file, 'f'); return ftok($file, 'f');
} }
/**
* Returns the full path to the semaphore file
*
* @param string $key The key of the semaphore
*
* @return string The full path
*/
private static function keyToFile($key)
{
$temp = get_temppath();
return $temp . '/' . $key . '.sem';
}
/** /**
* (@inheritdoc) * (@inheritdoc)
*/ */
public function acquireLock($key, $timeout = 120, $ttl = Cache::FIVE_MINUTES) public function acquireLock($key, $timeout = 120, $ttl = Cache\Cache::FIVE_MINUTES)
{ {
self::$semaphore[$key] = sem_get(self::semaphoreKey($key)); self::$semaphore[$key] = sem_get(self::semaphoreKey($key));
if (self::$semaphore[$key]) { if (self::$semaphore[$key]) {
@ -52,14 +64,24 @@ class SemaphoreLock extends Lock
*/ */
public function releaseLock($key, $override = false) public function releaseLock($key, $override = false)
{ {
if (empty(self::$semaphore[$key])) { $success = false;
return false;
} else { if (!empty(self::$semaphore[$key])) {
$success = @sem_release(self::$semaphore[$key]); try {
unset(self::$semaphore[$key]); $success = @sem_release(self::$semaphore[$key]) &&
$this->markRelease($key); unlink(self::keyToFile($key));
return $success; unset(self::$semaphore[$key]);
$this->markRelease($key);
} catch (\Exception $exception) {
$success = false;
}
} else if ($override) {
if ($this->acquireLock($key)) {
$success = $this->releaseLock($key, true);
}
} }
return $success;
} }
/** /**
@ -69,4 +91,47 @@ class SemaphoreLock extends Lock
{ {
return isset(self::$semaphore[$key]); return isset(self::$semaphore[$key]);
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return self::TYPE_SEMAPHORE;
}
/**
* {@inheritDoc}
*/
public function getLocks(string $prefix = '')
{
$temp = get_temppath();
$locks = [];
foreach (glob(sprintf('%s/%s*.sem', $temp, $prefix)) as $lock) {
$lock = pathinfo($lock, PATHINFO_FILENAME);
if(sem_get(self::semaphoreKey($lock))) {
$locks[] = $lock;
}
}
return $locks;
}
/**
* {@inheritDoc}
*/
public function releaseAll($override = false)
{
$success = parent::releaseAll($override);
$temp = get_temppath();
foreach (glob(sprintf('%s/*.sem', $temp)) as $lock) {
$lock = pathinfo($lock, PATHINFO_FILENAME);
if (!$this->releaseLock($lock, true)) {
$success = false;
}
}
return $success;
}
} }

View file

@ -0,0 +1,215 @@
<?php
namespace Friendica\Test\src\Console;
use Friendica\App;
use Friendica\App\Mode;
use Friendica\Console\Lock;
use Friendica\Core\Lock\ILock;
use Mockery\MockInterface;
class LockConsoleTest extends ConsoleTest
{
/**
* @var App\Mode|MockInterface $appMode
*/
private $appMode;
/**
* @var ILock|MockInterface
*/
private $lockMock;
protected function setUp()
{
parent::setUp();
\Mockery::getConfiguration()->setConstantsMap([
Mode::class => [
'DBCONFIGAVAILABLE' => 0
]
]);
$this->appMode = \Mockery::mock(App\Mode::class);
$this->appMode->shouldReceive('has')
->andReturn(true);
$this->lockMock = \Mockery::mock(ILock::class);
}
public function testList()
{
$this->lockMock
->shouldReceive('getLocks')
->andReturn(['test', 'test2'])
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'list');
$txt = $this->dumpExecute($console);
$this->assertEquals("Listing all Locks:\ntest\ntest2\n2 locks found\n", $txt);
}
public function testListPrefix()
{
$this->lockMock
->shouldReceive('getLocks')
->with('test')
->andReturn(['test', 'test2'])
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'list');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("Listing all Locks starting with \"test\":\ntest\ntest2\n2 locks found\n", $txt);
}
public function testDelLock()
{
$this->lockMock
->shouldReceive('releaseLock')
->with('test', true)
->andReturn(true)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'del');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("Lock 'test' released.\n", $txt);
}
public function testDelUnknownLock()
{
$this->lockMock
->shouldReceive('releaseLock')
->with('test', true)
->andReturn(false)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'del');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("Couldn't release Lock 'test'\n", $txt);
}
public function testSetLock()
{
$this->lockMock
->shouldReceive('isLocked')
->with('test')
->andReturn(false)
->once();
$this->lockMock
->shouldReceive('acquireLock')
->with('test')
->andReturn(true)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'set');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("Lock 'test' acquired.\n", $txt);
}
public function testSetLockIsLocked()
{
$this->lockMock
->shouldReceive('isLocked')
->with('test')
->andReturn(true)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'set');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("[Error] 'test' is already set.\n", $txt);
}
public function testSetLockNotWorking()
{
$this->lockMock
->shouldReceive('isLocked')
->with('test')
->andReturn(false)
->once();
$this->lockMock
->shouldReceive('acquireLock')
->with('test')
->andReturn(false)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'set');
$console->setArgument(1, 'test');
$txt = $this->dumpExecute($console);
$this->assertEquals("[Error] Unable to lock 'test'.\n", $txt);
}
public function testReleaseAll()
{
$this->lockMock
->shouldReceive('releaseAll')
->andReturn(true)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'clear');
$txt = $this->dumpExecute($console);
$this->assertEquals("Locks successfully cleared.\n", $txt);
}
public function testReleaseAllFailed()
{
$this->lockMock
->shouldReceive('releaseAll')
->andReturn(false)
->once();
$console = new Lock($this->appMode, $this->lockMock, $this->consoleArgv);
$console->setArgument(0, 'clear');
$txt = $this->dumpExecute($console);
$this->assertEquals("[Error] Unable to clear the locks.\n", $txt);
}
public function testGetHelp()
{
// Usable to purposely fail if new commands are added without taking tests into account
$theHelp = <<<HELP
console lock - Manage node locks
Synopsis
bin/console lock list [<prefix>] [-h|--help|-?] [-v]
bin/console lock set <lock> [<timeout> [<ttl>]] [-h|--help|-?] [-v]
bin/console lock del <lock> [-h|--help|-?] [-v]
bin/console lock clear [-h|--help|-?] [-v]
Description
bin/console lock list [<prefix>]
List all locks, optionally filtered by a prefix
bin/console lock set <lock> [<timeout> [<ttl>]]
Sets manually a lock, optionally with the provided TTL (time to live) with a default of five minutes.
bin/console lock del <lock>
Deletes a lock.
bin/console lock clear
Clears all locks
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
$console = new Lock($this->appMode, $this->lockMock, [$this->consoleArgv]);
$console->setOption('help', true);
$txt = $this->dumpExecute($console);
$this->assertEquals($txt, $theHelp);
}
}

View file

@ -2,7 +2,6 @@
namespace Friendica\Test\src\Core\Cache; namespace Friendica\Test\src\Core\Cache;
use Friendica\Core\Cache\MemcachedCache;
use Friendica\Test\MockedTest; use Friendica\Test\MockedTest;
use Friendica\Util\PidFile; use Friendica\Util\PidFile;
@ -202,10 +201,6 @@ abstract class CacheTest extends MockedTest
*/ */
public function testGetAllKeys($value1, $value2, $value3) public function testGetAllKeys($value1, $value2, $value3)
{ {
if ($this->cache instanceof MemcachedCache) {
$this->markTestSkipped('Memcached doesn\'t support getAllKeys anymore');
}
$this->assertTrue($this->instance->set('value1', $value1)); $this->assertTrue($this->instance->set('value1', $value1));
$this->assertTrue($this->instance->set('value2', $value2)); $this->assertTrue($this->instance->set('value2', $value2));
$this->assertTrue($this->instance->set('test_value3', $value3)); $this->assertTrue($this->instance->set('test_value3', $value3));
@ -219,5 +214,7 @@ abstract class CacheTest extends MockedTest
$list = $this->instance->getAllKeys('test'); $list = $this->instance->getAllKeys('test');
$this->assertContains('test_value3', $list); $this->assertContains('test_value3', $list);
$this->assertNotContains('value1', $list);
$this->assertNotContains('value2', $list);
} }
} }

View file

@ -23,12 +23,12 @@ abstract class LockTest extends MockedTest
parent::setUp(); parent::setUp();
$this->instance = $this->getInstance(); $this->instance = $this->getInstance();
$this->instance->releaseAll(); $this->instance->releaseAll(true);
} }
protected function tearDown() protected function tearDown()
{ {
$this->instance->releaseAll(); $this->instance->releaseAll(true);
parent::tearDown(); parent::tearDown();
} }
@ -123,6 +123,46 @@ abstract class LockTest extends MockedTest
$this->assertFalse($this->instance->isLocked('test')); $this->assertFalse($this->instance->isLocked('test'));
} }
/**
* @small
*/
public function testGetLocks()
{
$this->assertTrue($this->instance->acquireLock('foo', 1));
$this->assertTrue($this->instance->acquireLock('bar', 1));
$this->assertTrue($this->instance->acquireLock('nice', 1));
$this->assertTrue($this->instance->isLocked('foo'));
$this->assertTrue($this->instance->isLocked('bar'));
$this->assertTrue($this->instance->isLocked('nice'));
$locks = $this->instance->getLocks();
$this->assertContains('foo', $locks);
$this->assertContains('bar', $locks);
$this->assertContains('nice', $locks);
}
/**
* @small
*/
public function testGetLocksWithPrefix()
{
$this->assertTrue($this->instance->acquireLock('foo', 1));
$this->assertTrue($this->instance->acquireLock('test1', 1));
$this->assertTrue($this->instance->acquireLock('test2', 1));
$this->assertTrue($this->instance->isLocked('foo'));
$this->assertTrue($this->instance->isLocked('test1'));
$this->assertTrue($this->instance->isLocked('test2'));
$locks = $this->instance->getLocks('test');
$this->assertContains('test1', $locks);
$this->assertContains('test2', $locks);
$this->assertNotContains('foo', $locks);
}
/** /**
* @medium * @medium
*/ */

View file

@ -12,8 +12,6 @@ class SemaphoreLockTest extends LockTest
{ {
public function setUp() public function setUp()
{ {
parent::setUp();
$dice = \Mockery::mock(Dice::class)->makePartial(); $dice = \Mockery::mock(Dice::class)->makePartial();
$app = \Mockery::mock(App::class); $app = \Mockery::mock(App::class);
@ -29,6 +27,8 @@ class SemaphoreLockTest extends LockTest
// @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject // @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject
BaseObject::setDependencyInjection($dice); BaseObject::setDependencyInjection($dice);
parent::setUp();
} }
protected function getInstance() protected function getInstance()