mirror of
https://github.com/godruoyi/go-snowflake.git
synced 2025-12-23 05:25:15 +00:00
🚛 first commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.code
|
||||
.git
|
||||
.idea
|
||||
*.DS_Store
|
||||
/vendor
|
||||
31
atomic_resolver.go
Normal file
31
atomic_resolver.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package snowflake
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
var lastTime int64
|
||||
var lastSeq uint32
|
||||
|
||||
// AtomicResolver define as atomic sequence resolver, base on standard sync/atomic.
|
||||
func AtomicResolver(ms int64) (uint16, error) {
|
||||
var last int64
|
||||
var seq, localSeq uint32
|
||||
|
||||
for {
|
||||
last = atomic.LoadInt64(&lastTime)
|
||||
localSeq = atomic.LoadUint32(&lastSeq)
|
||||
if last > ms {
|
||||
return MaxSequence, nil
|
||||
}
|
||||
|
||||
if last == ms {
|
||||
seq = MaxSequence & (localSeq + 1)
|
||||
if seq == 0 {
|
||||
return MaxSequence, nil
|
||||
}
|
||||
}
|
||||
|
||||
if atomic.CompareAndSwapInt64(&lastTime, last, ms) && atomic.CompareAndSwapUint32(&lastSeq, localSeq, seq) {
|
||||
return uint16(seq), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
29
atomic_resolver_test.go
Normal file
29
atomic_resolver_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package snowflake_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func TestAtomicResolver(t *testing.T) {
|
||||
id, _ := snowflake.AtomicResolver(1)
|
||||
|
||||
if id != 0 {
|
||||
t.Error("Sequence should be equal 0")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCombinationParallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
snowflake.AtomicResolver(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkAtomicResolver(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
snowflake.AtomicResolver(1)
|
||||
}
|
||||
}
|
||||
21
example/example.go
Normal file
21
example/example.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
id := snowflake.ID()
|
||||
fmt.Println(id)
|
||||
|
||||
sid := snowflake.ParseID(id)
|
||||
// SID {
|
||||
// Sequence: 0
|
||||
// MachineID: 0
|
||||
// Timestamp: x
|
||||
// ID: x
|
||||
// }
|
||||
fmt.Println(sid)
|
||||
}
|
||||
8
package.go
Normal file
8
package.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Package snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.
|
||||
// The first bit is unused sign bit.
|
||||
// The second part consists of a 41-bit timestamp (milliseconds) whose value is the offset of the current time relative to a certain time.
|
||||
// The 5 bits of the third and fourth parts represent data center and worker, and max value is 2^5 -1 = 31.
|
||||
// The last part consists of 12 bits, its means the length of the serial number generated per millisecond per working node, a maximum of 2^12 -1 = 4095 IDs can be generated in the same millisecond.
|
||||
// In a distributed environment, five-bit datacenter and worker mean that can deploy 31 datacenters, and each datacenter can deploy up to 31 nodes.
|
||||
// The binary length of 41 bits is at most 2^41 -1 millisecond = 69 years. So the snowflake algorithm can be used for up to 69 years, In order to maximize the use of the algorithm, you should specify a start time for it.
|
||||
package snowflake
|
||||
65
privateip.go
Normal file
65
privateip.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package snowflake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
// PrivateIPToMachineID convert private ip to machine id.
|
||||
// From https://github.com/sony/sonyflake/blob/master/sonyflake.go
|
||||
func PrivateIPToMachineID() uint16 {
|
||||
ip, err := lower16BitPrivateIP()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// private function defined.
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
func privateIPv4() (net.IP, error) {
|
||||
as, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, a := range as {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok || ipnet.IP.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := ipnet.IP.To4()
|
||||
if isPrivateIPv4(ip) {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("no private ip address")
|
||||
}
|
||||
|
||||
func isPrivateIPv4(ip net.IP) bool {
|
||||
return ip != nil &&
|
||||
(ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168)
|
||||
}
|
||||
|
||||
func lower16BitPrivateIP() (uint16, error) {
|
||||
ip, err := privateIPv4()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Snowflake macnineID max length is 10, max value is 1023
|
||||
// If ip[2] > 3, return ip[2] + ip[3], but:
|
||||
// 10.21.5.211 => return 5 + 211 = 216
|
||||
// 10.21.4.212 => return 4 + 212 = 216
|
||||
// need help if you can do this.
|
||||
if ip[2] > 3 {
|
||||
return uint16(ip[2] + ip[3]), nil
|
||||
}
|
||||
|
||||
return uint16(ip[2])<<8 + uint16(ip[3]), nil
|
||||
}
|
||||
14
privateip_test.go
Normal file
14
privateip_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package snowflake_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func TestPrivateIPToMachineID(t *testing.T) {
|
||||
mid := snowflake.PrivateIPToMachineID()
|
||||
if mid <= 0 {
|
||||
t.Error("MachineID should be > 0")
|
||||
}
|
||||
}
|
||||
187
readme.md
Normal file
187
readme.md
Normal file
@@ -0,0 +1,187 @@
|
||||
<div>
|
||||
<p align="center">
|
||||
<image src="https://www.pngkey.com/png/full/105-1052235_snowflake-png-transparent-background-snowflake-with-clear-background.png" width="250" height="250">
|
||||
</p>
|
||||
<p align="center">An Lock Free ID Generator for Golang based on Snowflake Algorithm (Twitter announced).</p>
|
||||
<!-- <p align="center">
|
||||
<a href="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/">
|
||||
<image src="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/badges/quality-score.png?b=master" alt="quality score">
|
||||
</a>
|
||||
<a href="https://github.com/godruoyi/go-snowflake">
|
||||
<image src="https://github.styleci.io/repos/201936013/shield?branch=master" alt="godruoyi go-snowflake">
|
||||
</a>
|
||||
<a href="https://github.com/godruoyi/go-snowflake">
|
||||
<image src="https://poser.pugx.org/godruoyi/go-snowflake/license" alt="License">
|
||||
</a>
|
||||
<a href="https://packagist.org/packages/godruoyi/go-snowflake">
|
||||
<image src="https://poser.pugx.org/godruoyi/go-snowflake/v/stable" alt="Packagist Version">
|
||||
</a>
|
||||
<a href="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/">
|
||||
<image src="https://scrutinizer-ci.com/g/godruoyi/go-snowflake/badges/build.png?b=master" alt="build passed">
|
||||
</a>
|
||||
<a href="https://github.com/godruoyi/go-snowflake">
|
||||
<image src="https://poser.pugx.org/godruoyi/go-snowflake/downloads" alt="Total Downloads">
|
||||
</a>
|
||||
</p> -->
|
||||
</div>
|
||||
|
||||
## Description
|
||||
|
||||
An Lock Free ID Generator for Golang implementation.
|
||||
|
||||

|
||||
|
||||
Snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.
|
||||
|
||||
* The first bit is unused sign bit.
|
||||
* The second part consists of a 41-bit timestamp (milliseconds) whose value is the offset of the current time relative to a certain time.
|
||||
* The 10 bits machineID, and max value is 2^10 -1 = 1023.
|
||||
* The last part consists of 12 bits, its means the length of the serial number generated per millisecond per working node, a maximum of 2^12 -1 = 4095 IDs can be generated in the same millisecond.
|
||||
* The binary length of 41 bits is at most 2^41 -1 millisecond = 69 years. So the snowflake algorithm can be used for up to 69 years, In order to maximize the use of the algorithm, you should specify a start time for it.
|
||||
|
||||
> You must know, The ID generated by the snowflake algorithm is not guaranteed to be unique.
|
||||
> For example, when two different requests enter the same machine at the same time, and the sequence generated by the node is the same, the generated ID will be duplicated.
|
||||
|
||||
So if you want use the snowflake algorithm to generate unique ID, You must ensure: The sequence-number generated in the same millisecond of the same node is unique.
|
||||
Based on this, we created this package and integrated multiple sequence-number providers into it.
|
||||
|
||||
* AtomicResolver (base sync/atmoic)
|
||||
* Custom (you can custom)
|
||||
|
||||
> Each provider only needs to ensure that the serial number generated in the same millisecond is different. You can get a unique ID.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
$ go get github.com/godruoyi/go-snowflake
|
||||
```
|
||||
|
||||
## Useage
|
||||
|
||||
1. simple to use.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
id := snowflake.ID()
|
||||
fmt.Println(id)
|
||||
// 1537200202186752
|
||||
}
|
||||
```
|
||||
|
||||
2. Specify the MachineID.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
snowflake.SetMachineID(1)
|
||||
id := snowflake.ID()
|
||||
fmt.Println(id)
|
||||
}
|
||||
```
|
||||
|
||||
3. Specify start time.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
snowflake.SetStartTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
|
||||
id := snowflake.ID()
|
||||
fmt.Println(id)
|
||||
}
|
||||
```
|
||||
|
||||
4. Parse ID.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
id := snowflake.ID()
|
||||
sid := snowflake.ParseID(id)
|
||||
|
||||
fmt.Println(sid.ID) //
|
||||
fmt.Println(sid.MachineID) //
|
||||
fmt.Println(sid.Sequence) //
|
||||
fmt.Println(sid.Timestamp) //
|
||||
fmt.Println(sid.GenerateTime()) //
|
||||
}
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
> ⚠️⚠️ All SetXXX method is thread-unsafe, recommended you call him in the main function.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"net/http"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func main() {
|
||||
snowflake.SetMachineID(1) // change to your machineID
|
||||
snowflake.SetStartTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC))
|
||||
|
||||
http.HandleFunc("/order", submitOrder)
|
||||
http.ListenAndServe(":8090", nil)
|
||||
}
|
||||
|
||||
func submitOrder(w http.ResponseWriter, req *http.Request) {
|
||||
orderId := snowflake.ID()
|
||||
// save order
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
1. Custom sequence resolver.
|
||||
|
||||
You can customize the sequence-number resolver by following way:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func yourSequenceNumber(ms int64) (uint16, error) {
|
||||
|
||||
}
|
||||
|
||||
// usage
|
||||
|
||||
snowflake.SetSequenceResolver(yourSequenceNumber)
|
||||
snowflake.ID()
|
||||
```
|
||||
|
||||
And you can use closure:
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
172
snowflake.go
Normal file
172
snowflake.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package snowflake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These constants are the bit lengths of snowflake ID parts.
|
||||
const (
|
||||
TimestampLength = 41
|
||||
MachineIDLength = 10
|
||||
SequenceLength = 12
|
||||
MaxSequence = 1<<SequenceLength - 1
|
||||
MaxTimestamp = 1<<TimestampLength - 1
|
||||
MaxMachineID = 1<<MachineIDLength - 1
|
||||
|
||||
machineIDMoveLength = SequenceLength
|
||||
timestampMoveLength = MachineIDLength + SequenceLength
|
||||
)
|
||||
|
||||
// SequenceResolver the snowflake sequence resolver.
|
||||
//
|
||||
// When you want use the snowflake algorithm to generate unique ID, You must ensure: The sequence-number generated in the same millisecond of the same node is unique.
|
||||
// Based on this, we create this interface provide following reslover:
|
||||
// AtomicResolver : base sync/atomic (by default).
|
||||
type SequenceResolver func(ms int64) (uint16, error)
|
||||
|
||||
// default start time is 2014-09-01 00:00:00UTC
|
||||
// default machineID is 0
|
||||
// default resolver is AtomicResolver
|
||||
var (
|
||||
resolver SequenceResolver
|
||||
machineID = 0
|
||||
startTime = time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
// ID use ID to generate snowflake id and it will ignore error. if you want error info, you need use NextID method.
|
||||
// This function is thread safe.
|
||||
func ID() uint64 {
|
||||
id, _ := NextID()
|
||||
return id
|
||||
}
|
||||
|
||||
// NextID use NextID to generate snowflake id and return an error.
|
||||
// This function is thread safe.
|
||||
func NextID() (uint64, error) {
|
||||
c := currentMillis()
|
||||
seqResolver := callSequenceResolver()
|
||||
seq, err := seqResolver(c)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for seq >= MaxSequence {
|
||||
c = waitForNextMillis(c)
|
||||
seq, err = seqResolver(c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
df := int(elapsedTime(startTime))
|
||||
if df < 0 || df > MaxTimestamp {
|
||||
return 0, errors.New("The maximum life cycle of the snowflake algorithm is 2^41-1(millis), please check starttime")
|
||||
}
|
||||
|
||||
id := uint64(df<<timestampMoveLength | machineID<<machineIDMoveLength | int(seq))
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// SetStartTime set the start time for snowflake algorithm.
|
||||
//
|
||||
// It will panic when:
|
||||
// s IsZero
|
||||
// s > current millisecond
|
||||
// current millisecond - s > 2^41(69 years).
|
||||
// This function is thread-unsafe, recommended you call him in the main function.
|
||||
func SetStartTime(s time.Time) {
|
||||
s = s.UTC()
|
||||
|
||||
if s.IsZero() {
|
||||
panic("The start time cannot be a zero value")
|
||||
}
|
||||
|
||||
if s.After(time.Now()) {
|
||||
panic("The s cannot be greater than the current millisecond")
|
||||
}
|
||||
|
||||
// Because s must after now, so the `df` not < 0.
|
||||
df := elapsedTime(s)
|
||||
if df > MaxTimestamp {
|
||||
panic("The maximum life cycle of the snowflake algorithm is 69 years")
|
||||
}
|
||||
|
||||
startTime = s
|
||||
}
|
||||
|
||||
// SetMachineID specify the machine ID. It will panic when machineid > max limit for 2^10-1.
|
||||
// This function is thread-unsafe, recommended you call him in the main function.
|
||||
func SetMachineID(m uint16) {
|
||||
if m > MaxMachineID {
|
||||
panic("The machineid cannot be greater than 1023")
|
||||
}
|
||||
machineID = int(m)
|
||||
}
|
||||
|
||||
// SetSequenceResolver set an custom sequence resolver.
|
||||
// This function is thread-unsafe, recommended you call him in the main function.
|
||||
func SetSequenceResolver(seq SequenceResolver) {
|
||||
if seq != nil {
|
||||
resolver = seq
|
||||
}
|
||||
}
|
||||
|
||||
// SID snowflake id
|
||||
type SID struct {
|
||||
Sequence uint64
|
||||
MachineID uint64
|
||||
Timestamp uint64
|
||||
ID uint64
|
||||
}
|
||||
|
||||
// GenerateTime snowflake generate at, return a UTC time.
|
||||
func (id *SID) GenerateTime() time.Time {
|
||||
ms := startTime.UTC().UnixNano()/1e6 + int64(id.Timestamp)
|
||||
|
||||
return time.Unix(0, (ms * int64(time.Millisecond))).UTC()
|
||||
}
|
||||
|
||||
// ParseID parse snowflake it to SID struct.
|
||||
func ParseID(id uint64) SID {
|
||||
time := id >> (SequenceLength + MachineIDLength)
|
||||
sequence := id & MaxSequence
|
||||
machineID := (id & (MaxMachineID << SequenceLength)) >> SequenceLength
|
||||
|
||||
return SID{
|
||||
ID: id,
|
||||
Sequence: sequence,
|
||||
MachineID: machineID,
|
||||
Timestamp: time,
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// private function defined.
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
func waitForNextMillis(last int64) int64 {
|
||||
now := currentMillis()
|
||||
for now == last {
|
||||
now = currentMillis()
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
func callSequenceResolver() SequenceResolver {
|
||||
if resolver == nil {
|
||||
return AtomicResolver
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
func elapsedTime(s time.Time) int64 {
|
||||
return currentMillis() - s.UTC().UnixNano()/1e6
|
||||
}
|
||||
|
||||
// currentMillis get current millisecond.
|
||||
func currentMillis() int64 {
|
||||
return time.Now().UTC().UnixNano() / 1e6
|
||||
}
|
||||
202
snowflake_test.go
Normal file
202
snowflake_test.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package snowflake_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/godruoyi/go-snowflake"
|
||||
)
|
||||
|
||||
func TestID(t *testing.T) {
|
||||
id := snowflake.ID()
|
||||
|
||||
if id <= 0 {
|
||||
t.Error("The snowflake should't < 0.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStartTime(t *testing.T) {
|
||||
t.Run("A nil time", func(tt *testing.T) {
|
||||
defer func() {
|
||||
if e := recover(); e == nil {
|
||||
tt.Error("Should throw a error when start time is zero")
|
||||
} else if e.(string) != "The start time cannot be a zero value" {
|
||||
tt.Error("The error message should equal [The start time cannot be a zero value]")
|
||||
}
|
||||
}()
|
||||
var time time.Time
|
||||
snowflake.SetStartTime(time)
|
||||
})
|
||||
|
||||
t.Run("Start time too big", func(tt *testing.T) {
|
||||
defer func() {
|
||||
if e := recover(); e == nil {
|
||||
tt.Error("Should throw a error when start time is too big")
|
||||
} else if e.(string) != "The s cannot be greater than the current millisecond" {
|
||||
tt.Error("The error message should equal [The s cannot be greater than the current millisecond]")
|
||||
}
|
||||
}()
|
||||
time := time.Date(2035, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||
snowflake.SetStartTime(time)
|
||||
})
|
||||
|
||||
t.Run("Start time too small", func(tt *testing.T) {
|
||||
defer func() {
|
||||
if e := recover(); e == nil {
|
||||
tt.Error("Should throw a error when starttime is too small")
|
||||
} else if e.(string) != "The maximum life cycle of the snowflake algorithm is 69 years" {
|
||||
tt.Error("The error message should equal [The maximum life cycle of the snowflake algorithm is 69 years]")
|
||||
}
|
||||
}()
|
||||
// because 2021-69 = 1952, set df time > 69 years to test.
|
||||
time := time.Date(1951, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||
snowflake.SetStartTime(time)
|
||||
})
|
||||
|
||||
t.Run("Default start time", func(tt *testing.T) {
|
||||
defaultTime := time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC)
|
||||
defaultNano := defaultTime.UTC().UnixNano() / 1e6
|
||||
|
||||
sid := snowflake.ParseID(snowflake.ID())
|
||||
currentTime := sid.Timestamp + uint64(defaultNano)
|
||||
|
||||
nowNano := time.Now().UTC().UnixNano() / 1e6
|
||||
|
||||
// approximate equality, Assuming that the program is completed in one second.
|
||||
if currentTime/1000 != uint64(nowNano)/1000 {
|
||||
t.Error("The timestamp should be equal")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Basic", func(tt *testing.T) {
|
||||
date := time.Date(2002, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||
snowflake.SetStartTime(date)
|
||||
|
||||
nowNano := time.Now().UTC().UnixNano() / 1e6
|
||||
startNano := date.UTC().UnixNano() / 1e6
|
||||
df := nowNano - startNano
|
||||
|
||||
sid := snowflake.ParseID(snowflake.ID())
|
||||
|
||||
// approximate equality, Assuming that the program is completed in one second.
|
||||
if sid.Timestamp/1000 != uint64(df)/1000 {
|
||||
t.Error("The timestamp should be equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetMachineID(t *testing.T) {
|
||||
// first test,
|
||||
sid := snowflake.ParseID(snowflake.ID())
|
||||
if sid.MachineID != 0 {
|
||||
t.Error("MachineID should be equal 0")
|
||||
}
|
||||
|
||||
t.Run("No Panic", func(tt *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Error("An error should not be returned")
|
||||
}
|
||||
}()
|
||||
|
||||
snowflake.SetMachineID(1)
|
||||
id := snowflake.ID()
|
||||
sid := snowflake.ParseID(id)
|
||||
|
||||
if sid.MachineID != 1 {
|
||||
tt.Error("The machineID should be equal 1")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Panic", func(tt *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
tt.Error("Should throw a error")
|
||||
} else if err.(string) != "The machineid cannot be greater than 1023" {
|
||||
tt.Error("The error message should be eq 「The machineid cannot be greater than 1023」")
|
||||
}
|
||||
}()
|
||||
|
||||
snowflake.SetMachineID(1024)
|
||||
})
|
||||
|
||||
snowflake.SetMachineID(100)
|
||||
sid = snowflake.ParseID(snowflake.ID())
|
||||
if sid.MachineID != 100 {
|
||||
t.Error("MachineID should be equal 100")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSequenceResolver(t *testing.T) {
|
||||
snowflake.SetSequenceResolver(func(c int64) (uint16, error) {
|
||||
return 100, nil
|
||||
})
|
||||
|
||||
id := snowflake.ID()
|
||||
sid := snowflake.ParseID(id)
|
||||
|
||||
if sid.Sequence != 100 {
|
||||
t.Error("The snowflake number part of sequence should be equal 100")
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
id = snowflake.ID()
|
||||
sid2 := snowflake.ParseID(id)
|
||||
|
||||
if sid2.Sequence != 100 {
|
||||
t.Error("The snowflake number part of sequence should be equal 100")
|
||||
}
|
||||
|
||||
if sid2.Timestamp <= sid.Timestamp {
|
||||
t.Error("It should be bigger than the previous time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextID(t *testing.T) {
|
||||
_, err := snowflake.NextID()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
snowflake.SetSequenceResolver(func(ms int64) (uint16, error) {
|
||||
return 0, errors.New("test error")
|
||||
})
|
||||
_, e := snowflake.NextID()
|
||||
if e == nil {
|
||||
t.Error("Should be throw error")
|
||||
} else if e.Error() != "test error" {
|
||||
t.Error("NextID error message should be equal [test error]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseID(t *testing.T) {
|
||||
time := 101 << (snowflake.MachineIDLength + snowflake.SequenceLength)
|
||||
machineid := 1023 << snowflake.SequenceLength
|
||||
seq := 999
|
||||
|
||||
id := uint64(time | machineid | seq)
|
||||
|
||||
d := snowflake.ParseID(id)
|
||||
if d.Sequence != 999 {
|
||||
t.Error("Sequence should be equal 999")
|
||||
}
|
||||
|
||||
if d.MachineID != 1023 {
|
||||
t.Error("MachineID should be equal 1023")
|
||||
}
|
||||
|
||||
if d.Timestamp != 101 {
|
||||
t.Error("Timestamp should be equal 101")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSID_GenerateTime(t *testing.T) {
|
||||
sid := snowflake.ParseID(snowflake.ID())
|
||||
|
||||
if sid.GenerateTime().UTC().Second() != time.Now().UTC().Second() {
|
||||
t.Error("The id generate time should be equal")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user