Skip to content

Commit ee5c233

Browse files
authored
Merge pull request #34 from multiformats/feat/yet-more-tests
Even more tests/benchmarks, less repetition in-code
2 parents c03399a + aa5d547 commit ee5c233

File tree

2 files changed

+142
-62
lines changed

2 files changed

+142
-62
lines changed

multibase.go

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,8 @@ const (
3737
Base64urlPad = 'U'
3838
)
3939

40-
// Encodings is a map of the supported encoding, unsupported encoding
40+
// EncodingToStr is a map of the supported encoding, unsupported encoding
4141
// specified in standard are left out
42-
var Encodings = map[string]Encoding{
43-
"identity": 0x00,
44-
"base2": '0',
45-
"base16": 'f',
46-
"base16upper": 'F',
47-
"base32": 'b',
48-
"base32upper": 'B',
49-
"base32pad": 'c',
50-
"base32padupper": 'C',
51-
"base32hex": 'v',
52-
"base32hexupper": 'V',
53-
"base32hexpad": 't',
54-
"base32hexpadupper": 'T',
55-
"base58flickr": 'Z',
56-
"base58btc": 'z',
57-
"base64": 'm',
58-
"base64url": 'u',
59-
"base64pad": 'M',
60-
"base64urlpad": 'U',
61-
}
62-
6342
var EncodingToStr = map[Encoding]string{
6443
0x00: "identity",
6544
'0': "base2",
@@ -73,14 +52,22 @@ var EncodingToStr = map[Encoding]string{
7352
'V': "base32hexupper",
7453
't': "base32hexpad",
7554
'T': "base32hexpadupper",
76-
'Z': "base58flickr",
7755
'z': "base58btc",
56+
'Z': "base58flickr",
7857
'm': "base64",
7958
'u': "base64url",
8059
'M': "base64pad",
8160
'U': "base64urlpad",
8261
}
8362

63+
var Encodings = map[string]Encoding{}
64+
65+
func init() {
66+
for e, n := range EncodingToStr {
67+
Encodings[n] = e
68+
}
69+
}
70+
8471
// ErrUnsupportedEncoding is returned when the selected encoding is not known or
8572
// implemented.
8673
var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported")

multibase_test.go

Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package multibase
33
import (
44
"bytes"
55
"math/rand"
6+
"sort"
67
"testing"
78
)
89

@@ -36,6 +37,7 @@ var encodedSamples = map[Encoding]string{
3637
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
3738
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
3839
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
40+
Base58Flickr: "Z36tpRGiQ9Endr7dHahm9xwQdhmoER4emaRVT",
3941
Base64: "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
4042
Base64url: "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
4143
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
@@ -49,7 +51,7 @@ func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string)
4951
return
5052
}
5153
if actual != expected {
52-
t.Errorf("encoding failed for %c (%d), expected: %s, got: %s", encoding, encoding, expected, actual)
54+
t.Errorf("encoding failed for %c (%d / %s), expected: %s, got: %s", encoding, encoding, EncodingToStr[encoding], expected, actual)
5355
}
5456
}
5557

@@ -68,25 +70,53 @@ func testDecode(t *testing.T, expectedEncoding Encoding, expectedBytes []byte, d
6870
}
6971

