ksuid_test.go 7.1 KB
package ksuid

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"sort"
	"strings"
	"testing"
	"time"
)

func TestConstructionTimestamp(t *testing.T) {
	x := New()
	nowTime := time.Now().Round(1 * time.Minute)
	xTime := x.Time().Round(1 * time.Minute)

	if xTime != nowTime {
		t.Fatal(xTime, "!=", nowTime)
	}
}

func TestNil(t *testing.T) {
	if !Nil.IsNil() {
		t.Fatal("Nil should be Nil!")
	}

	x, _ := FromBytes(make([]byte, byteLength))
	if !x.IsNil() {
		t.Fatal("Zero-byte array should be Nil!")
	}
}

func TestEncoding(t *testing.T) {
	x, _ := FromBytes(make([]byte, byteLength))
	if !x.IsNil() {
		t.Fatal("Zero-byte array should be Nil!")
	}

	encoded := x.String()
	expected := strings.Repeat("0", stringEncodedLength)

	if encoded != expected {
		t.Fatal("expected", expected, "encoded", encoded)
	}
}

func TestPadding(t *testing.T) {
	b := make([]byte, byteLength)
	for i := 0; i < byteLength; i++ {
		b[i] = 255
	}

	x, _ := FromBytes(b)
	xEncoded := x.String()
	nilEncoded := Nil.String()

	if len(xEncoded) != len(nilEncoded) {
		t.Fatal("Encoding should produce equal-length strings for zero and max case")
	}
}

func TestParse(t *testing.T) {
	_, err := Parse("123")
	if err != errStrSize {
		t.Fatal("Expected Parsing a 3-char string to return an error")
	}

	parsed, err := Parse(strings.Repeat("0", stringEncodedLength))
	if err != nil {
		t.Fatal("Unexpected error", err)
	}

	if Compare(parsed, Nil) != 0 {
		t.Fatal("Parsing all-zeroes string should equal Nil value",
			"expected:", Nil,
			"actual:", parsed)
	}

	maxBytes := make([]byte, byteLength)
	for i := 0; i < byteLength; i++ {
		maxBytes[i] = 255
	}
	maxBytesKSUID, err := FromBytes(maxBytes)
	if err != nil {
		t.Fatal("Unexpected error", err)
	}

	maxParseKSUID, err := Parse(maxStringEncoded)
	if err != nil {
		t.Fatal("Unexpected error", err)
	}

	if Compare(maxBytesKSUID, maxParseKSUID) != 0 {
		t.Fatal("String decoder broke for max string")
	}
}

func TestIssue25(t *testing.T) {
	// https://github.com/segmentio/ksuid/issues/25
	for _, s := range []string{
		"aaaaaaaaaaaaaaaaaaaaaaaaaaa",
		"aWgEPTl1tmebfsQzFP4bxwgy80!",
	} {
		_, err := Parse(s)
		if err != errStrValue {
			t.Error("invalid KSUID representations cannot be successfully parsed, got err =", err)
		}
	}
}

func TestEncodeAndDecode(t *testing.T) {
	x := New()
	builtFromEncodedString, err := Parse(x.String())
	if err != nil {
		t.Fatal("Unexpected error", err)
	}

	if Compare(x, builtFromEncodedString) != 0 {
		t.Fatal("Parse(X).String() != X")
	}
}

func TestMarshalText(t *testing.T) {
	var id1 = New()
	var id2 KSUID

	if err := id2.UnmarshalText([]byte(id1.String())); err != nil {
		t.Fatal(err)
	}

	if id1 != id2 {
		t.Fatal(id1, "!=", id2)
	}

	if b, err := id2.MarshalText(); err != nil {
		t.Fatal(err)
	} else if s := string(b); s != id1.String() {
		t.Fatal(s)
	}
}

func TestMarshalBinary(t *testing.T) {
	var id1 = New()
	var id2 KSUID

	if err := id2.UnmarshalBinary(id1.Bytes()); err != nil {
		t.Fatal(err)
	}

	if id1 != id2 {
		t.Fatal(id1, "!=", id2)
	}

	if b, err := id2.MarshalBinary(); err != nil {
		t.Fatal(err)
	} else if bytes.Compare(b, id1.Bytes()) != 0 {
		t.Fatal("bad binary form:", id2)
	}
}

func TestMashalJSON(t *testing.T) {
	var id1 = New()
	var id2 KSUID

	if b, err := json.Marshal(id1); err != nil {
		t.Fatal(err)
	} else if err := json.Unmarshal(b, &id2); err != nil {
		t.Fatal(err)
	} else if id1 != id2 {
		t.Error(id1, "!=", id2)
	}
}

