11package hyperflake
22
33import (
4+ "sync"
45 "testing"
56)
67
8+ // TestDecodeID verifies that known IDs decode to the expected components.
79func TestDecodeID (t * testing.T ) {
810 hf := NewHyperflakeConfig (0 , 0 )
911
1012 testCases := []struct {
13+ name string
1114 id int64
1215 expectedSignbit int
1316 expectedTimestamp int64
@@ -16,6 +19,7 @@ func TestDecodeID(t *testing.T) {
1619 expectedSequenceNumber int
1720 }{
1821 {
22+ name : "zero datacenter and machine ID" ,
1923 id : 3282575599297626112 ,
2024 expectedSignbit : 0 ,
2125 expectedTimestamp : 1729311810178 ,
@@ -24,6 +28,7 @@ func TestDecodeID(t *testing.T) {
2428 expectedSequenceNumber : 0 ,
2529 },
2630 {
31+ name : "non-zero datacenter, machine ID and sequence number" ,
2732 id : 3273974649684016911 ,
2833 expectedSignbit : 0 ,
2934 expectedTimestamp : 1727261183992 ,
@@ -34,30 +39,159 @@ func TestDecodeID(t *testing.T) {
3439 }
3540
3641 for _ , tc := range testCases {
37- t .Run ("" , func (t * testing.T ) {
38- decodedID , err := hf .DecodeID (tc .id )
42+ t .Run (tc . name , func (t * testing.T ) {
43+ decoded , err := hf .DecodeID (tc .id )
3944 if err != nil {
4045 t .Fatalf ("DecodeID(%d) returned error: %v" , tc .id , err )
4146 }
42- if decodedID .Signbit != tc .expectedSignbit {
43- t .Errorf ("DecodeID(%d) Signbit = %d; want %d" , tc . id , decodedID .Signbit , tc .expectedSignbit )
47+ if decoded .Signbit != tc .expectedSignbit {
48+ t .Errorf ("Signbit = %d; want %d" , decoded .Signbit , tc .expectedSignbit )
4449 }
45- if decodedID .Timestamp != tc .expectedTimestamp {
46- t .Errorf ("DecodeID(%d) Timestamp = %d; want %d" , tc . id , decodedID .Timestamp , tc .expectedTimestamp )
50+ if decoded .Timestamp != tc .expectedTimestamp {
51+ t .Errorf ("Timestamp = %d; want %d" , decoded .Timestamp , tc .expectedTimestamp )
4752 }
48- if decodedID .DatacenterID != tc .expectedDatacenterID {
49- t .Errorf ("DecodeID(%d) DatacenterID = %d; want %d" , tc . id , decodedID .DatacenterID , tc .expectedDatacenterID )
53+ if decoded .DatacenterID != tc .expectedDatacenterID {
54+ t .Errorf ("DatacenterID = %d; want %d" , decoded .DatacenterID , tc .expectedDatacenterID )
5055 }
51- if decodedID .MachineID != tc .expectedMachineID {
52- t .Errorf ("DecodeID(%d) MachineID = %d; want %d" , tc . id , decodedID .MachineID , tc .expectedMachineID )
56+ if decoded .MachineID != tc .expectedMachineID {
57+ t .Errorf ("MachineID = %d; want %d" , decoded .MachineID , tc .expectedMachineID )
5358 }
54- if decodedID .SequenceNumber != tc .expectedSequenceNumber {
55- t .Errorf ("DecodeID(%d) SequenceNumber = %d; want %d" , tc . id , decodedID .SequenceNumber , tc .expectedSequenceNumber )
59+ if decoded .SequenceNumber != tc .expectedSequenceNumber {
60+ t .Errorf ("SequenceNumber = %d; want %d" , decoded .SequenceNumber , tc .expectedSequenceNumber )
5661 }
5762 })
5863 }
64+ }
65+
66+ // TestGenerateAndDecode verifies that a generated ID round-trips correctly through DecodeID.
67+ func TestGenerateAndDecode (t * testing.T ) {
68+ const datacenterID = 12
69+ const machineID = 7
70+
71+ config := NewHyperflakeConfig (datacenterID , machineID )
72+ id , err := config .GenerateHyperflakeID ()
73+ if err != nil {
74+ t .Fatalf ("GenerateHyperflakeID() error: %v" , err )
75+ }
76+ if id <= 0 {
77+ t .Fatalf ("GenerateHyperflakeID() returned non-positive ID: %d" , id )
78+ }
79+
80+ decoded , err := config .DecodeID (id )
81+ if err != nil {
82+ t .Fatalf ("DecodeID(%d) error: %v" , id , err )
83+ }
84+ if decoded .DatacenterID != datacenterID {
85+ t .Errorf ("DatacenterID = %d; want %d" , decoded .DatacenterID , datacenterID )
86+ }
87+ if decoded .MachineID != machineID {
88+ t .Errorf ("MachineID = %d; want %d" , decoded .MachineID , machineID )
89+ }
90+ if decoded .ID != id {
91+ t .Errorf ("ID = %d; want %d" , decoded .ID , id )
92+ }
93+ }
94+
95+ // TestConcurrency verifies that GenerateHyperflakeID is safe for concurrent use
96+ // and produces no duplicate IDs across goroutines.
97+ func TestConcurrency (t * testing.T ) {
98+ const goroutines = 100
99+ const idsPerGoroutine = 100
100+
101+ config := NewHyperflakeConfig (1 , 1 )
102+ ids := make (chan int64 , goroutines * idsPerGoroutine )
103+
104+ var wg sync.WaitGroup
105+ for i := 0 ; i < goroutines ; i ++ {
106+ wg .Add (1 )
107+ go func () {
108+ defer wg .Done ()
109+ for j := 0 ; j < idsPerGoroutine ; j ++ {
110+ id , err := config .GenerateHyperflakeID ()
111+ if err != nil {
112+ t .Errorf ("GenerateHyperflakeID() error: %v" , err )
113+ return
114+ }
115+ ids <- id
116+ }
117+ }()
118+ }
119+
120+ wg .Wait ()
121+ close (ids )
122+
123+ seen := make (map [int64 ]struct {}, goroutines * idsPerGoroutine )
124+ for id := range ids {
125+ if _ , exists := seen [id ]; exists {
126+ t .Errorf ("duplicate ID generated: %d" , id )
127+ }
128+ seen [id ] = struct {}{}
129+ }
130+ }
59131
60- idd , _ := hf .GenerateHyperflakeID ()
132+ // TestClockBackwards verifies that GenerateHyperflakeID returns an error
133+ // if the clock appears to move backwards.
134+ func TestClockBackwards (t * testing.T ) {
135+ config := NewHyperflakeConfig (0 , 0 )
136+ // Simulate a future lastTimestamp to trigger the clock-backwards guard.
137+ config .lastTimestamp = 1 << 41 - 1 // max possible timestamp
61138
62- println (idd )
139+ _ , err := config .GenerateHyperflakeID ()
140+ if err == nil {
141+ t .Fatal ("expected clock-backwards error, got nil" )
142+ }
143+ }
144+
145+ // TestCustomEpoch verifies that NewHyperflakeConfigWithEpoch uses the provided epoch
146+ // and that the decoded timestamp reflects it correctly.
147+ func TestCustomEpoch (t * testing.T ) {
148+ customEpoch := int64 (1_000_000_000_000 ) // arbitrary epoch in ms
149+
150+ config := NewHyperflakeConfigWithEpoch (1 , 1 , customEpoch )
151+ id , err := config .GenerateHyperflakeID ()
152+ if err != nil {
153+ t .Fatalf ("GenerateHyperflakeID() error: %v" , err )
154+ }
155+
156+ decoded , err := config .DecodeID (id )
157+ if err != nil {
158+ t .Fatalf ("DecodeID(%d) error: %v" , id , err )
159+ }
160+
161+ // Timestamp field should equal TimestampSinceEpoch + customEpoch.
162+ if decoded .Timestamp != decoded .TimestampSinceEpoch + customEpoch {
163+ t .Errorf ("Timestamp = %d; want TimestampSinceEpoch(%d) + epoch(%d) = %d" ,
164+ decoded .Timestamp , decoded .TimestampSinceEpoch , customEpoch , decoded .TimestampSinceEpoch + customEpoch )
165+ }
166+ }
167+
168+ // TestSettersGetters verifies Set/Get methods for datacenter and machine IDs.
169+ func TestSettersGetters (t * testing.T ) {
170+ config := NewHyperflakeConfig (0 , 0 )
171+
172+ config .SetDatacenterID (15 )
173+ if got := config .GetDatacenterID (); got != 15 {
174+ t .Errorf ("GetDatacenterID() = %d; want 15" , got )
175+ }
176+
177+ config .SetMachineID (31 )
178+ if got := config .GetMachineID (); got != 31 {
179+ t .Errorf ("GetMachineID() = %d; want 31" , got )
180+ }
181+
182+ // Verify the updated IDs are encoded in generated IDs.
183+ id , err := config .GenerateHyperflakeID ()
184+ if err != nil {
185+ t .Fatalf ("GenerateHyperflakeID() error: %v" , err )
186+ }
187+ decoded , err := config .DecodeID (id )
188+ if err != nil {
189+ t .Fatalf ("DecodeID error: %v" , err )
190+ }
191+ if decoded .DatacenterID != 15 {
192+ t .Errorf ("encoded DatacenterID = %d; want 15" , decoded .DatacenterID )
193+ }
194+ if decoded .MachineID != 31 {
195+ t .Errorf ("encoded MachineID = %d; want 31" , decoded .MachineID )
196+ }
63197}
0 commit comments