diff --git a/.idea/blade.xml b/.idea/blade.xml index 4f345f0..a07eb08 100644 --- a/.idea/blade.xml +++ b/.idea/blade.xml @@ -108,7 +108,7 @@ - + diff --git a/src/Factories/BitsFactory.php b/src/Factories/BitsFactory.php index 15b287e..79661bc 100644 --- a/src/Factories/BitsFactory.php +++ b/src/Factories/BitsFactory.php @@ -2,7 +2,10 @@ namespace Glhd\Bits\Factories; +use Carbon\CarbonImmutable; use Carbon\CarbonInterface; +use Closure; +use DateTime; use Glhd\Bits\Contracts\Configuration; use Glhd\Bits\Contracts\MakesBits; use Glhd\Bits\Contracts\ResolvesSequences; @@ -17,6 +20,8 @@ abstract class BitsFactory implements MakesBits { + protected ?Closure $now_resolver = null; + public function __construct( public readonly CarbonInterface $epoch, protected Configuration $config, @@ -25,10 +30,29 @@ public function __construct( $this->validateConfiguration(); } + public function setTestNow(CarbonInterface|Closure|null $now_resolver = null): static + { + if ($now_resolver instanceof CarbonInterface) { + $test_now = $now_resolver; + $now_resolver = fn() => $test_now->avoidMutation(); + } + + $this->now_resolver = $now_resolver; + + return $this; + } + + protected function now(): CarbonInterface + { + return $this->now_resolver + ? call_user_func($this->now_resolver) + : new CarbonImmutable(new DateTime()); + } + /** @return array {0: int, 1: int} */ protected function waitForValidTimestampAndSequence(): array { - $timestamp = $this->diffFromEpoch(now()); + $timestamp = $this->diffFromEpoch($this->now()); $sequence = $this->sequence->next($timestamp); // If we've used all available numbers in sequence, we'll sleep and try again diff --git a/tests/Unit/CustomTest.php b/tests/Unit/CustomTest.php index 43bad8c..1b11a3f 100644 --- a/tests/Unit/CustomTest.php +++ b/tests/Unit/CustomTest.php @@ -111,6 +111,7 @@ public function test_it_generates_predictable_snowflakes(): void $sequence = 0; $factory = $this->getBitsFactory(7, new TestingSequenceResolver($sequence)); + $factory->setTestNow(now()); $bits_at_epoch1 = $factory->make(); @@ -126,7 +127,7 @@ public function test_it_generates_predictable_snowflakes(): void $this->assertEquals($bits_at_epoch2->values[1], 7); $this->assertEquals($bits_at_epoch2->values[2], 1); - Date::setTestNow(now()->addMicrosecond()); + $factory->setTestNow(now()->addMicrosecond()); $bits_at_1us = $factory->make(); $this->assertEquals($bits_at_1us->id(), 0b0000000000000000000000000000000000000000000000000101110000000010); @@ -134,7 +135,7 @@ public function test_it_generates_predictable_snowflakes(): void $this->assertEquals($bits_at_1us->values[1], 7); $this->assertEquals($bits_at_1us->values[2], 2); - Date::setTestNow(now()->addMicrosecond()); + $factory->setTestNow(now()->addMicroseconds(2)); $bits_at_2us = $factory->make(); $this->assertEquals($bits_at_2us->id(), 0b0000000000000000000000000000000000000000000000001001110000000011); diff --git a/tests/Unit/SnowflakeTest.php b/tests/Unit/SnowflakeTest.php index f73d965..f07586c 100644 --- a/tests/Unit/SnowflakeTest.php +++ b/tests/Unit/SnowflakeTest.php @@ -7,6 +7,7 @@ use Glhd\Bits\Contracts\MakesSnowflakes; use Glhd\Bits\Contracts\ResolvesSequences; use Glhd\Bits\Factories\SnowflakeFactory; +use Glhd\Bits\SequenceResolvers\InMemorySequenceResolver; use Glhd\Bits\SequenceResolvers\TestingSequenceResolver; use Glhd\Bits\Snowflake; use Glhd\Bits\Sonyflake; @@ -114,6 +115,8 @@ public function test_it_generates_predictable_snowflakes(): void sequence: new TestingSequenceResolver($sequence) ); + $factory->setTestNow(now()); + $snowflake_at_epoch1 = $factory->make(); $this->assertEquals($snowflake_at_epoch1->id(), 0b0000000000000000000000000000000000000000000000101111000000000000); @@ -130,7 +133,8 @@ public function test_it_generates_predictable_snowflakes(): void $this->assertEquals($snowflake_at_epoch2->worker_id, 15); $this->assertEquals($snowflake_at_epoch2->sequence, 1); - Date::setTestNow(now()->addMillisecond()); + $factory->setTestNow(now()->addMillisecond()); + $snowflake_at_1ms = $factory->make(); $this->assertEquals($snowflake_at_1ms->id(), 0b0000000000000000000000000000000000000000010000101111000000000010); @@ -139,7 +143,8 @@ public function test_it_generates_predictable_snowflakes(): void $this->assertEquals($snowflake_at_1ms->worker_id, 15); $this->assertEquals($snowflake_at_1ms->sequence, 2); - Date::setTestNow(now()->addMillisecond()); + $factory->setTestNow(now()->addMilliseconds(2)); + $snowflake_at_2ms = $factory->make(); $this->assertEquals($snowflake_at_2ms->id(), 0b0000000000000000000000000000000000000000100000101111000000000011); @@ -149,6 +154,31 @@ public function test_it_generates_predictable_snowflakes(): void $this->assertEquals($snowflake_at_2ms->sequence, 3); } + public function test_it_generates_using_real_now_rather_than_test_now(): void + { + Date::setTestNow(now()->subMinute()); + + $factory = new SnowflakeFactory( + epoch: now(), + datacenter_id: 1, + worker_id: 15, + config: app(SnowflakesConfig::class), + sequence: new InMemorySequenceResolver(), + ); + + $factory->setTestNow(now()); + $snowflake_at_epoch = $factory->make(); + + $this->assertEquals($snowflake_at_epoch->id(), 0b0000000000000000000000000000000000000000000000101111000000000000); + $this->assertEquals($snowflake_at_epoch->timestamp, 0); + + $factory->setTestNow(null); + $snowflake_at_real_now = $factory->make(); + + $this->assertGreaterThan(0b0000000000000000000000000000000000000000000000101111000000000000, $snowflake_at_real_now->id()); + $this->assertGreaterThanOrEqual(60_000, $snowflake_at_real_now->timestamp); // 1 min = 60,000 ms + } + public function test_it_sleeps_1ms_when_sequence_limit_is_reached(): void { Date::setTestNow(now()); diff --git a/tests/Unit/SonyflakeTest.php b/tests/Unit/SonyflakeTest.php index 08fe224..1bd60fb 100644 --- a/tests/Unit/SonyflakeTest.php +++ b/tests/Unit/SonyflakeTest.php @@ -96,6 +96,7 @@ public function test_it_generates_predictable_sonyflakes(): void $sequence = 0; $factory = new SonyflakeFactory(now(), 1, app(SonyflakesConfig::class), new TestingSequenceResolver($sequence)); + $factory->setTestNow(now()); $sonyflake_at_epoch1 = $factory->make(); @@ -111,7 +112,8 @@ public function test_it_generates_predictable_sonyflakes(): void $this->assertEquals($sonyflake_at_epoch2->machine_id, 1); $this->assertEquals($sonyflake_at_epoch2->sequence, 1); - Date::setTestNow(now()->addMilliseconds(10)); + $factory->setTestNow(now()->addMilliseconds(10)); + $sonyflake_at_10ms = $factory->make(); $this->assertEquals($sonyflake_at_10ms->id(), 0b0000000000000000000000000000000000000001000000100000000000000001); @@ -119,7 +121,8 @@ public function test_it_generates_predictable_sonyflakes(): void $this->assertEquals($sonyflake_at_10ms->machine_id, 1); $this->assertEquals($sonyflake_at_10ms->sequence, 2); - Date::setTestNow(now()->addMilliseconds(10)); + $factory->setTestNow(now()->addMilliseconds(20)); + $sonyflake_at_20ms = $factory->make(); $this->assertEquals($sonyflake_at_20ms->id(), 0b0000000000000000000000000000000000000010000000110000000000000001);