7072
func TestEncode(t *testing.T) {
71-
for encoding, data := range encodedSamples {
72-
testEncode(t, encoding, sampleBytes, data)
73+
for encoding := range EncodingToStr {
74+
testEncode(t, encoding, sampleBytes, encodedSamples[encoding])
7375
}
7476
}
7577

7678
func TestDecode(t *testing.T) {
77-
for encoding, data := range encodedSamples {
78-
testDecode(t, encoding, sampleBytes, data)
79+
for encoding := range EncodingToStr {
80+
testDecode(t, encoding, sampleBytes, encodedSamples[encoding])
7981
}
8082
}
8183

8284
func TestRoundTrip(t *testing.T) {
83-
buf := make([]byte, 37+16) // sufficiently large prime number of bytes (37) + another 16 to test leading 0s
85+
86+
for base := range EncodingToStr {
87+
if int(base) == 0 {
88+
// skip identity: any byte goes there
89+
continue
90+
}
91+
92+
_, _, err := Decode(string(base) + "\u00A0")
93+
if err == nil {
94+
t.Fatal(EncodingToStr[base] + " decode should fail on low-unicode")
95+
}
96+
97+
_, _, err = Decode(string(base) + "\u1F4A8")
98+
if err == nil {
99+
t.Fatal(EncodingToStr[base] + " decode should fail on emoji")
100+
}
101+
102+
_, _, err = Decode(string(base) + "!")
103+
if err == nil {
104+
t.Fatal(EncodingToStr[base] + " decode should fail on punctuation")
105+
}
106+
107+
_, _, err = Decode(string(base) + "\xA0")
108+
if err == nil {
109+
t.Fatal(EncodingToStr[base] + " decode should fail on high-latin1")
110+
}
111+
}
112+
113+
buf := make([]byte, 137+16) // sufficiently large prime number of bytes + another 16 to test leading 0s
84114
rand.Read(buf[16:])
85115

86116
for base := range EncodingToStr {
87117

88118
// test roundtrip from the full zero-prefixed buffer down to a single byte
89-
for i := 0; i <= len(buf)-1; i++ {
119+
for i := 0; i < len(buf); i++ {
90120

91121
// use a copy to verify we are not overwriting the supplied buffer
92122
newBuf := make([]byte, len(buf)-i)
@@ -114,14 +144,17 @@ func TestRoundTrip(t *testing.T) {
114144
t.Fatal("input wasnt the same as output", buf[i:], out)
115145
}
116146

117-
// When we have 3 leading zeroes, and this is a case-insensitive codec
118-
// semi-randomly swap case in enc and try again
147+
// When we have 3 leading zeroes, do a few extra tests
148+
// ( choice of leading zeroes is arbitrary - just cutting down on test permutations )
149+
119150
if i == 13 {
151+
152+
// if this is a case-insensitive codec semi-randomly swap case in enc and try again
120153
name := EncodingToStr[base]
121154
if name[len(name)-5:] == "upper" || Encodings[name+"upper"] > 0 {
122155
caseTamperedEnc := []byte(enc)
123156

124-
for _, j := range []int{3, 5, 8, 13, 21, 23, 29} {
157+
for _, j := range []int{3, 5, 8, 13, 21, 23, 29, 47, 52} {
125158
if caseTamperedEnc[j] >= 65 && caseTamperedEnc[j] <= 90 {
126159
caseTamperedEnc[j] += 32
127160
} else if caseTamperedEnc[j] >= 97 && caseTamperedEnc[j] <= 122 {
@@ -138,49 +171,77 @@ func TestRoundTrip(t *testing.T) {
138171
t.Fatal("got wrong encoding out")
139172
}
140173
if !bytes.Equal(buf[i:], out) {
141-
t.Fatal("input wasnt the same as output", buf[i:], out)
174+
t.Fatal("input wasn't the same as output", buf[i:], out)
142175
}
143176
}
144177
}
145178
}
146179
}
147180

181+
// Test that nothing overflows
182+
maxValueBuf := make([]byte, 131)
183+
for i := 0; i < len(maxValueBuf); i++ {
184+
maxValueBuf[i] = 0xFF
185+
}
186+
187+
for base := range EncodingToStr {
188+
189+
// test roundtrip from the complete buffer down to a single byte
190+
for i := 0; i < len(maxValueBuf); i++ {
191+
192+
enc, err := Encode(base, maxValueBuf[i:])
193+
if err != nil {
194+
t.Fatal(err)
195+
}
196+
197+
e, out, err := Decode(enc)
198+
if err != nil {
199+
t.Fatal(err)
200+
}
201+
202+
if e != base {
203+
t.Fatal("got wrong encoding out")
204+
}
205+
206+
if !bytes.Equal(maxValueBuf[i:], out) {
207+
t.Fatal("input wasn't the same as output", maxValueBuf[i:], out)
208+
}
209+
}
210+
}
211+
148212
_, _, err := Decode("")
149213
if err == nil {
150-
t.Fatal("shouldnt be able to decode empty string")
214+
t.Fatal("shouldn't be able to decode empty string")
151215
}
152216
}
153217

218+
var benchmarkBuf [36]byte // typical CID size
219+
var benchmarkCodecs []string
220+
221+
func init() {
222+
rand.Read(benchmarkBuf[:])
223+
224+
benchmarkCodecs = make([]string, 0, len(Encodings))
225+
for n := range Encodings {
226+
227+
// // Only bench b36 and b58
228+
// if len(n) < 6 || (n[4:6] != "36" && n[4:6] != "58") {
229+
// continue
230+
// }
231+
232+
benchmarkCodecs = append(benchmarkCodecs, n)
233+
}
234+
sort.Strings(benchmarkCodecs)
235+
}
236+
154237
func BenchmarkRoundTrip(b *testing.B) {
155-
buf := make([]byte, 32)
156-
rand.Read(buf)
157238
b.ResetTimer()
158239

159-
bases := map[string]Encoding{
160-
"Identity": Identity,
161-
"Base2": Base2,
162-
"Base16": Base16,
163-
"Base16Upper": Base16Upper,
164-
"Base32": Base32,
165-
"Base32Upper": Base32Upper,
166-
"Base32pad": Base32pad,
167-
"Base32padUpper": Base32padUpper,
168-
"Base32hex": Base32hex,
169-
"Base32hexUpper": Base32hexUpper,
170-
"Base32hexPad": Base32hexPad,
171-
"Base32hexPadUpper": Base32hexPadUpper,
172-
"Base58Flickr": Base58Flickr,
173-
"Base58BTC": Base58BTC,
174-
"Base64": Base64,
175-
"Base64url": Base64url,
176-
"Base64pad": Base64pad,
177-
"Base64urlPad": Base64urlPad,
178-
}
179-
180-
for name, base := range bases {
240+
for _, name := range benchmarkCodecs {
181241
b.Run(name, func(b *testing.B) {
242+
base := Encodings[name]
182243
for i := 0; i < b.N; i++ {
183-
enc, err := Encode(base, buf)
244+
enc, err := Encode(base, benchmarkBuf[:])
184245
if err != nil {
185246
b.Fatal(err)
186247
}
@@ -194,8 +255,40 @@ func BenchmarkRoundTrip(b *testing.B) {
194255
b.Fatal("got wrong encoding out")
195256
}
196257

197-
if !bytes.Equal(buf, out) {
198-
b.Fatal("input wasnt the same as output", buf, out)
258+
if !bytes.Equal(benchmarkBuf[:], out) {
259+
b.Fatal("input wasnt the same as output", benchmarkBuf, out)
260+
}
261+
}
262+
})
263+
}
264+
}
265+
266+
func BenchmarkEncode(b *testing.B) {
267+
b.ResetTimer()
268+
269+
for _, name := range benchmarkCodecs {
270+
b.Run(name, func(b *testing.B) {
271+
base := Encodings[name]
272+
for i := 0; i < b.N; i++ {
273+
_, err := Encode(base, benchmarkBuf[:])
274+
if err != nil {
275+
b.Fatal(err)
276+
}
277+
}
278+
})
279+
}
280+
}
281+
282+
func BenchmarkDecode(b *testing.B) {
283+
b.ResetTimer()
284+
285+
for _, name := range benchmarkCodecs {
286+
b.Run(name, func(b *testing.B) {
287+
enc, _ := Encode(Encodings[name], benchmarkBuf[:])
288+
for i := 0; i < b.N; i++ {
289+
_, _, err := Decode(enc)
290+
if err != nil {
291+
b.Fatal(err)
199292
}
200293
}
201294
})

0 commit comments

Comments
 (0)