Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/testo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ jobs:
['ubuntu-latest']
dependencies: >-
['lowest', 'highest']
ini-values: memory_limit=-1, zend.assertions=1
74 changes: 57 additions & 17 deletions tests/src/LockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Mockery as m;
use Mockery\MockInterface;
use Ramsey\Uuid\Uuid;
use RoadRunner\Lock\DTO\V1BETA1\Request;
use RoadRunner\Lock\DTO\V1BETA1\Response;
use RoadRunner\Lock\Lock;
Expand All @@ -14,8 +15,11 @@
use Spiral\Goridge\RPC\RPCInterface;
use Testo\Assert;
use Testo\Assert\ExpectException;
use Testo\Core\Exception\SkipTest;
use Testo\Data\DataProvider;
use Testo\Expect;
use Testo\Lifecycle\BeforeTest;
use Testo\Skip;
use Testo\Test;

#[Test]
Expand Down Expand Up @@ -79,6 +83,26 @@ public static function resultDataProvider(): \Traversable
yield [false];
}

public static function negativeTimeDataProvider(): \Generator
{
yield 'lock ttl' => ['lock', ['resource', 'uuid', -300]];
yield 'lock waitTTL' => ['lock', ['resource', 'uuid', 0, -300]];
yield 'lockRead ttl' => ['lockRead', ['resource', 'uuid', -300]];
yield 'lockRead waitTTL' => ['lockRead', ['resource', 'uuid', 0, -300]];
yield 'updateTTL ttl' => ['updateTTL', ['resource', 'uuid', -300]];
}

public static function compoundIntervalDataProvider(): \Generator
{
yield 'minutes and seconds' => [new \DateInterval('PT1M30S'), 90_000_000];
yield 'hours' => [new \DateInterval('PT1H'), 3_600_000_000];
yield 'days' => [new \DateInterval('P1D'), 86_400_000_000];

$interval = new \DateInterval('PT1S');
$interval->f = 0.5;
yield 'fraction of a second' => [$interval, 1_500_000];
}

#[DataProvider('lockTypeDataProvider')]
public function testLock(
string $method,
Expand Down Expand Up @@ -199,34 +223,50 @@ public function testExistsWithId(bool $result): void
Assert::same($this->lock->exists('resource', 'some-id'), $result);
}

#[ExpectException(\LogicException::class)]
public function testLockNegativeTtl(): void
#[DataProvider('negativeTimeDataProvider')]
public function testNegativeTimeFailsAssertion(string $method, array $args): void
{
$this->lock->lock('resource', 'uuid', -300);
}
if (\ini_get('zend.assertions') !== '1') {
throw new SkipTest('The negative time check is an assert(), inactive unless zend.assertions=1');
}

#[ExpectException(\LogicException::class)]
public function testLockNegativeWaitTtl(): void
{
$this->lock->lock('resource', 'uuid', 0, -300);
Expect::exception(\AssertionError::class);
$this->lock->$method(...$args);
}

#[ExpectException(\LogicException::class)]
public function testLockReadNegativeTtl(): void
#[Skip('Bug: the docblock promises InvalidArgumentException for a negative ttl, but the check is an assert(); with assertions off the negative value is sent to RoadRunner')]
#[DataProvider('negativeTimeDataProvider')]
#[ExpectException(\InvalidArgumentException::class)]
public function testNegativeTimeThrowsInvalidArgumentException(string $method, array $args): void
{
$this->lock->lock('resource', 'uuid', -300);
$this->lock->$method(...$args);
}

#[ExpectException(\LogicException::class)]
public function testLockReadNegativeWaitTtl(): void
#[Skip('Bug: convertTimeToMicroseconds() reads only the seconds field of a DateInterval (format("%s")), dropping minutes, hours, days and microseconds')]
#[DataProvider('compoundIntervalDataProvider')]
public function testCompoundDateIntervalTtl(\DateInterval $ttl, int $expectedTtl): void
{
$this->lock->lockRead('resource', 'uuid', 0, -300);
$this->rpc->shouldReceive('call')
->once()
->withArgs(static fn(string $method, Request $request): bool => $request->getTtl() === $expectedTtl)
->andReturn(new Response(['ok' => true]));

Assert::true($this->lock->updateTTL('resource', 'some-id', $ttl));
}

#[ExpectException(\LogicException::class)]
public function testUpdateNegativeWaitTtl(): void
public function testLockGeneratesUuidByDefault(): void
{
$this->lock->updateTTL('resource', 'uuid', -300);
$rpc = m::mock(RPCInterface::class);
$rpc->shouldReceive('withCodec')->andReturnSelf();
$rpc->shouldReceive('call')
->once()
->withArgs(static fn(string $method, Request $request): bool => Uuid::isValid($request->getId()))
->andReturn(new Response(['ok' => true]));

$id = (new Lock($rpc))->lock('resource');

Assert::string($id);
Assert::same(Uuid::fromString($id)->getFields()->getVersion(), 4);
}

#[BeforeTest]
Expand Down
36 changes: 36 additions & 0 deletions tests/src/UuidLockIdGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace RoadRunner\Lock\Tests;

use Mockery as m;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactoryInterface;
use RoadRunner\Lock\UuidLockIdGenerator;
use Testo\Assert;
use Testo\Test;

#[Test]
final class UuidLockIdGeneratorTest
{
public function testGeneratesUuidV4ByDefault(): void
{
$generator = new UuidLockIdGenerator();

$id = $generator->generate();

Assert::true(Uuid::isValid($id));
Assert::same(Uuid::fromString($id)->getFields()->getVersion(), 4);
Assert::notSame($generator->generate(), $id);
}

public function testUsesGivenFactory(): void
{
$uuid = Uuid::fromString('2d1e4f7a-8b3c-4d5e-9f60-718293a4b5c6');
$factory = m::mock(UuidFactoryInterface::class);
$factory->shouldReceive('uuid4')->once()->andReturn($uuid);

Assert::same((new UuidLockIdGenerator($factory))->generate(), '2d1e4f7a-8b3c-4d5e-9f60-718293a4b5c6');
}
}
Loading