func TestFlag(t *testing.T) {
	var id1 = New()
	var id2 KSUID

	fset := flag.NewFlagSet("test", flag.ContinueOnError)
	fset.Var(&id2, "id", "the KSUID")

	if err := fset.Parse([]string{"-id", id1.String()}); err != nil {
		t.Fatal(err)
	}

	if id1 != id2 {
		t.Error(id1, "!=", id2)
	}
}

func TestSqlValuer(t *testing.T) {
	id, _ := Parse(maxStringEncoded)

	if v, err := id.Value(); err != nil {
		t.Error(err)
	} else if s, ok := v.(string); !ok {
		t.Error("not a string value")
	} else if s != maxStringEncoded {
		t.Error("bad string value::", s)
	}
}

func TestSqlValuerNilValue(t *testing.T) {
	if v, err := Nil.Value(); err != nil {
		t.Error(err)
	} else if v != nil {
		t.Errorf("bad nil value: %v", v)
	}
}

func TestSqlScanner(t *testing.T) {
	id1 := New()
	id2 := New()

	tests := []struct {
		ksuid KSUID
		value interface{}
	}{
		{Nil, nil},
		{id1, id1.String()},
		{id2, id2.Bytes()},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%T", test.value), func(t *testing.T) {
			var id KSUID

			if err := id.Scan(test.value); err != nil {
				t.Error(err)
			}

			if id != test.ksuid {
				t.Error("bad KSUID:")
				t.Logf("expected %v", test.ksuid)
				t.Logf("found    %v", id)
			}
		})
	}
}

func TestAppend(t *testing.T) {
	for _, repr := range []string{"0pN1Own7255s7jwpwy495bAZeEa", "aWgEPTl1tmebfsQzFP4bxwgy80V"} {
		k, _ := Parse(repr)
		a := make([]byte, 0, stringEncodedLength)

		a = append(a, "?: "...)
		a = k.Append(a)

		if s := string(a); s != "?: "+repr {
			t.Error(s)
		}
	}
}

func TestSort(t *testing.T) {
	ids1 := [11]KSUID{}
	ids2 := [11]KSUID{}

	for i := range ids1 {
		ids1[i] = New()
	}

	ids2 = ids1
	sort.Slice(ids2[:], func(i, j int) bool {
		return Compare(ids2[i], ids2[j]) < 0
	})

	Sort(ids1[:])

	if !IsSorted(ids1[:]) {
		t.Error("not sorted")
	}

	if ids1 != ids2 {
		t.Error("bad order:")
		t.Log(ids1)
		t.Log(ids2)
	}
}

func TestPrevNext(t *testing.T) {
	tests := []struct {
		id   KSUID
		prev KSUID
		next KSUID
	}{
		{
			id:   Nil,
			prev: Max,
			next: KSUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
		},
		{
			id:   Max,
			prev: KSUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe},
			next: Nil,
		},
	}

	for _, test := range tests {
		t.Run(test.id.String(), func(t *testing.T) {
			testPrevNext(t, test.id, test.prev, test.next)
		})
	}
}

func TestGetTimestamp(t *testing.T) {
	nowTime := time.Now()
	x, _ := NewRandomWithTime(nowTime)
	xTime := int64(x.Timestamp())
	unix := nowTime.Unix()
	if xTime != unix - epochStamp {
		t.Fatal(xTime, "!=", unix)
	}
}

func testPrevNext(t *testing.T, id, prev, next KSUID) {
	id1 := id.Prev()
	id2 := id.Next()

	if id1 != prev {
		t.Error("previous id of the nil KSUID is wrong:", id1, "!=", prev)
	}

	if id2 != next {
		t.Error("next id of the nil KSUID is wrong:", id2, "!=", next)
	}
}

func BenchmarkAppend(b *testing.B) {
	a := make([]byte, 0, stringEncodedLength)
	k := New()

	for i := 0; i != b.N; i++ {
		k.Append(a)
	}
}

func BenchmarkString(b *testing.B) {
	k := New()

	for i := 0; i != b.N; i++ {
		_ = k.String()
	}
}

func BenchmarkParse(b *testing.B) {
	for i := 0; i != b.N; i++ {
		Parse(maxStringEncoded)
	}
}

func BenchmarkCompare(b *testing.B) {
	k1 := New()
	k2 := New()

	for i := 0; i != b.N; i++ {
		Compare(k1, k2)
	}
}

func BenchmarkSort(b *testing.B) {
	ids1 := [101]KSUID{}
	ids2 := [101]KSUID{}

	for i := range ids1 {
		ids1[i] = New()
	}

	for i := 0; i != b.N; i++ {
		ids2 = ids1
		Sort(ids2[:])
	}
}

func BenchmarkNew(b *testing.B) {
	b.Run("with crypto rand", func(b *testing.B) {
		SetRand(nil)
		for i := 0; i != b.N; i++ {
			New()
		}
	})
	b.Run("with math rand", func(b *testing.B) {
		SetRand(FastRander)
		for i := 0; i != b.N; i++ {
			New()
		}
	})
}