package uuid import ( "bytes" "encoding/hex" "fmt" "io" "os" "reflect" "strings" ) func showColour() bool { return os.Getenv("NO_COLOUR") == "" } func testStart(name string) { fmt.Fprintf(os.Stderr, "%s:\n", name) } func testing(message string, body func()) { if showColour() { fmt.Fprintf( os.Stderr, "\033[0;33mtesting\033[0m: %s... ", message, ) body() fmt.Fprintf(os.Stderr, "\033[0;32mOK\033[0m.\n") } else { fmt.Fprintf(os.Stderr, "testing: %s... ", message) body() fmt.Fprintf(os.Stderr, "OK.\n") } } func assertEq(given any, expected any) { if !reflect.DeepEqual(given, expected) { if showColour() { fmt.Fprintf(os.Stderr, "\033[0;31mERR\033[0m.\n") } else { fmt.Fprintf(os.Stderr, "ERR.\n") } fmt.Fprintf(os.Stderr, "given != expected\n") fmt.Fprintf(os.Stderr, "given: %#v\n", given) fmt.Fprintf(os.Stderr, "expected: %#v\n", expected) os.Exit(1) } } func test_NewV4From() { testStart("NewV4From()") testing("we propagate the error when it happens", func() { _, err := NewV4From(strings.NewReader("")) assertEq(err, io.EOF) }) testing("we get the same UUID from the same input", func() { const s = "abcdefghijklmnop" r1 := strings.NewReader(s) uuid1, err := NewV4From(r1) assertEq(err, nil) r2 := strings.NewReader(s) uuid2, err := NewV4From(r2) assertEq(err, nil) assertEq(uuid1, uuid2) }) testing("we the bytes in UUID are what the reader gives us", func() { input := []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, } expected := UUID{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 + 0x40, 0x07, 0x08 + 0x80, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, } r := bytes.NewReader(input) given, err := NewV4From(r) assertEq(err, nil) assertEq(given, expected) }) testing("v4 and variant markers", func() { input := []byte{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x71, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } expected := UUID{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x41, // not 0x11 0x11, 0xb1, // not 0x11 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } r := bytes.NewReader(input) given, err := NewV4From(r) assertEq(err, nil) assertEq(given, expected) }) } func test_NewV4() { testStart("NewV4()") testing("we can generate UUID values: ", func() { var uuid UUID = NewV4() assertEq(uuid == Nil, false) assertEq(uuid == Max, false) }) testing("panic when the randomReader fails", func() { savedReader := randomReader randomReader = strings.NewReader("") defer func() { r := recover() assertEq(r, io.EOF) randomReader = savedReader }() NewV4() os.Exit(5) }) } func test_NewV7From() { testStart("NewV7From()") testing("reader error is propagated", func() { nowFn := func() uint64 { return 0 } _, err := NewV7From(strings.NewReader(""), nowFn) assertEq(err, io.EOF) }) testing("we get the same UUID given the same input", func() { const s = "abcdefgh" nowFn := func() uint64 { return 0 } r1 := strings.NewReader(s) uuid1, err := NewV7From(r1, nowFn) assertEq(err, nil) r2 := strings.NewReader(s) uuid2, err := NewV7From(r2, nowFn) assertEq(err, nil) assertEq(uuid1, uuid2) }) testing("the bytes are what the reader plus the time gives", func() { randomInput := []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, } nowFn := func() uint64 { return 0xffffffff00000000 } expected := UUID{ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00 + 0x70, 0x00, 0x00 + 0x80, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, } r := bytes.NewReader(randomInput) given, err := NewV7From(r, nowFn) assertEq(err, nil) assertEq(given, expected) }) testing("v7 and variant markers", func() { randomInput := []byte{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } nowFn := func() uint64 { return 0x1111111111111111 } expected := UUID{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x71, // not 0x11 0x11, 0x91, // not 0x11 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } r := bytes.NewReader(randomInput) given, err := NewV7From(r, nowFn) assertEq(err, nil) assertEq(given, expected) }) } func test_NewV7() { testStart("NewV7()") testing("we can generate UUID values: ", func() { var uuid UUID = NewV7() assertEq(uuid == Nil, false) assertEq(uuid == Max, false) }) testing("panic when reader fails", func() { savedReader := randomReader randomReader = strings.NewReader("") defer func() { r := recover() assertEq(r, io.EOF) randomReader = savedReader }() NewV7() os.Exit(5) }) } func test_String() { testStart("UUID.String()") testing("simple example values", func() { uuids := []UUID { UUID { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, UUID { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, }, UUID { 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, 222, }, UUID { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }, } strs := []string { "00000000-0000-0000-0000-000000000000", "00010203-0405-0607-0809-0a0b0c0d0e0f", "dededede-dede-dede-dede-dededededede", "ffffffff-ffff-ffff-ffff-ffffffffffff", } for i := range uuids { s1 := uuids[i].String() s2 := fmt.Sprintf("%v", uuids[i]) assertEq(s1, strs[i]) assertEq(s2, strs[i]) } }) } func test_FromString() { testStart("FromString()") testing("UUID -> string -> UUID round trip", func() { for i := 0; i < 100; i++ { uuid0 := NewV4() uuid1, err := FromString(uuid0.String()) assertEq(err, nil) assertEq(uuid0, uuid1) } }) testing("errors we detect", func() { var err error _, err = FromString("") assertEq(err, ErrBadLength) _, err = FromString("---000000000000000000000000000000000") assertEq(err, ErrBadDashCount) _, err = FromString("----00000000000000000000000000000000") assertEq(err, ErrBadDashPosition) _, err = FromString("00000000-0000-0000-0000-00000000000g") assertEq(err, hex.InvalidByteError('g')) _, err = FromString("00000000-0000-0000-0000-000000000000") assertEq(err, nil) }) } func test_usage() { testStart("usage()") testing("all it does is write to the given io.Writer", func() { w := strings.Builder{} usage("xxx", &w) const expectedRaw = ` Usage: xxx [-v (4|7)] xxx STRING ` expected := strings.Trim( strings.ReplaceAll(expectedRaw, "\t", ""), "\n", ) + "\n" assertEq(w.String(), expected) }) } func test_actionForSubargs() { testStart("actionForSubargs()") testing("we decide based only on length", func() { assertEq(actionForSubargs([]string{}), actionType_generate) assertEq(actionForSubargs([]string{""}), actionType_parse) assertEq(actionForSubargs([]string{"id"}), actionType_parse) }) } func test_getopt() { testStart("getopt()") const usageRaw = ` Usage: $0 [-v (4|7)] $0 STRING ` usage := strings.Trim( strings.ReplaceAll(usageRaw, "\t", ""), "\n", ) + "\n" testing("we supress the default error message", func() { w := strings.Builder{} argv := []string{"$0", "-h"} _, rc := getopt(argv, &w) assertEq(w.String(), usage) assertEq(rc, 2) }) testing("we get unsupported flag error", func() { w := strings.Builder{} argv := []string{"$0", "-Z"} _, rc := getopt(argv, &w) const message = "flag provided but not defined: -Z\n" assertEq(w.String(), message + usage) assertEq(rc, 2) }) testing("we get incorrect use of flag error", func() { w := strings.Builder{} argv := []string{"$0", "-v"} _, rc := getopt(argv, &w) const message = "flag needs an argument: -v\n" assertEq(w.String(), message + usage) assertEq(rc, 2) }) testing("we get bad flag value error", func() { w := strings.Builder{} argv := []string{"$0", "-v", "a"} _, rc := getopt(argv, &w) const message = "invalid value \"a\" for flag -v: parse error\n" assertEq(w.String(), message + usage) assertEq(rc, 2) }) testing("the args has the picked version", func() { var ( w1 = strings.Builder{} w2 = strings.Builder{} w3 = strings.Builder{} ) argsIn1 := []string{"$0"} argsIn2 := []string{"$0", "-v", "4"} argsIn3 := []string{"$0", "-v", "7"} args1, rc1 := getopt(argsIn1, &w1) args2, rc2 := getopt(argsIn2, &w2) args3, rc3 := getopt(argsIn3, &w3) expected1 := argsT{ allArgs: []string{"$0"}, subArgs: []string{}, action: actionType_generate, version: 4, } expected2 := argsT{ allArgs: []string{"$0", "-v", "4"}, subArgs: []string{}, action: actionType_generate, version: 4, } expected3 := argsT{ allArgs: []string{"$0", "-v", "7"}, subArgs: []string{}, action: actionType_generate, version: 7, } assertEq(w1.String(), "") assertEq(w2.String(), "") assertEq(w3.String(), "") assertEq(rc1, 0) assertEq(rc2, 0) assertEq(rc3, 0) assertEq(args1, expected1) assertEq(args2, expected2) assertEq(args3, expected3) }) testing("the args has the picked action", func() { var ( w1 = strings.Builder{} w2 = strings.Builder{} ) argsIn1 := []string{"$0", "the-string"} argsIn2 := []string{"$0", "-v", "7", "the-string"} args1, rc1 := getopt(argsIn1, &w1) args2, rc2 := getopt(argsIn2, &w2) expected1 := argsT{ allArgs: []string{"$0", "the-string"}, subArgs: []string{"the-string"}, action: actionType_parse, version: 4, } expected2 := argsT{ allArgs: []string{"$0", "-v", "7", "the-string"}, subArgs: []string{"the-string"}, action: actionType_parse, version: 7, } assertEq(w1.String(), "") assertEq(w2.String(), "") assertEq(rc1, 0) assertEq(rc2, 0) assertEq(args1, expected1) assertEq(args2, expected2) }) } func test_run() { testStart("run()") testing("generating IDs gives us 0 rc", func() { out1 := strings.Builder{} out2 := strings.Builder{} args1 := argsT{ action: actionType_generate, version: 4, } args2 := argsT{ action: actionType_generate, version: 7, } rc1 := run(args1, nil, &out1, nil) rc2 := run(args2, nil, &out2, nil) assertEq(rc1, 0) assertEq(rc2, 0) assertEq(len(out1.String()), encodedLength + len("\n")) assertEq(len(out2.String()), encodedLength + len("\n")) }) testing("parsing varies based on ID validity", func() { const id = "ggggggg-ggggg-gggg-gggg-gggggggggggg" err1 := strings.Builder{} args1 := argsT{ action: actionType_parse, subArgs: []string{ id }, } args2 := argsT{ action: actionType_parse, subArgs: []string{New().String()}, } rc1 := run(args1, nil, nil, &err1) rc2 := run(args2, nil, nil, nil) assertEq(rc1, 3) assertEq(rc2, 0) assertEq(err1.String(), "uuid: Bad char in string\n") }) } func MainTest() { test_NewV4From() test_NewV4() test_NewV7From() test_NewV7() test_String() test_FromString() test_usage() test_actionForSubargs() test_getopt() test_run() }