func TestCustomTimestamp(t *testing.T) { // timestamp - epoch = adjusted time testCases := []struct { ts int64 adjTs int64 }{ {1397666977000, 72290977000}, // Now {1397666978000, 72290978000}, // in 1 second {1395881056000, 70505056000}, // 3 weeks ago {1303001162000, -22374838000}, // 3 years ago {1492390054000, 167014054000}, // in 3 years {2344466898000, 1019090898000}, // in 30 years } // Initialise our custom epoch epoch, err := time.Parse(time.RFC3339, defaultEpoch) require.NoError(t, err) epochMs := util.TimeToMsInt64(epoch) for _, tc := range testCases { adjTs := util.CustomTimestamp(epochMs, time.Unix(tc.ts/1000, 0)) assert.Equal(t, adjTs, tc.adjTs, "Times should match") } }
// Mint a new 128bit ID based on the current time, worker id and sequence func (bf *Bigflake) Mint() (*BigflakeId, error) { bf.Lock() defer bf.Unlock() // Setup locks in our configured options bf.once.Do(bf.setup) // Ensure we only mint IDs if correctly configured if bf.workerId > bf.maxWorkerId { return nil, ErrInvalidWorkerId } // Get the current timestamp in ms // @todo generalise to allow custom epoch t := util.TimeToMsInt64(time.Now()) // Update bigflake with this, which Mawill increment sequence number if needed err := bf.update(t) if err != nil { return nil, err } // Mint a new ID id := bf.mintId(bf.lastTimestamp, bf.workerId, bf.sequence, 48, 16) bfId := &BigflakeId{ id: id, } return bfId, nil }
// New creates a new instance of a snowflake compatible ID minter // the worker ID must be unique otherwise ID collisions are likely to occur func New(workerId uint32) (*Snowflake, error) { // initialise with the defaults, including epoch // 2012-01-01 00:00:00 +0000 UTC => 1325376000000 epoch, err := time.Parse(time.RFC3339, defaultEpoch) if err != nil { return nil, err } return &Snowflake{ workerId: workerId, sequenceBits: defaultSequenceBits, workerIdBits: defaultWorkerIdBits, epoch: util.TimeToMsInt64(epoch), }, nil }
func newBigflakeMinter(t *testing.T) *Bigflake { mac := "80:36:bc:db:64:16" workerId, err := util.MacAddressToWorkerId(mac) if err != nil { t.Fail() } return &Bigflake{ lastTimestamp: util.TimeToMsInt64(time.Now()), workerIdBits: defaultWorkerIdBits, sequenceBits: defaultSequenceBits, workerId: int64(workerId), sequence: 0, epoch: 0, } }
func ksortability(t *testing.T, formatFunc func(id *BigflakeId) string) { var ( lexicalOrder sort.StringSlice = make([]string, 0) originalOrder = make([]string, 0) id = &BigflakeId{} // Allow us to progressively jump forwards in time timeDiff time.Duration = 10 * time.Millisecond ) // Generate lots of ids bf, err := New(0) require.NoError(t, err) bf.setup() for i := 0; i < 10000000; i++ { if i%300000 == 0 { timeDiff = timeDiff * 2 t.Logf("Moved to %v offset", timeDiff) } // Update time, sequence etc err := bf.update(util.TimeToMsInt64(time.Now().Add(timeDiff))) require.NoError(t, err) id.id = bf.mintId(bf.lastTimestamp, bf.workerId, bf.sequence, 48, 16) idStr := formatFunc(id) lexicalOrder = append(lexicalOrder, idStr) originalOrder = append(originalOrder, idStr) } // Sort string array lexicalOrder.Sort() // Compare ordering var mismatch int64 for i, v := range originalOrder { if lexicalOrder[i] != v { mismatch++ } } assert.Equal(t, int64(0), mismatch, fmt.Sprintf("Expected zero mismatches, got %v", mismatch)) }
func TestSequenceOverflow(t *testing.T) { // Setup snowflake at a particular time which we will freeze at sf, err := New(0) require.NoError(t, err) tms := util.TimeToMsInt64(time.Now()) sf.lastTimestamp = tms invalidSequenceIds := []uint32{4096, 5841, 892347934} for _, seq := range invalidSequenceIds { // Fix the sequence ID, then update // This should fail, as we are within the same ms sf.sequence = seq err := sf.update(tms) assert.Error(t, err) assert.Equal(t, err, ErrSequenceOverflow, "Error should match") } }
func TestPreEpochTime(t *testing.T) { testCases := []time.Time{ time.Date(2012, 1, 0, 0, 0, 0, 0, time.UTC), time.Date(2011, 9, 5, 0, 0, 0, 0, time.UTC), time.Date(1066, 9, 5, 0, 0, 0, 0, time.UTC), } for _, tc := range testCases { sf, err := New(0) require.NoError(t, err) // Initialise our custom epoch epoch, err := time.Parse(time.RFC3339, defaultEpoch) require.NoError(t, err) epochMs := util.TimeToMsInt64(epoch) ts := util.CustomTimestamp(epochMs, tc) err = sf.update(ts) assert.Error(t, err) } }