Skip to content

Commit

Permalink
Even more tests/benchmarks, less repetition in-code
Browse files Browse the repository at this point in the history
  • Loading branch information
ribasushi committed May 23, 2020
1 parent c03399a commit aa5d547
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 62 deletions.
33 changes: 10 additions & 23 deletions multibase.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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")
Expand Down
171 changes: 132 additions & 39 deletions multibase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package multibase
import (
"bytes"
"math/rand"
"sort"
"testing"
)

Expand Down Expand Up @@ -36,6 +37,7 @@ var encodedSamples = map[Encoding]string{
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
Base58Flickr: "Z36tpRGiQ9Endr7dHahm9xwQdhmoER4emaRVT",
Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
Expand All @@ -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)
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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)
}
}
})
Expand Down

0 comments on commit aa5d547

Please sign in to comment.