From aa5d547a81d790462411d1d4ac2c55f384f6ece4 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Fri, 22 May 2020 03:43:29 +0200 Subject: [PATCH] Even more tests/benchmarks, less repetition in-code --- multibase.go | 33 +++------ multibase_test.go | 171 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 142 insertions(+), 62 deletions(-) diff --git a/multibase.go b/multibase.go index a60403d..c1833b5 100644 --- a/multibase.go +++ b/multibase.go @@ -37,29 +37,8 @@ const ( Base64urlPad = 'U' ) -// Encodings is a map of the supported encoding, unsupported encoding +// EncodingToStr is a map of the supported encoding, unsupported encoding // specified in standard are left out -var Encodings = map[string]Encoding{ - "identity": 0x00, - "base2": '0', - "base16": 'f', - "base16upper": 'F', - "base32": 'b', - "base32upper": 'B', - "base32pad": 'c', - "base32padupper": 'C', - "base32hex": 'v', - "base32hexupper": 'V', - "base32hexpad": 't', - "base32hexpadupper": 'T', - "base58flickr": 'Z', - "base58btc": 'z', - "base64": 'm', - "base64url": 'u', - "base64pad": 'M', - "base64urlpad": 'U', -} - var EncodingToStr = map[Encoding]string{ 0x00: "identity", '0': "base2", @@ -73,14 +52,22 @@ var EncodingToStr = map[Encoding]string{ 'V': "base32hexupper", 't': "base32hexpad", 'T': "base32hexpadupper", - 'Z': "base58flickr", 'z': "base58btc", + 'Z': "base58flickr", 'm': "base64", 'u': "base64url", 'M': "base64pad", 'U': "base64urlpad", } +var Encodings = map[string]Encoding{} + +func init() { + for e, n := range EncodingToStr { + Encodings[n] = e + } +} + // ErrUnsupportedEncoding is returned when the selected encoding is not known or // implemented. var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported") diff --git a/multibase_test.go b/multibase_test.go index 1713feb..4c75036 100644 --- a/multibase_test.go +++ b/multibase_test.go @@ -3,6 +3,7 @@ package multibase import ( "bytes" "math/rand" + "sort" "testing" ) @@ -36,6 +37,7 @@ var encodedSamples = map[Encoding]string{ Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======", Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======", Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt", + Base58Flickr: "Z36tpRGiQ9Endr7dHahm9xwQdhmoER4emaRVT", Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE", Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE", Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=", @@ -49,7 +51,7 @@ func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string) return } if actual != expected { - t.Errorf("encoding failed for %c (%d), expected: %s, got: %s", encoding, encoding, expected, actual) + t.Errorf("encoding failed for %c (%d / %s), expected: %s, got: %s", encoding, encoding, EncodingToStr[encoding], expected, actual) } } @@ -68,25 +70,53 @@ func testDecode(t *testing.T, expectedEncoding Encoding, expectedBytes []byte, d } func TestEncode(t *testing.T) { - for encoding, data := range encodedSamples { - testEncode(t, encoding, sampleBytes, data) + for encoding := range EncodingToStr { + testEncode(t, encoding, sampleBytes, encodedSamples[encoding]) } } func TestDecode(t *testing.T) { - for encoding, data := range encodedSamples { - testDecode(t, encoding, sampleBytes, data) + for encoding := range EncodingToStr { + testDecode(t, encoding, sampleBytes, encodedSamples[encoding]) } } func TestRoundTrip(t *testing.T) { - buf := make([]byte, 37+16) // sufficiently large prime number of bytes (37) + another 16 to test leading 0s + + for base := range EncodingToStr { + if int(base) == 0 { + // skip identity: any byte goes there + continue + } + + _, _, err := Decode(string(base) + "\u00A0") + if err == nil { + t.Fatal(EncodingToStr[base] + " decode should fail on low-unicode") + } + + _, _, err = Decode(string(base) + "\u1F4A8") + if err == nil { + t.Fatal(EncodingToStr[base] + " decode should fail on emoji") + } + + _, _, err = Decode(string(base) + "!") + if err == nil { + t.Fatal(EncodingToStr[base] + " decode should fail on punctuation") + } + + _, _, err = Decode(string(base) + "\xA0") + if err == nil { + t.Fatal(EncodingToStr[base] + " decode should fail on high-latin1") + } + } + + buf := make([]byte, 137+16) // sufficiently large prime number of bytes + another 16 to test leading 0s rand.Read(buf[16:]) for base := range EncodingToStr { // test roundtrip from the full zero-prefixed buffer down to a single byte - for i := 0; i <= len(buf)-1; i++ { + for i := 0; i < len(buf); i++ { // use a copy to verify we are not overwriting the supplied buffer newBuf := make([]byte, len(buf)-i) @@ -114,14 +144,17 @@ func TestRoundTrip(t *testing.T) { t.Fatal("input wasnt the same as output", buf[i:], out) } - // When we have 3 leading zeroes, and this is a case-insensitive codec - // semi-randomly swap case in enc and try again + // When we have 3 leading zeroes, do a few extra tests + // ( choice of leading zeroes is arbitrary - just cutting down on test permutations ) + if i == 13 { + + // if this is a case-insensitive codec semi-randomly swap case in enc and try again name := EncodingToStr[base] if name[len(name)-5:] == "upper" || Encodings[name+"upper"] > 0 { caseTamperedEnc := []byte(enc) - for _, j := range []int{3, 5, 8, 13, 21, 23, 29} { + for _, j := range []int{3, 5, 8, 13, 21, 23, 29, 47, 52} { if caseTamperedEnc[j] >= 65 && caseTamperedEnc[j] <= 90 { caseTamperedEnc[j] += 32 } else if caseTamperedEnc[j] >= 97 && caseTamperedEnc[j] <= 122 { @@ -138,49 +171,77 @@ func TestRoundTrip(t *testing.T) { t.Fatal("got wrong encoding out") } if !bytes.Equal(buf[i:], out) { - t.Fatal("input wasnt the same as output", buf[i:], out) + t.Fatal("input wasn't the same as output", buf[i:], out) } } } } } + // Test that nothing overflows + maxValueBuf := make([]byte, 131) + for i := 0; i < len(maxValueBuf); i++ { + maxValueBuf[i] = 0xFF + } + + for base := range EncodingToStr { + + // test roundtrip from the complete buffer down to a single byte + for i := 0; i < len(maxValueBuf); i++ { + + enc, err := Encode(base, maxValueBuf[i:]) + if err != nil { + t.Fatal(err) + } + + e, out, err := Decode(enc) + if err != nil { + t.Fatal(err) + } + + if e != base { + t.Fatal("got wrong encoding out") + } + + if !bytes.Equal(maxValueBuf[i:], out) { + t.Fatal("input wasn't the same as output", maxValueBuf[i:], out) + } + } + } + _, _, err := Decode("") if err == nil { - t.Fatal("shouldnt be able to decode empty string") + t.Fatal("shouldn't be able to decode empty string") } } +var benchmarkBuf [36]byte // typical CID size +var benchmarkCodecs []string + +func init() { + rand.Read(benchmarkBuf[:]) + + benchmarkCodecs = make([]string, 0, len(Encodings)) + for n := range Encodings { + + // // Only bench b36 and b58 + // if len(n) < 6 || (n[4:6] != "36" && n[4:6] != "58") { + // continue + // } + + benchmarkCodecs = append(benchmarkCodecs, n) + } + sort.Strings(benchmarkCodecs) +} + func BenchmarkRoundTrip(b *testing.B) { - buf := make([]byte, 32) - rand.Read(buf) b.ResetTimer() - bases := map[string]Encoding{ - "Identity": Identity, - "Base2": Base2, - "Base16": Base16, - "Base16Upper": Base16Upper, - "Base32": Base32, - "Base32Upper": Base32Upper, - "Base32pad": Base32pad, - "Base32padUpper": Base32padUpper, - "Base32hex": Base32hex, - "Base32hexUpper": Base32hexUpper, - "Base32hexPad": Base32hexPad, - "Base32hexPadUpper": Base32hexPadUpper, - "Base58Flickr": Base58Flickr, - "Base58BTC": Base58BTC, - "Base64": Base64, - "Base64url": Base64url, - "Base64pad": Base64pad, - "Base64urlPad": Base64urlPad, - } - - for name, base := range bases { + for _, name := range benchmarkCodecs { b.Run(name, func(b *testing.B) { + base := Encodings[name] for i := 0; i < b.N; i++ { - enc, err := Encode(base, buf) + enc, err := Encode(base, benchmarkBuf[:]) if err != nil { b.Fatal(err) } @@ -194,8 +255,40 @@ func BenchmarkRoundTrip(b *testing.B) { b.Fatal("got wrong encoding out") } - if !bytes.Equal(buf, out) { - b.Fatal("input wasnt the same as output", buf, out) + if !bytes.Equal(benchmarkBuf[:], out) { + b.Fatal("input wasnt the same as output", benchmarkBuf, out) + } + } + }) + } +} + +func BenchmarkEncode(b *testing.B) { + b.ResetTimer() + + for _, name := range benchmarkCodecs { + b.Run(name, func(b *testing.B) { + base := Encodings[name] + for i := 0; i < b.N; i++ { + _, err := Encode(base, benchmarkBuf[:]) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkDecode(b *testing.B) { + b.ResetTimer() + + for _, name := range benchmarkCodecs { + b.Run(name, func(b *testing.B) { + enc, _ := Encode(Encodings[name], benchmarkBuf[:]) + for i := 0; i < b.N; i++ { + _, _, err := Decode(enc) + if err != nil { + b.Fatal(err) } } })