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);