From 80c99c0e836ddc3672fb367429ba16ef77819dbd Mon Sep 17 00:00:00 2001 From: Bruce Marriner Date: Fri, 12 Apr 2019 17:06:54 -0500 Subject: [PATCH] Added Parsers and Tests fixes #6 --- README.md | 2 +- snowflake.go | 56 +++++++- snowflake_test.go | 320 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 330 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 0e6623c..612288d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ snowflake ==== -[![GoDoc](https://godoc.org/github.com/bwmarrin/snowflake?status.svg)](https://godoc.org/github.com/bwmarrin/snowflake) [![Go report](http://goreportcard.com/badge/bwmarrin/snowflake)](http://goreportcard.com/report/bwmarrin/snowflake) [![Build Status](https://travis-ci.org/bwmarrin/snowflake.svg?branch=master)](https://travis-ci.org/bwmarrin/snowflake) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23info-blue.svg)](https://discord.gg/0f1SbxBZjYq9jLBk) +[![GoDoc](https://godoc.org/github.com/bwmarrin/snowflake?status.svg)](https://godoc.org/github.com/bwmarrin/snowflake) [![Go report](http://goreportcard.com/badge/bwmarrin/snowflake)](http://goreportcard.com/report/bwmarrin/snowflake) [![Coverage](http://gocover.io/_badge/github.com/bwmarrin/snowflake)](https://gocover.io/github.com/bwmarrin/snowflake) [![Build Status](https://travis-ci.org/bwmarrin/snowflake.svg?branch=master)](https://travis-ci.org/bwmarrin/snowflake) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23info-blue.svg)](https://discord.gg/0f1SbxBZjYq9jLBk) snowflake is a [Go](https://golang.org/) package that provides * A very simple Twitter snowflake generator. diff --git a/snowflake.go b/snowflake.go index c4dc536..3ea0856 100644 --- a/snowflake.go +++ b/snowflake.go @@ -166,25 +166,38 @@ func (f ID) Int64() int64 { return int64(f) } +// ParseInt64 converts an int64 into a snowflake ID +func ParseInt64(id int64) ID { + return ID(id) +} + // String returns a string of the snowflake ID func (f ID) String() string { return strconv.FormatInt(int64(f), 10) } +// ParseString converts a string into a snowflake ID +func ParseString(id string) (ID, error) { + i, err := strconv.ParseInt(id, 10, 64) + return ID(i), err + +} + // Base2 returns a string base2 of the snowflake ID func (f ID) Base2() string { return strconv.FormatInt(int64(f), 2) } -// Base36 returns a base36 string of the snowflake ID -func (f ID) Base36() string { - return strconv.FormatInt(int64(f), 36) +// ParseBase2 converts a Base2 string into a snowflake ID +func ParseBase2(id string) (ID, error) { + i, err := strconv.ParseInt(id, 2, 64) + return ID(i), err } // Base32 uses the z-base-32 character set but encodes and decodes similar // to base58, allowing it to create an even smaller result string. // NOTE: There are many different base32 implementations so becareful when -// doing any interoperation interop with other packages. +// doing any interoperation. func (f ID) Base32() string { if f < 32 { @@ -207,7 +220,7 @@ func (f ID) Base32() string { // ParseBase32 parses a base32 []byte into a snowflake ID // NOTE: There are many different base32 implementations so becareful when -// doing any interoperation interop with other packages. +// doing any interoperation. func ParseBase32(b []byte) (ID, error) { var id int64 @@ -222,6 +235,17 @@ func ParseBase32(b []byte) (ID, error) { return ID(id), nil } +// Base36 returns a base36 string of the snowflake ID +func (f ID) Base36() string { + return strconv.FormatInt(int64(f), 36) +} + +// ParseBase36 converts a Base36 string into a snowflake ID +func ParseBase36(id string) (ID, error) { + i, err := strconv.ParseInt(id, 36, 64) + return ID(i), err +} + // Base58 returns a base58 string of the snowflake ID func (f ID) Base58() string { @@ -263,11 +287,27 @@ func (f ID) Base64() string { return base64.StdEncoding.EncodeToString(f.Bytes()) } +// ParseBase64 converts a base64 string into a snowflake ID +func ParseBase64(id string) (ID, error) { + b, err := base64.StdEncoding.DecodeString(id) + if err != nil { + return -1, err + } + return ParseBytes(b) + +} + // Bytes returns a byte slice of the snowflake ID func (f ID) Bytes() []byte { return []byte(f.String()) } +// ParseBytes converts a byte slice into a snowflake ID +func ParseBytes(id []byte) (ID, error) { + i, err := strconv.ParseInt(string(id), 10, 64) + return ID(i), err +} + // IntBytes returns an array of bytes of the snowflake ID, encoded as a // big endian integer. func (f ID) IntBytes() [8]byte { @@ -276,6 +316,12 @@ func (f ID) IntBytes() [8]byte { return b } +// ParseIntBytes converts an array of bytes encoded as big endian integer as +// a snowflake ID +func ParseIntBytes(id [8]byte) ID { + return ID(int64(binary.BigEndian.Uint64(id[:]))) +} + // Time returns an int64 unix timestamp in milliseconds of the snowflake ID time // DEPRECATED: the below function will be removed in a future release. func (f ID) Time() int64 { diff --git a/snowflake_test.go b/snowflake_test.go index cf857b4..83e19ca 100644 --- a/snowflake_test.go +++ b/snowflake_test.go @@ -6,13 +6,17 @@ import ( "testing" ) -// check if Generate will create duplicate IDs +//****************************************************************************** +// General Test funcs + +// lazy check if Generate will create duplicate IDs +// would be good to later enhance this with more smarts func TestGenerateDuplicateID(t *testing.T) { node, _ := NewNode(1) var x, y ID - for i := 0; i < 100000000; i++ { + for i := 0; i < 1000000; i++ { y = node.Generate() if x == y { t.Errorf("x(%d) & y(%d) are the same", x, y) @@ -40,17 +44,282 @@ func TestRace(t *testing.T) { } +//****************************************************************************** +// Converters/Parsers Test funcs +// We should have funcs here to test conversion both ways for everything + +func TestPrintAll(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + id := node.Generate() + + t.Logf("Int64 : %#v", id.Int64()) + t.Logf("String : %#v", id.String()) + t.Logf("Base2 : %#v", id.Base2()) + t.Logf("Base32 : %#v", id.Base32()) + t.Logf("Base36 : %#v", id.Base36()) + t.Logf("Base58 : %#v", id.Base58()) + t.Logf("Base64 : %#v", id.Base64()) + t.Logf("Bytes : %#v", id.Bytes()) + t.Logf("IntBytes : %#v", id.IntBytes()) + +} + +func TestInt64(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.Int64() + + pID := ParseInt64(i) + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + mi := int64(1116766490855473152) + pID = ParseInt64(mi) + if pID.Int64() != mi { + t.Fatalf("pID %v != mi %v", pID.Int64(), mi) + } + +} + +func TestString(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + si := oID.String() + + pID, err := ParseString(si) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := `1116766490855473152` + _, err = ParseString(ms) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + + ms = `1112316766490855473152` + _, err = ParseString(ms) + if err == nil { + t.Fatalf("no error parsing %s", ms) + } +} + +func TestBase2(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.Base2() + + pID, err := ParseBase2(i) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := `111101111111101110110101100101001000000000000000000000000000` + _, err = ParseBase2(ms) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + + ms = `1112316766490855473152` + _, err = ParseBase2(ms) + if err == nil { + t.Fatalf("no error parsing %s", ms) + } +} + +func TestBase32(t *testing.T) { + + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + for i := 0; i < 100; i++ { + + sf := node.Generate() + b32i := sf.Base32() + psf, err := ParseBase32([]byte(b32i)) + if err != nil { + t.Fatal(err) + } + if sf != psf { + t.Fatal("Parsed does not match String.") + } + } +} + +func TestBase36(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.Base36() + + pID, err := ParseBase36(i) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := `8hgmw4blvlkw` + _, err = ParseBase36(ms) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + + ms = `68h5gmw443blv2lk1w` + _, err = ParseBase36(ms) + if err == nil { + t.Fatalf("no error parsing, %s", err) + } +} + +func TestBase58(t *testing.T) { + + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + for i := 0; i < 10; i++ { + + sf := node.Generate() + b58 := sf.Base58() + psf, err := ParseBase58([]byte(b58)) + if err != nil { + t.Fatal(err) + } + if sf != psf { + t.Fatal("Parsed does not match String.") + } + } +} + +func TestBase64(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.Base64() + + pID, err := ParseBase64(i) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := `MTExNjgxOTQ5NDY2MDk5NzEyMA==` + _, err = ParseBase64(ms) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + + ms = `MTExNjgxOTQ5NDY2MDk5NzEyMA` + _, err = ParseBase64(ms) + if err == nil { + t.Fatalf("no error parsing, %s", err) + } +} + +func TestBytes(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.Bytes() + + pID, err := ParseBytes(i) + if err != nil { + t.Fatalf("error parsing, %s", err) + } + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := []byte{0x31, 0x31, 0x31, 0x36, 0x38, 0x32, 0x31, 0x36, 0x37, 0x39, 0x35, 0x37, 0x30, 0x34, 0x31, 0x39, 0x37, 0x31, 0x32} + _, err = ParseBytes(ms) + if err != nil { + t.Fatalf("error parsing, %#v", err) + } + + ms = []byte{0xFF, 0xFF, 0xFF, 0x31, 0x31, 0x31, 0x36, 0x38, 0x32, 0x31, 0x36, 0x37, 0x39, 0x35, 0x37, 0x30, 0x34, 0x31, 0x39, 0x37, 0x31, 0x32} + _, err = ParseBytes(ms) + if err == nil { + t.Fatalf("no error parsing, %#v", err) + } +} + +func TestIntBytes(t *testing.T) { + node, err := NewNode(0) + if err != nil { + t.Fatalf("error creating NewNode, %s", err) + } + + oID := node.Generate() + i := oID.IntBytes() + + pID := ParseIntBytes(i) + if pID != oID { + t.Fatalf("pID %v != oID %v", pID, oID) + } + + ms := [8]uint8{0xf, 0x7f, 0xc0, 0xfc, 0x2f, 0x80, 0x0, 0x0} + mi := int64(1116823421972381696) + pID = ParseIntBytes(ms) + if pID.Int64() != mi { + t.Fatalf("pID %v != mi %v", pID.Int64(), mi) + } + +} + +//****************************************************************************** +// Marshall Test Methods + func TestMarshalJSON(t *testing.T) { id := ID(13587) expected := "\"13587\"" bytes, err := id.MarshalJSON() if err != nil { - t.Error("Unexpected error during MarshalJSON") + t.Fatalf("Unexpected error during MarshalJSON") } if string(bytes) != expected { - t.Errorf("Got %s, expected %s", string(bytes), expected) + t.Fatalf("Got %s, expected %s", string(bytes), expected) } } @@ -58,7 +327,7 @@ func TestMarshalsIntBytes(t *testing.T) { id := ID(13587).IntBytes() expected := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x35, 0x13} if !bytes.Equal(id[:], expected) { - t.Errorf("Expected ID to be encoded as %v, got %v", expected, id) + t.Fatalf("Expected ID to be encoded as %v, got %v", expected, id) } } @@ -77,32 +346,17 @@ func TestUnmarshalJSON(t *testing.T) { var id ID err := id.UnmarshalJSON([]byte(tc.json)) if !reflect.DeepEqual(err, tc.expectedErr) { - t.Errorf("Expected to get error '%s' decoding JSON, but got '%s'", tc.expectedErr, err) + t.Fatalf("Expected to get error '%s' decoding JSON, but got '%s'", tc.expectedErr, err) } if id != tc.expectedID { - t.Errorf("Expected to get ID '%s' decoding JSON, but got '%s'", tc.expectedID, id) + t.Fatalf("Expected to get ID '%s' decoding JSON, but got '%s'", tc.expectedID, id) } } } -func TestBase32(t *testing.T) { - - node, _ := NewNode(1) - - for i := 0; i < 100; i++ { - - sf := node.Generate() - b32i := sf.Base32() - psf, err := ParseBase32([]byte(b32i)) - if err != nil { - t.Fatal(err) - } - if sf != psf { - t.Fatal("Parsed does not match String.") - } - } -} +// **************************************************************************** +// Benchmark Methods func BenchmarkParseBase32(b *testing.B) { @@ -129,24 +383,6 @@ func BenchmarkBase32(b *testing.B) { sf.Base32() } } -func TestBase58(t *testing.T) { - - node, _ := NewNode(1) - - for i := 0; i < 10; i++ { - - sf := node.Generate() - b58 := sf.Base58() - psf, err := ParseBase58([]byte(b58)) - if err != nil { - t.Fatal(err) - } - if sf != psf { - t.Fatal("Parsed does not match String.") - } - } -} - func BenchmarkParseBase58(b *testing.B) { node, _ := NewNode(1)