diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..3e265af --- /dev/null +++ b/.cz.toml @@ -0,0 +1,5 @@ +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "$version" +version_scheme = "semver" +version = "0.0.1" diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..8f3c5df --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go 1.22 + uses: actions/setup-go@v3 + with: + go-version: 1.22 + + - name: Cache Go modules + uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Update dependencies + run: go mod tidy + + - name: Test with Coverage + run: go test ./... -coverprofile=coverage.out -covermode=atomic + + - name: Check Coverage + run: | + go tool cover -func=coverage.out -o coverage-summary.txt + COVERAGE=$(go tool cover -func=coverage.out | grep total: | awk '{print substr($3, 1, length($3)-1)}') + echo "Total test coverage: $COVERAGE%" + if (( $(echo "$COVERAGE < 60" |bc -l) )); then + echo "Test coverage is below 60%" + exit 1 + fi + env: + GO111MODULE: on + + - name: Install golangci-lint + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest + + - name: Run golangci-lint + run: golangci-lint run ./... + env: + GO111MODULE: on diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6a1b30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +.idea/ +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index acf8201..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: go -sudo: false -go: - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x - - tip -matrix: - allow_failures: - - go: tip - fast_finish: true -before_install: - - go get github.com/mattn/goveralls - - go get golang.org/x/tools/cmd/cover -script: - - $HOME/gopath/bin/goveralls -service=travis-ci -notifications: - email: false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20b1b43 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +.PHONY: install-tools check test + +# Go tool paths +GOLINT = $(shell go env GOPATH)/bin/golint +INEFFASSIGN = $(shell go env GOPATH)/bin/ineffassign +MISSPELL = $(shell go env GOPATH)/bin/misspell +GOCYCLO = $(shell go env GOPATH)/bin/gocyclo + +install-tools: + @echo "Installing tools..." + go install golang.org/x/lint/golint@latest + go install github.com/gordonklaus/ineffassign@latest + go install github.com/client9/misspell/cmd/misspell@latest + go install github.com/fzipp/gocyclo/cmd/gocyclo@latest + +check: install-tools + @echo "Running checks..." + go fmt ./... + go vet ./... + $(GOLINT) ./... + $(MISSPELL) -w . + $(GOCYCLO) -over 10 . + $(INEFFASSIGN) . + +test: + @echo "Running tests..." + go test -coverprofile=coverage.out -coverpkg=$$(go list ./... | grep -v /test$$ | grep -v main | grep -v '_repository.go$$' | tr '\n' ',') ./... + +coverage-report: test + @echo "Generating coverage report..." + go tool cover -func=coverage.out | grep total | awk '{print substr($$NF, 1, length($$NF)-1)}' > coverage.txt + @COVERAGE=$$(cat coverage.txt); \ + echo "Coverage: $$COVERAGE"; \ + if (( $$(echo "$$COVERAGE < 30" | bc -l) )); then \ + echo "Coverage is below 30%. Stopping the process."; \ + exit 1; \ + fi diff --git a/README.md b/README.md index 362b270..391ba21 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # UUID package for Go language [![Build Status](https://travis-ci.org/satori/go.uuid.svg?branch=master)](https://travis-ci.org/satori/go.uuid) +![CI](https://github.com/satori/go.uuid/actions/workflows/actions.yml/badge.svg) +[![Go Report Card](https://goreportcard.com/badge/github.com/satori/go.uuid)](https://goreportcard.com/report/github.com/satori/go.uuid) [![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) [![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.svg)](http://godoc.org/github.com/satori/go.uuid) diff --git a/codec.go b/codec.go index 656892c..1014353 100644 --- a/codec.go +++ b/codec.go @@ -30,6 +30,9 @@ import ( // FromBytes returns UUID converted from raw byte slice input. // It will return error if the slice isn't 16 bytes long. func FromBytes(input []byte) (u UUID, err error) { + if len(input) != Size { + return Nil, fmt.Errorf("uuid: expected %d bytes, got %d bytes", Size, len(input)) + } err = u.UnmarshalBinary(input) return } @@ -48,6 +51,9 @@ func FromBytesOrNil(input []byte) UUID { // Input is expected in a form accepted by UnmarshalText. func FromString(input string) (u UUID, err error) { err = u.UnmarshalText([]byte(input)) + if err != nil { + return Nil, fmt.Errorf("uuid: failed to parse UUID from string: %s", input) + } return } @@ -70,27 +76,30 @@ func (u UUID) MarshalText() (text []byte, err error) { // UnmarshalText implements the encoding.TextUnmarshaler interface. // Following formats are supported: -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", -// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", -// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" -// "6ba7b8109dad11d180b400c04fd430c8" +// +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" +// "6ba7b8109dad11d180b400c04fd430c8" +// // ABNF for supported UUID text representation follows: -// uuid := canonical | hashlike | braced | urn -// plain := canonical | hashlike -// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct -// hashlike := 12hexoct -// braced := '{' plain '}' -// urn := URN ':' UUID-NID ':' plain -// URN := 'urn' -// UUID-NID := 'uuid' -// 12hexoct := 6hexoct 6hexoct -// 6hexoct := 4hexoct 2hexoct -// 4hexoct := 2hexoct 2hexoct -// 2hexoct := hexoct hexoct -// hexoct := hexdig hexdig -// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | -// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | -// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' +// +// uuid := canonical | hashlike | braced | urn +// plain := canonical | hashlike +// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct +// hashlike := 12hexoct +// braced := '{' plain '}' +// urn := URN ':' UUID-NID ':' plain +// URN := 'urn' +// UUID-NID := 'uuid' +// 12hexoct := 6hexoct 6hexoct +// 6hexoct := 4hexoct 2hexoct +// 4hexoct := 2hexoct 2hexoct +// 2hexoct := hexoct hexoct +// hexoct := hexdig hexdig +// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | +// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | +// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' func (u *UUID) UnmarshalText(text []byte) (err error) { switch len(text) { case 32: @@ -112,65 +121,51 @@ func (u *UUID) UnmarshalText(text []byte) (err error) { // "6ba7b810-9dad-11d1-80b4-00c04fd430c8". func (u *UUID) decodeCanonical(t []byte) (err error) { if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) + return fmt.Errorf("uuid: incorrect UUID format %s", string(t)) } - src := t[:] dst := u[:] - for i, byteGroup := range byteGroups { if i > 0 { - src = src[1:] // skip dash + t = t[1:] // skip dash } - _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) - if err != nil { - return + if _, err := hex.Decode(dst[:byteGroup/2], t[:byteGroup]); err != nil { + return err } - src = src[byteGroup:] + t = t[byteGroup:] dst = dst[byteGroup/2:] } - return + return nil } // decodeHashLike decodes UUID string in format // "6ba7b8109dad11d180b400c04fd430c8". func (u *UUID) decodeHashLike(t []byte) (err error) { - src := t[:] - dst := u[:] - - if _, err = hex.Decode(dst, src); err != nil { - return err + if _, err = hex.Decode(u[:], t); err != nil { + return fmt.Errorf("uuid: failed to decode hash-like format: %s", string(t)) } - return + return nil } // decodeBraced decodes UUID string in format // "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format // "{6ba7b8109dad11d180b400c04fd430c8}". func (u *UUID) decodeBraced(t []byte) (err error) { - l := len(t) - - if t[0] != '{' || t[l-1] != '}' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) + if len(t) < 2 || t[0] != '{' || t[len(t)-1] != '}' { + return fmt.Errorf("uuid: incorrect UUID format %s", string(t)) } - - return u.decodePlain(t[1 : l-1]) + return u.decodePlain(t[1 : len(t)-1]) } // decodeURN decodes UUID string in format // "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format // "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". func (u *UUID) decodeURN(t []byte) (err error) { - total := len(t) - - urn_uuid_prefix := t[:9] - - if !bytes.Equal(urn_uuid_prefix, urnPrefix) { - return fmt.Errorf("uuid: incorrect UUID format: %s", t) + if len(t) < 9 || !bytes.Equal(t[:9], urnPrefix) { + return fmt.Errorf("uuid: incorrect URN format: %s", string(t)) } - - return u.decodePlain(t[9:total]) + return u.decodePlain(t[9:]) } // decodePlain decodes UUID string in canonical format @@ -183,24 +178,21 @@ func (u *UUID) decodePlain(t []byte) (err error) { case 36: return u.decodeCanonical(t) default: - return fmt.Errorf("uuid: incorrrect UUID length: %s", t) + return fmt.Errorf("uuid: incorrect UUID length: %s", string(t)) } } // MarshalBinary implements the encoding.BinaryMarshaler interface. func (u UUID) MarshalBinary() (data []byte, err error) { - data = u.Bytes() - return + return u.Bytes(), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. // It will return error if the slice isn't 16 bytes long. func (u *UUID) UnmarshalBinary(data []byte) (err error) { if len(data) != Size { - err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) - return + return fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) } copy(u[:], data) - - return + return nil } diff --git a/codec_test.go b/codec_test.go index 7158c09..1061fc1 100644 --- a/codec_test.go +++ b/codec_test.go @@ -22,149 +22,138 @@ package uuid import ( - "bytes" + "testing" - . "gopkg.in/check.v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -type codecTestSuite struct{} - -var _ = Suite(&codecTestSuite{}) - -func (s *codecTestSuite) TestFromBytes(c *C) { +func TestFromBytes(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} u1, err := FromBytes(b1) - c.Assert(err, IsNil) - c.Assert(u1, Equals, u) + require.NoError(t, err) + assert.Equal(t, u, u1) - b2 := []byte{} + var b2 []byte _, err = FromBytes(b2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *codecTestSuite) BenchmarkFromBytes(c *C) { +func BenchmarkFromBytes(b *testing.B) { bytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} - for i := 0; i < c.N; i++ { - FromBytes(bytes) + for i := 0; i < b.N; i++ { + _, _ = FromBytes(bytes) } } -func (s *codecTestSuite) TestMarshalBinary(c *C) { +func TestMarshalBinary(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b2, err := u.MarshalBinary() - c.Assert(err, IsNil) - c.Assert(bytes.Equal(b1, b2), Equals, true) + require.NoError(t, err) + assert.Equal(t, b1, b2) } -func (s *codecTestSuite) BenchmarkMarshalBinary(c *C) { +func BenchmarkMarshalBinary(b *testing.B) { u, err := NewV4() - c.Assert(err, IsNil) - for i := 0; i < c.N; i++ { - u.MarshalBinary() + require.NoError(b, err) + for i := 0; i < b.N; i++ { + _, _ = u.MarshalBinary() } } -func (s *codecTestSuite) TestUnmarshalBinary(c *C) { +func TestUnmarshalBinary(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} u1 := UUID{} err := u1.UnmarshalBinary(b1) - c.Assert(err, IsNil) - c.Assert(u1, Equals, u) + require.NoError(t, err) + assert.Equal(t, u, u1) - b2 := []byte{} + var b2 []byte u2 := UUID{} err = u2.UnmarshalBinary(b2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *codecTestSuite) TestFromString(c *C) { +func TestFromString(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} - s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" - s2 := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" - s3 := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" - s4 := "6ba7b8109dad11d180b400c04fd430c8" - s5 := "urn:uuid:6ba7b8109dad11d180b400c04fd430c8" - - _, err := FromString("") - c.Assert(err, NotNil) - - u1, err := FromString(s1) - c.Assert(err, IsNil) - c.Assert(u1, Equals, u) - - u2, err := FromString(s2) - c.Assert(err, IsNil) - c.Assert(u2, Equals, u) - - u3, err := FromString(s3) - c.Assert(err, IsNil) - c.Assert(u3, Equals, u) + tests := []struct { + input string + expected UUID + }{ + {"6ba7b810-9dad-11d1-80b4-00c04fd430c8", u}, + {"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", u}, + {"urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8", u}, + {"6ba7b8109dad11d180b400c04fd430c8", u}, + {"urn:uuid:6ba7b8109dad11d180b400c04fd430c8", u}, + } - u4, err := FromString(s4) - c.Assert(err, IsNil) - c.Assert(u4, Equals, u) + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + u1, err := FromString(tt.input) + require.NoError(t, err) + assert.Equal(t, tt.expected, u1) + }) + } - u5, err := FromString(s5) - c.Assert(err, IsNil) - c.Assert(u5, Equals, u) + _, err := FromString("") + assert.Error(t, err) } -func (s *codecTestSuite) BenchmarkFromString(c *C) { +func BenchmarkFromString(b *testing.B) { str := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" - for i := 0; i < c.N; i++ { - FromString(str) + for i := 0; i < b.N; i++ { + _, _ = FromString(str) } } -func (s *codecTestSuite) BenchmarkFromStringUrn(c *C) { +func BenchmarkFromStringUrn(b *testing.B) { str := "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" - for i := 0; i < c.N; i++ { - FromString(str) + for i := 0; i < b.N; i++ { + _, err := FromString(str) + require.NoError(b, err) } } -func (s *codecTestSuite) BenchmarkFromStringWithBrackets(c *C) { +func BenchmarkFromStringWithBrackets(b *testing.B) { str := "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" - for i := 0; i < c.N; i++ { - FromString(str) + for i := 0; i < b.N; i++ { + _, err := FromString(str) + require.NoError(b, err) } } -func (s *codecTestSuite) TestFromStringShort(c *C) { - // Invalid 35-character UUID string - s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c" - - for i := len(s1); i >= 0; i-- { - _, err := FromString(s1[:i]) - c.Assert(err, NotNil) +func TestFromStringShort(t *testing.T) { + shortString := "6ba7b810-9dad-11d1-80b4-00c04fd430c" + for i := len(shortString); i >= 0; i-- { + _, err := FromString(shortString[:i]) + assert.Error(t, err) } } -func (s *codecTestSuite) TestFromStringLong(c *C) { - // Invalid 37+ character UUID string - strings := []string{ +func TestFromStringLong(t *testing.T) { + longStrings := []string{ "6ba7b810-9dad-11d1-80b4-00c04fd430c8=", "6ba7b810-9dad-11d1-80b4-00c04fd430c8}", "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}f", "6ba7b810-9dad-11d1-80b4-00c04fd430c800c04fd430c8", } - - for _, str := range strings { - _, err := FromString(str) - c.Assert(err, NotNil) + for _, str := range longStrings { + t.Run(str, func(t *testing.T) { + _, err := FromString(str) + assert.Error(t, err) + }) } } -func (s *codecTestSuite) TestFromStringInvalid(c *C) { - // Invalid UUID string formats - strings := []string{ +func TestFromStringInvalid(t *testing.T) { + invalidStrings := []string{ "6ba7b8109dad11d180b400c04fd430c86ba7b8109dad11d180b400c04fd430c8", "urn:uuid:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", "uuid:urn:6ba7b810-9dad-11d1-80b4-00c04fd430c8", @@ -183,69 +172,71 @@ func (s *codecTestSuite) TestFromStringInvalid(c *C) { "6ba7b8109dad11d180b4-00c04fd430c8", } - for _, str := range strings { - _, err := FromString(str) - c.Assert(err, NotNil) + for _, str := range invalidStrings { + t.Run(str, func(t *testing.T) { + _, err := FromString(str) + assert.Error(t, err) + }) } } -func (s *codecTestSuite) TestFromStringOrNil(c *C) { +func TestFromStringOrNil(t *testing.T) { u := FromStringOrNil("") - c.Assert(u, Equals, Nil) + assert.Equal(t, Nil, u) } -func (s *codecTestSuite) TestFromBytesOrNil(c *C) { - b := []byte{} +func TestFromBytesOrNil(t *testing.T) { + var b []byte u := FromBytesOrNil(b) - c.Assert(u, Equals, Nil) + assert.Equal(t, Nil, u) } -func (s *codecTestSuite) TestMarshalText(c *C) { +func TestMarshalText(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") b2, err := u.MarshalText() - c.Assert(err, IsNil) - c.Assert(bytes.Equal(b1, b2), Equals, true) + require.NoError(t, err) + assert.Equal(t, b1, b2) } -func (s *codecTestSuite) BenchmarkMarshalText(c *C) { +func BenchmarkMarshalText(b *testing.B) { u, err := NewV4() - c.Assert(err, IsNil) - for i := 0; i < c.N; i++ { - u.MarshalText() + require.NoError(b, err) + for i := 0; i < b.N; i++ { + _, _ = u.MarshalText() } } -func (s *codecTestSuite) TestUnmarshalText(c *C) { +func TestUnmarshalText(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") u1 := UUID{} err := u1.UnmarshalText(b1) - c.Assert(err, IsNil) - c.Assert(u1, Equals, u) + require.NoError(t, err) + assert.Equal(t, u, u1) b2 := []byte("") u2 := UUID{} err = u2.UnmarshalText(b2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *codecTestSuite) BenchmarkUnmarshalText(c *C) { +func BenchmarkUnmarshalText(b *testing.B) { bytes := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") u := UUID{} - for i := 0; i < c.N; i++ { - u.UnmarshalText(bytes) + for i := 0; i < b.N; i++ { + _ = u.UnmarshalText(bytes) } } var sink string -func (s *codecTestSuite) BenchmarkMarshalToString(c *C) { +func BenchmarkMarshalToString(b *testing.B) { u, err := NewV4() - c.Assert(err, IsNil) - for i := 0; i < c.N; i++ { + require.NoError(b, err) + for i := 0; i < b.N; i++ { sink = u.String() } } diff --git a/generator.go b/generator.go index c50d33c..dd5d449 100644 --- a/generator.go +++ b/generator.go @@ -74,6 +74,16 @@ func NewV5(ns UUID, name string) UUID { return global.NewV5(ns, name) } +// NewV6 returns UUID +func NewV6() (UUID, error) { + return global.NewV6() +} + +// NewV7 returns UUID v7 +func NewV7() (UUID, error) { + return global.NewV7() +} + // Generator provides interface for generating UUIDs. type Generator interface { NewV1() (UUID, error) @@ -81,6 +91,8 @@ type Generator interface { NewV3(ns UUID, name string) UUID NewV4() (UUID, error) NewV5(ns UUID, name string) UUID + NewV6() (UUID, error) + NewV7() (UUID, error) } // Default generator implementation. @@ -112,7 +124,7 @@ func (g *rfc4122Generator) NewV1() (UUID, error) { timeNow, clockSeq, err := g.getClockSequence() if err != nil { - return Nil, err + return Nil, fmt.Errorf("failed to get clock sequence: %w", err) } binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) @@ -121,7 +133,7 @@ func (g *rfc4122Generator) NewV1() (UUID, error) { hardwareAddr, err := g.getHardwareAddr() if err != nil { - return Nil, err + return Nil, fmt.Errorf("failed to get hardware address: %w", err) } copy(u[10:], hardwareAddr) @@ -156,31 +168,80 @@ func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { // NewV3 returns UUID based on MD5 hash of namespace UUID and name. func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { u := newFromHash(md5.New(), ns, name) - u.SetVersion(V3) - u.SetVariant(VariantRFC4122) - - return u + return finalizeUUID(u, V3) } // NewV4 returns random generated UUID. func (g *rfc4122Generator) NewV4() (UUID, error) { u := UUID{} if _, err := io.ReadFull(g.rand, u[:]); err != nil { - return Nil, err + return Nil, fmt.Errorf("failed to generate random UUID: %w", err) } - u.SetVersion(V4) - u.SetVariant(VariantRFC4122) - - return u, nil + return finalizeUUID(u, V4), nil } // NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { u := newFromHash(sha1.New(), ns, name) - u.SetVersion(V5) + return finalizeUUID(u, V5) +} + +// NewV6 returns UUID v6 +func (g *rfc4122Generator) NewV6() (UUID, error) { + u := UUID{} + + timeNow, clockSeq, err := g.getClockSequence() + if err != nil { + return Nil, fmt.Errorf("failed to get clock sequence: %w", err) + } + + // Reorder time fields for V6 + binary.BigEndian.PutUint16(u[0:], uint16(timeNow>>48)) // time_high + binary.BigEndian.PutUint32(u[2:], uint32(timeNow>>16)) // time_mid + binary.BigEndian.PutUint16(u[6:], uint16(timeNow)) // time_low + binary.BigEndian.PutUint16(u[8:], clockSeq) // clock_seq + + hardwareAddr, err := g.getHardwareAddr() + if err != nil { + return Nil, fmt.Errorf("failed to get hardware address: %w", err) + } + copy(u[10:], hardwareAddr) + + u.SetVersion(V6) u.SetVariant(VariantRFC4122) - return u + return u, nil +} + +// NewV7 returns UUID v7 +func (g *rfc4122Generator) NewV7() (UUID, error) { + u := UUID{} + + // Timestamp in milliseconds since Unix epoch + timeNow := uint64(time.Now().UnixNano() / 1e6) + putUint48(u[:6], timeNow) + + // Random data + if _, err := io.ReadFull(g.rand, u[6:]); err != nil { + return Nil, fmt.Errorf("failed to generate random data for UUID V7: %w", err) + } + + u.SetVersion(V7) + u.SetVariant(VariantRFC4122) + + return u, nil +} + +func putUint48(b []byte, v uint64) { + if len(b) < 6 { + return // o podrĂ­as manejar un error si prefieres + } + b[0] = byte(v >> 40) + b[1] = byte(v >> 32) + b[2] = byte(v >> 24) + b[3] = byte(v >> 16) + b[4] = byte(v >> 8) + b[5] = byte(v) } // Returns epoch and clock sequence. @@ -189,6 +250,7 @@ func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { g.clockSequenceOnce.Do(func() { buf := make([]byte, 2) if _, err = io.ReadFull(g.rand, buf); err != nil { + err = fmt.Errorf("failed to read random data for clock sequence: %w", err) return } g.clockSequence = binary.BigEndian.Uint16(buf) @@ -201,8 +263,6 @@ func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { defer g.storageMutex.Unlock() timeNow := g.getEpoch() - // Clock didn't change since last UUID generation. - // Should increase clock sequence. if timeNow <= g.lastTime { g.clockSequence++ } @@ -215,21 +275,17 @@ func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { var err error g.hardwareAddrOnce.Do(func() { - if hwAddr, err := g.hwAddrFunc(); err == nil { + hwAddr, hwErr := g.hwAddrFunc() + if hwErr == nil { copy(g.hardwareAddr[:], hwAddr) - return - } - - // Initialize hardwareAddr randomly in case - // of real network interfaces absence. - if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err != nil { - return + } else { + if _, err = io.ReadFull(g.rand, g.hardwareAddr[:]); err == nil { + g.hardwareAddr[0] |= 0x01 // Set multicast bit + } } - // Set multicast bit as recommended by RFC 4122 - g.hardwareAddr[0] |= 0x01 }) if err != nil { - return []byte{}, err + return nil, fmt.Errorf("failed to get hardware address: %w", err) } return g.hardwareAddr[:], nil } @@ -254,12 +310,18 @@ func newFromHash(h hash.Hash, ns UUID, name string) UUID { func defaultHWAddrFunc() (net.HardwareAddr, error) { ifaces, err := net.Interfaces() if err != nil { - return []byte{}, err + return nil, fmt.Errorf("failed to get network interfaces: %w", err) } for _, iface := range ifaces { if len(iface.HardwareAddr) >= 6 { return iface.HardwareAddr, nil } } - return []byte{}, fmt.Errorf("uuid: no HW address found") + return nil, fmt.Errorf("uuid: no HW address found") +} + +func finalizeUUID(u UUID, version byte) UUID { + u.SetVersion(version) + u.SetVariant(VariantRFC4122) + return u } diff --git a/generator_test.go b/generator_test.go index a445fb0..fbbe7ae 100644 --- a/generator_test.go +++ b/generator_test.go @@ -25,11 +25,12 @@ import ( "bytes" "crypto/rand" "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "net" + "testing" "testing/iotest" "time" - - . "gopkg.in/check.v1" ) type faultyReader struct { @@ -45,22 +46,18 @@ func (r *faultyReader) Read(dest []byte) (int, error) { return rand.Read(dest) } -type genTestSuite struct{} - -var _ = Suite(&genTestSuite{}) - -func (s *genTestSuite) TestNewV1(c *C) { +func TestNewV1(t *testing.T) { u1, err := NewV1() - c.Assert(err, IsNil) - c.Assert(u1.Version(), Equals, V1) - c.Assert(u1.Variant(), Equals, VariantRFC4122) + require.NoError(t, err) + assert.Equal(t, V1, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) u2, err := NewV1() - c.Assert(err, IsNil) - c.Assert(u1, Not(Equals), u2) + require.NoError(t, err) + assert.NotEqual(t, u1, u2) } -func (s *genTestSuite) TestNewV1EpochStale(c *C) { +func TestNewV1EpochStale(t *testing.T) { g := &rfc4122Generator{ epochFunc: func() time.Time { return time.Unix(0, 0) @@ -69,172 +66,213 @@ func (s *genTestSuite) TestNewV1EpochStale(c *C) { rand: rand.Reader, } u1, err := g.NewV1() - c.Assert(err, IsNil) + require.NoError(t, err) + u2, err := g.NewV1() - c.Assert(err, IsNil) - c.Assert(u1, Not(Equals), u2) + require.NoError(t, err) + assert.NotEqual(t, u1, u2) } -func (s *genTestSuite) TestNewV1FaultyRand(c *C) { +func TestNewV1FaultyRand(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{}, } u1, err := g.NewV1() - c.Assert(err, NotNil) - c.Assert(u1, Equals, Nil) + require.Error(t, err) + assert.Equal(t, Nil, u1) } -func (s *genTestSuite) TestNewV1MissingNetworkInterfaces(c *C) { +func TestNewV1MissingNetworkInterfaces(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: func() (net.HardwareAddr, error) { - return []byte{}, fmt.Errorf("uuid: no hw address found") + return nil, fmt.Errorf("uuid: no hw address found") }, rand: rand.Reader, } - _, err := g.NewV1() - c.Assert(err, IsNil) + u1, err := g.NewV1() + require.NoError(t, err) + assert.NotEqual(t, Nil, u1) } -func (s *genTestSuite) TestNewV1MissingNetInterfacesAndFaultyRand(c *C) { +func TestNewV1MissingNetInterfacesAndFaultyRand(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: func() (net.HardwareAddr, error) { - return []byte{}, fmt.Errorf("uuid: no hw address found") + return nil, fmt.Errorf("uuid: no hw address found") }, rand: &faultyReader{ readToFail: 1, }, } u1, err := g.NewV1() - c.Assert(err, NotNil) - c.Assert(u1, Equals, Nil) + require.Error(t, err) + assert.Equal(t, Nil, u1) } -func (s *genTestSuite) BenchmarkNewV1(c *C) { - for i := 0; i < c.N; i++ { - NewV1() +func BenchmarkNewV1(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = NewV1() } } -func (s *genTestSuite) TestNewV2(c *C) { +func TestNewV2(t *testing.T) { u1, err := NewV2(DomainPerson) - c.Assert(err, IsNil) - c.Assert(u1.Version(), Equals, V2) - c.Assert(u1.Variant(), Equals, VariantRFC4122) + require.NoError(t, err) + assert.Equal(t, V2, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) u2, err := NewV2(DomainGroup) - c.Assert(err, IsNil) - c.Assert(u2.Version(), Equals, V2) - c.Assert(u2.Variant(), Equals, VariantRFC4122) + require.NoError(t, err) + assert.Equal(t, V2, u2.Version()) + assert.Equal(t, VariantRFC4122, u2.Variant()) u3, err := NewV2(DomainOrg) - c.Assert(err, IsNil) - c.Assert(u3.Version(), Equals, V2) - c.Assert(u3.Variant(), Equals, VariantRFC4122) + require.NoError(t, err) + assert.Equal(t, V2, u3.Version()) + assert.Equal(t, VariantRFC4122, u3.Variant()) } -func (s *genTestSuite) TestNewV2FaultyRand(c *C) { +func TestNewV2FaultyRand(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{}, } u1, err := g.NewV2(DomainPerson) - c.Assert(err, NotNil) - c.Assert(u1, Equals, Nil) + require.Error(t, err) + assert.Equal(t, Nil, u1) } -func (s *genTestSuite) BenchmarkNewV2(c *C) { - for i := 0; i < c.N; i++ { - NewV2(DomainPerson) +func BenchmarkNewV2(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = NewV2(DomainPerson) } } -func (s *genTestSuite) TestNewV3(c *C) { +func TestNewV3(t *testing.T) { u1 := NewV3(NamespaceDNS, "www.example.com") - c.Assert(u1.Version(), Equals, V3) - c.Assert(u1.Variant(), Equals, VariantRFC4122) - c.Assert(u1.String(), Equals, "5df41881-3aed-3515-88a7-2f4a814cf09e") + assert.Equal(t, V3, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) + assert.Equal(t, "5df41881-3aed-3515-88a7-2f4a814cf09e", u1.String()) u2 := NewV3(NamespaceDNS, "example.com") - c.Assert(u2, Not(Equals), u1) + assert.NotEqual(t, u1, u2) u3 := NewV3(NamespaceDNS, "example.com") - c.Assert(u3, Equals, u2) + assert.Equal(t, u2, u3) u4 := NewV3(NamespaceURL, "example.com") - c.Assert(u4, Not(Equals), u3) + assert.NotEqual(t, u3, u4) } -func (s *genTestSuite) BenchmarkNewV3(c *C) { - for i := 0; i < c.N; i++ { +func BenchmarkNewV3(b *testing.B) { + for i := 0; i < b.N; i++ { NewV3(NamespaceDNS, "www.example.com") } } -func (s *genTestSuite) TestNewV4(c *C) { +func TestNewV4(t *testing.T) { u1, err := NewV4() - c.Assert(err, IsNil) - c.Assert(u1.Version(), Equals, V4) - c.Assert(u1.Variant(), Equals, VariantRFC4122) + require.NoError(t, err) + assert.Equal(t, V4, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) u2, err := NewV4() - c.Assert(err, IsNil) - c.Assert(u1, Not(Equals), u2) + require.NoError(t, err) + assert.NotEqual(t, u1, u2) } -func (s *genTestSuite) TestNewV4FaultyRand(c *C) { +func TestNewV4FaultyRand(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: &faultyReader{}, } u1, err := g.NewV4() - c.Assert(err, NotNil) - c.Assert(u1, Equals, Nil) + require.Error(t, err) + assert.Equal(t, Nil, u1) } -func (s *genTestSuite) TestNewV4PartialRead(c *C) { +func TestNewV4PartialRead(t *testing.T) { g := &rfc4122Generator{ epochFunc: time.Now, hwAddrFunc: defaultHWAddrFunc, rand: iotest.OneByteReader(rand.Reader), } u1, err := g.NewV4() + require.NoError(t, err) + zeros := bytes.Count(u1.Bytes(), []byte{0}) mostlyZeros := zeros >= 10 - c.Assert(err, IsNil) - c.Assert(mostlyZeros, Equals, false) + assert.False(t, mostlyZeros, "Generated UUID contains mostly zeros") } -func (s *genTestSuite) BenchmarkNewV4(c *C) { - for i := 0; i < c.N; i++ { - NewV4() +func BenchmarkNewV4(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = NewV4() } } -func (s *genTestSuite) TestNewV5(c *C) { +func TestNewV5(t *testing.T) { u1 := NewV5(NamespaceDNS, "www.example.com") - c.Assert(u1.Version(), Equals, V5) - c.Assert(u1.Variant(), Equals, VariantRFC4122) - c.Assert(u1.String(), Equals, "2ed6657d-e927-568b-95e1-2665a8aea6a2") + assert.Equal(t, V5, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) + assert.Equal(t, "2ed6657d-e927-568b-95e1-2665a8aea6a2", u1.String()) u2 := NewV5(NamespaceDNS, "example.com") - c.Assert(u2, Not(Equals), u1) + assert.NotEqual(t, u1, u2) u3 := NewV5(NamespaceDNS, "example.com") - c.Assert(u3, Equals, u2) + assert.Equal(t, u2, u3) u4 := NewV5(NamespaceURL, "example.com") - c.Assert(u4, Not(Equals), u3) + assert.NotEqual(t, u3, u4) } -func (s *genTestSuite) BenchmarkNewV5(c *C) { - for i := 0; i < c.N; i++ { +func BenchmarkNewV5(b *testing.B) { + for i := 0; i < b.N; i++ { NewV5(NamespaceDNS, "www.example.com") } } + +func TestNewV6(t *testing.T) { + u1, err := NewV6() + require.NoError(t, err) + assert.Equal(t, V6, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) + + u2, err := NewV6() + require.NoError(t, err) + assert.NotEqual(t, u1, u2) + assert.True(t, bytes.Compare(u1[:6], u2[:6]) < 0 || bytes.Equal(u1[:6], u2[:6])) +} + +func BenchmarkNewV6(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = NewV6() + } +} + +func TestNewV7(t *testing.T) { + u1, err := NewV7() + require.NoError(t, err) + assert.Equal(t, V7, u1.Version()) + assert.Equal(t, VariantRFC4122, u1.Variant()) + + u2, err := NewV7() + require.NoError(t, err) + assert.NotEqual(t, u1, u2) + + assert.True(t, bytes.Compare(u1[:6], u2[:6]) < 0 || bytes.Equal(u1[:6], u2[:6])) + assert.NotEqual(t, u1[6:], u2[6:]) +} + +func BenchmarkNewV7(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = NewV7() + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9ebcd0c --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/satori/go.uuid + +go 1.22.6 + +require ( + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.2.1 // indirect + github.com/kr/text v0.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..664eeb1 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sql.go b/sql.go index 56759d3..5013278 100644 --- a/sql.go +++ b/sql.go @@ -27,6 +27,7 @@ import ( ) // Value implements the driver.Valuer interface. +// It converts the UUID to its string representation. func (u UUID) Value() (driver.Value, error) { return u.String(), nil } @@ -44,9 +45,10 @@ func (u *UUID) Scan(src interface{}) error { case string: return u.UnmarshalText([]byte(src)) - } - return fmt.Errorf("uuid: cannot convert %T to UUID", src) + default: + return fmt.Errorf("uuid: cannot convert %T to UUID", src) + } } // NullUUID can be used with the standard sql package to represent a @@ -57,11 +59,11 @@ type NullUUID struct { } // Value implements the driver.Valuer interface. +// It returns the UUID string value if valid, otherwise it returns nil. func (u NullUUID) Value() (driver.Value, error) { if !u.Valid { return nil, nil } - // Delegate to UUID Value function return u.UUID.Value() } @@ -72,7 +74,6 @@ func (u *NullUUID) Scan(src interface{}) error { return nil } - // Delegate to UUID Scan function u.Valid = true return u.UUID.Scan(src) } diff --git a/sql_test.go b/sql_test.go index 74255f5..fd443b7 100644 --- a/sql_test.go +++ b/sql_test.go @@ -22,115 +22,111 @@ package uuid import ( - . "gopkg.in/check.v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" ) -type sqlTestSuite struct{} - -var _ = Suite(&sqlTestSuite{}) - -func (s *sqlTestSuite) TestValue(c *C) { +func TestValue(t *testing.T) { u, err := FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") - c.Assert(err, IsNil) + require.NoError(t, err) val, err := u.Value() - c.Assert(err, IsNil) - c.Assert(val, Equals, u.String()) + require.NoError(t, err) + assert.Equal(t, u.String(), val) } -func (s *sqlTestSuite) TestValueNil(c *C) { +func TestValueNil(t *testing.T) { u := UUID{} val, err := u.Value() - c.Assert(err, IsNil) - c.Assert(val, Equals, Nil.String()) + require.NoError(t, err) + assert.Equal(t, Nil.String(), val) } -func (s *sqlTestSuite) TestNullUUIDValueNil(c *C) { +func TestNullUUIDValueNil(t *testing.T) { u := NullUUID{} val, err := u.Value() - c.Assert(err, IsNil) - c.Assert(val, IsNil) + require.NoError(t, err) + assert.Nil(t, val) } -func (s *sqlTestSuite) TestScanBinary(c *C) { +func TestScanBinary(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} u1 := UUID{} err := u1.Scan(b1) - c.Assert(err, IsNil) - c.Assert(u, Equals, u1) + require.NoError(t, err) + assert.Equal(t, u, u1) - b2 := []byte{} + var b2 []byte u2 := UUID{} - err = u2.Scan(b2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *sqlTestSuite) TestScanString(c *C) { +func TestScanString(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" u1 := UUID{} err := u1.Scan(s1) - c.Assert(err, IsNil) - c.Assert(u, Equals, u1) + require.NoError(t, err) + assert.Equal(t, u, u1) s2 := "" u2 := UUID{} - err = u2.Scan(s2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *sqlTestSuite) TestScanText(c *C) { +func TestScanText(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} b1 := []byte("6ba7b810-9dad-11d1-80b4-00c04fd430c8") u1 := UUID{} err := u1.Scan(b1) - c.Assert(err, IsNil) - c.Assert(u, Equals, u1) + require.NoError(t, err) + assert.Equal(t, u, u1) b2 := []byte("") u2 := UUID{} err = u2.Scan(b2) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *sqlTestSuite) TestScanUnsupported(c *C) { +func TestScanUnsupported(t *testing.T) { u := UUID{} err := u.Scan(true) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *sqlTestSuite) TestScanNil(c *C) { +func TestScanNil(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} err := u.Scan(nil) - c.Assert(err, NotNil) + assert.Error(t, err) } -func (s *sqlTestSuite) TestNullUUIDScanValid(c *C) { +func TestNullUUIDScanValid(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} s1 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8" u1 := NullUUID{} err := u1.Scan(s1) - c.Assert(err, IsNil) - c.Assert(u1.Valid, Equals, true) - c.Assert(u1.UUID, Equals, u) + require.NoError(t, err) + assert.True(t, u1.Valid) + assert.Equal(t, u, u1.UUID) } -func (s *sqlTestSuite) TestNullUUIDScanNil(c *C) { +func TestNullUUIDScanNil(t *testing.T) { u := NullUUID{UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}, true} err := u.Scan(nil) - c.Assert(err, IsNil) - c.Assert(u.Valid, Equals, false) - c.Assert(u.UUID, Equals, Nil) + require.NoError(t, err) + assert.False(t, u.Valid) + assert.Equal(t, Nil, u.UUID) } diff --git a/uuid.go b/uuid.go index a2b8e2c..32bc1cb 100644 --- a/uuid.go +++ b/uuid.go @@ -44,6 +44,8 @@ const ( V3 V4 V5 + V6 + V7 ) // UUID layout variants. @@ -152,6 +154,7 @@ func (u *UUID) SetVariant(v byte) { // Must is a helper that wraps a call to a function returning (UUID, error) // and panics if the error is non-nil. It is intended for use in variable // initializations such as +// // var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); func Must(u UUID, err error) UUID { if err != nil { diff --git a/uuid_test.go b/uuid_test.go index fa40280..649820f 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -24,77 +24,70 @@ package uuid import ( "bytes" "fmt" + "github.com/stretchr/testify/assert" "testing" - - . "gopkg.in/check.v1" ) -// Hook up gocheck into the "go test" runner. -func TestUUID(t *testing.T) { TestingT(t) } - -type testSuite struct{} - -var _ = Suite(&testSuite{}) - -func (s *testSuite) TestBytes(c *C) { +func TestBytes(t *testing.T) { u := UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} + expectedBytes := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} - bytes1 := []byte{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8} - - c.Assert(bytes.Equal(u.Bytes(), bytes1), Equals, true) + assert.True(t, bytes.Equal(u.Bytes(), expectedBytes)) } -func (s *testSuite) TestString(c *C) { - c.Assert(NamespaceDNS.String(), Equals, "6ba7b810-9dad-11d1-80b4-00c04fd430c8") +func TestString(t *testing.T) { + assert.Equal(t, "6ba7b810-9dad-11d1-80b4-00c04fd430c8", NamespaceDNS.String()) } -func (s *testSuite) TestEqual(c *C) { - c.Assert(Equal(NamespaceDNS, NamespaceDNS), Equals, true) - c.Assert(Equal(NamespaceDNS, NamespaceURL), Equals, false) +func TestEqual(t *testing.T) { + assert.True(t, Equal(NamespaceDNS, NamespaceDNS)) + assert.False(t, Equal(NamespaceDNS, NamespaceURL)) } -func (s *testSuite) TestVersion(c *C) { +func TestVersion(t *testing.T) { u := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - c.Assert(u.Version(), Equals, V1) + assert.Equal(t, V1, u.Version()) } -func (s *testSuite) TestSetVersion(c *C) { +func TestSetVersion(t *testing.T) { u := UUID{} u.SetVersion(4) - c.Assert(u.Version(), Equals, V4) + assert.Equal(t, V4, u.Version()) } -func (s *testSuite) TestVariant(c *C) { +func TestVariant(t *testing.T) { u1 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - c.Assert(u1.Variant(), Equals, VariantNCS) + assert.Equal(t, VariantNCS, u1.Variant()) u2 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - c.Assert(u2.Variant(), Equals, VariantRFC4122) + assert.Equal(t, VariantRFC4122, u2.Variant()) u3 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - c.Assert(u3.Variant(), Equals, VariantMicrosoft) + assert.Equal(t, VariantMicrosoft, u3.Variant()) u4 := UUID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} - c.Assert(u4.Variant(), Equals, VariantFuture) + assert.Equal(t, VariantFuture, u4.Variant()) } -func (s *testSuite) TestSetVariant(c *C) { +func TestSetVariant(t *testing.T) { u := UUID{} u.SetVariant(VariantNCS) - c.Assert(u.Variant(), Equals, VariantNCS) + assert.Equal(t, VariantNCS, u.Variant()) + u.SetVariant(VariantRFC4122) - c.Assert(u.Variant(), Equals, VariantRFC4122) + assert.Equal(t, VariantRFC4122, u.Variant()) + u.SetVariant(VariantMicrosoft) - c.Assert(u.Variant(), Equals, VariantMicrosoft) + assert.Equal(t, VariantMicrosoft, u.Variant()) + u.SetVariant(VariantFuture) - c.Assert(u.Variant(), Equals, VariantFuture) + assert.Equal(t, VariantFuture, u.Variant()) } -func (s *testSuite) TestMust(c *C) { - defer func() { - c.Assert(recover(), NotNil) - }() - Must(func() (UUID, error) { - return Nil, fmt.Errorf("uuid: expected error") - }()) +func TestMust(t *testing.T) { + assert.Panics(t, func() { + Must(func() (UUID, error) { + return Nil, fmt.Errorf("uuid: expected error") + }()) + }) }