Repository: exercism/xgo
Pull request #674 (2017-05-18 04:10:58)

variable-length-quantity: create test case generator
For #605

Generate two test case arrays, one for encode and
one for decode, from the canonical-data.json.

Update example solution to pass the tests.
The API changed to use slices in order to match
the canonical-data.json test cases.
Retain the old functions but rename to decodeInt and encodeInt
and utilize these as helpers to support the new slice based API.

Update test program to use generated test case arrays.
Put FAIL and PASS in test result output.

Increment testVersion and targetTestVersion in
example solution and test program.

leenipper: I pushed an update which has a simpler generator.
leenipper: Well, I did wonder about the 0-length array, whether it was clear enough.  Do you think it is worth adding an errorExpected bool for clarity in the test data ?  Will be fairly easy to do.
petertseng: I would add it - I'm looking at the example of https://github.com/exercism/xgo/blob/master/exercises/largest-series-product/cases_test.go where we could find out via `expected < 0` whether it's an error, but we still add an `ok` boolean anyway.

However, just to make sure I'm not falling to confirmation bias, I looked for a counterexample, and I found it in Forth: https://github.com/exercism/xgo/blob/master/exercises/forth/cases_test.go#L15 there is a comment there:

> // nil slice indicates error expected.

So it looks like there are two possible ways of making it clearer, so we have a choice.
leenipper: Ok. I chose to add the nil slice comment, and I updated test program to compare against nil as well.
petertseng: I just remembered something... forgot to check that `err` is an `error`? (#275)

Maybe we should only check it in the first few exercises that have `error` and not worry about it for the rest.

Patch:
diff --git a/README.md b/README.md
index 56dc175c..1603c520 100644
--- a/README.md
+++ b/README.md
@@ -138,13 +138,10 @@ are useful if they let the solver try a change and see a performance effect.
 
 ### Testable examples
 
-Some exercises can contain [Example tests](https://blog.golang.org/examples)
-that document the exercise API. These examples are run alongside the standard
-exercise tests and will verify that the exercise API is working as expected.
-They are not required by all exercises and are not intended to replace the
-data-driven tests. They are most useful for providing examples of how an
-exercise's API is used. Have a look at the example tests in the [clock exercise](https://github.com/exercism/xgo/blob/master/exercises/clock/example_clock_test.go)
-to see them in action.
+Some exercises can contain [Example tests](https://blog.golang.org/examples) that document the exercise API. These
+examples are run alongside the standard exercise tests and will verify that the
+exercise API is working as expected. They are not required by all exercises and
+are most useful when clarifying the API of the exercise.
 
 ### Stub files
 
diff --git a/config.json b/config.json
index 61c5551d..5e28ca2d 100644
--- a/config.json
+++ b/config.json
@@ -222,12 +222,9 @@
       ]
     },
     {
-      "difficulty": 5,
+      "difficulty": 1,
       "slug": "sum-of-multiples",
-      "topics": [
-	"Control-flow (loops)",
-	"Mathematics"
-      ]
+      "topics": []
     },
     {
       "difficulty": 1,
diff --git a/exercises/atbash-cipher/.meta/gen.go b/exercises/atbash-cipher/.meta/gen.go
index 6d1d13cc..d7e75242 100644
--- a/exercises/atbash-cipher/.meta/gen.go
+++ b/exercises/atbash-cipher/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/bob/.meta/gen.go b/exercises/bob/.meta/gen.go
index a9482f1c..1d787691 100644
--- a/exercises/bob/.meta/gen.go
+++ b/exercises/bob/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/bowling/.meta/gen.go b/exercises/bowling/.meta/gen.go
index beb36033..8f2a8225 100644
--- a/exercises/bowling/.meta/gen.go
+++ b/exercises/bowling/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/bracket-push/.meta/gen.go b/exercises/bracket-push/.meta/gen.go
index 5c4309eb..34f3eefe 100644
--- a/exercises/bracket-push/.meta/gen.go
+++ b/exercises/bracket-push/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/change/.meta/gen.go b/exercises/change/.meta/gen.go
index c07c4893..2b02e545 100644
--- a/exercises/change/.meta/gen.go
+++ b/exercises/change/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/clock/.meta/gen.go b/exercises/clock/.meta/gen.go
index 0772915d..4636d2dd 100644
--- a/exercises/clock/.meta/gen.go
+++ b/exercises/clock/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/connect/.meta/gen.go b/exercises/connect/.meta/gen.go
index 6bb4bdd2..1e3e0afa 100644
--- a/exercises/connect/.meta/gen.go
+++ b/exercises/connect/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/custom-set/.meta/gen.go b/exercises/custom-set/.meta/gen.go
index a7869735..1388b9c7 100644
--- a/exercises/custom-set/.meta/gen.go
+++ b/exercises/custom-set/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/forth/.meta/gen.go b/exercises/forth/.meta/gen.go
index 90c34121..ac841595 100644
--- a/exercises/forth/.meta/gen.go
+++ b/exercises/forth/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/gigasecond/.meta/gen.go b/exercises/gigasecond/.meta/gen.go
index 08580242..fbebbe02 100644
--- a/exercises/gigasecond/.meta/gen.go
+++ b/exercises/gigasecond/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/hamming/.meta/gen.go b/exercises/hamming/.meta/gen.go
index cecf05e9..3e07c4af 100644
--- a/exercises/hamming/.meta/gen.go
+++ b/exercises/hamming/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/largest-series-product/.meta/gen.go b/exercises/largest-series-product/.meta/gen.go
index 2d5e657b..87599040 100644
--- a/exercises/largest-series-product/.meta/gen.go
+++ b/exercises/largest-series-product/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/leap/.meta/gen.go b/exercises/leap/.meta/gen.go
index 673cd126..c76280cf 100644
--- a/exercises/leap/.meta/gen.go
+++ b/exercises/leap/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/luhn/.meta/gen.go b/exercises/luhn/.meta/gen.go
index 84c1346d..03a7f876 100644
--- a/exercises/luhn/.meta/gen.go
+++ b/exercises/luhn/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/meetup/.meta/gen.go b/exercises/meetup/.meta/gen.go
index b208af57..e7996d73 100644
--- a/exercises/meetup/.meta/gen.go
+++ b/exercises/meetup/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/phone-number/.meta/gen.go b/exercises/phone-number/.meta/gen.go
index ede00cea..f038ae82 100644
--- a/exercises/phone-number/.meta/gen.go
+++ b/exercises/phone-number/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/raindrops/.meta/gen.go b/exercises/raindrops/.meta/gen.go
index 1bae154d..486292a5 100644
--- a/exercises/raindrops/.meta/gen.go
+++ b/exercises/raindrops/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/rna-transcription/.meta/gen.go b/exercises/rna-transcription/.meta/gen.go
index 2cc61c94..3e3e047c 100644
--- a/exercises/rna-transcription/.meta/gen.go
+++ b/exercises/rna-transcription/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/roman-numerals/.meta/gen.go b/exercises/roman-numerals/.meta/gen.go
index e11ea615..568efeb7 100644
--- a/exercises/roman-numerals/.meta/gen.go
+++ b/exercises/roman-numerals/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/scrabble-score/.meta/gen.go b/exercises/scrabble-score/.meta/gen.go
index 0a1f3b12..b51e77a9 100644
--- a/exercises/scrabble-score/.meta/gen.go
+++ b/exercises/scrabble-score/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/transpose/.meta/gen.go b/exercises/transpose/.meta/gen.go
index 590709f7..1dcb047f 100644
--- a/exercises/transpose/.meta/gen.go
+++ b/exercises/transpose/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (
diff --git a/exercises/variable-length-quantity/.meta/gen.go b/exercises/variable-length-quantity/.meta/gen.go
new file mode 100644
index 00000000..5b71a941
--- /dev/null
+++ b/exercises/variable-length-quantity/.meta/gen.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+	"log"
+	"text/template"
+
+	"../../../gen"
+)
+
+func main() {
+	t := template.New("").Funcs(template.FuncMap{
+		"GroupComment": GroupComment,
+		"byteSlice":    byteSlice,
+		"lengthSlice":  lengthSlice,
+	})
+	t, err := t.Parse(tmpl)
+	if err != nil {
+		log.Fatal(err)
+	}
+	var j js
+	if err := gen.Gen("variable-length-quantity", &j, t); err != nil {
+		log.Fatal(err)
+	}
+}
+
+// byteSlice converts a slice of uint32 to a byte slice.
+func byteSlice(ns []uint32) []byte {
+	b := make([]byte, len(ns))
+	for i, n := range ns {
+		b[i] = byte(n)
+	}
+	return b
+}
+
+// lengthSlice returns the length of given slice.
+func lengthSlice(ns []uint32) int {
+	return len(ns)
+}
+
+// The JSON structure we expect to be able to unmarshal into
+type js struct {
+	Groups []TestGroup `json:"Cases"`
+}
+
+type TestGroup struct {
+	Description string
+	Cases       []OneCase
+}
+
+type OneCase struct {
+	Description string
+	Property    string   // "encode" or "decode"
+	Input       []uint32 // supports both []byte and []uint32 in JSON.
+	Expected    []uint32 // supports []byte, []uint32, or null in JSON.
+}
+
+// PropertyMatch returns true when given test case c has .Property field matching property;
+// this serves as a filter to put test cases with "like" property into the same group.
+func (c OneCase) PropertyMatch(property string) bool { return c.Property == property }
+
+// GroupComment looks in each of the test case groups to find the
+// group for which every test case has the .Property matching given property;
+// it returns the .Description field for the matching property group,
+// or a 'Note: ...' if no test group consistently matches given property.
+func GroupComment(groups []TestGroup, property string) string {
+	for _, group := range groups {
+		propertyGroupMatch := true
+		for _, testcase := range group.Cases {
+			if !testcase.PropertyMatch(property) {
+				propertyGroupMatch = false
+				break
+			}
+		}
+		if propertyGroupMatch {
+			return group.Description
+		}
+	}
+	return "Note: Apparent inconsistent use of \"property\": \"" + property + "\" within test case group!"
+}
+
+// template applied to above data structure generates the Go test cases
+var tmpl = `package variablelengthquantity
+
+{{.Header}}
+
+// {{GroupComment .J.Groups "encode"}}
+var encodeTestCases = []struct {
+	description string
+	input	[]uint32
+	output	[]byte
+}{ {{range .J.Groups}} {{range .Cases}}
+	{{if .PropertyMatch "encode"}} {
+		{{printf "%q" .Description}},
+		{{printf "%#v" .Input }},
+		{{byteSlice .Expected | printf "%#v" }},
+	},{{- end}}{{end}}{{end}}
+}
+
+// {{GroupComment .J.Groups "decode"}}
+var decodeTestCases = []struct {
+	description string
+	input	[]byte
+	output	[]uint32 // nil slice indicates error expected.
+	size int
+}{ {{range .J.Groups}} {{range .Cases}}
+	{{if .PropertyMatch "decode"}} {
+		{{printf "%q" .Description}},
+		{{byteSlice .Input | printf "%#v" }},
+		{{printf "%#v" .Expected }},
+		{{lengthSlice .Input}},
+	},{{- end}}{{end}}{{end}}
+}
+`
diff --git a/exercises/variable-length-quantity/example.go b/exercises/variable-length-quantity/example.go
index 685217ea..530cd075 100644
--- a/exercises/variable-length-quantity/example.go
+++ b/exercises/variable-length-quantity/example.go
@@ -1,9 +1,13 @@
 package variablelengthquantity
 
-const testVersion = 2
+import "errors"
 
-// EncodeVarint returns the varint encoding of x.
-func EncodeVarint(x uint32) []byte {
+const testVersion = 3
+
+var ErrUnterminatedSequence = errors.New("unterminated sequence")
+
+// encodeInt returns the varint encoding of x.
+func encodeInt(x uint32) []byte {
 	if x>>7 == 0 {
 		return []byte{
 			byte(x),
@@ -25,7 +29,17 @@ func EncodeVarint(x uint32) []byte {
 		}
 	}
 
+	if x>>28 == 0 {
+		return []byte{
+			byte(0x80 | x>>21),
+			byte(0x80 | x>>14),
+			byte(0x80 | x>>6),
+			byte(127 & x),
+		}
+	}
+
 	return []byte{
+		byte(0x80 | x>>28),
 		byte(0x80 | x>>21),
 		byte(0x80 | x>>14),
 		byte(0x80 | x>>7),
@@ -33,26 +47,55 @@ func EncodeVarint(x uint32) []byte {
 	}
 }
 
-// DecodeVarint reads a varint-encoded integer from the slice.
+// decodeInt reads a varint-encoded integer from the slice.
 // It returns the integer and the number of bytes consumed, or
 // zero if there is not enough.
-func DecodeVarint(buf []byte) (x uint32, n int) {
+func decodeInt(buf []byte) (x uint32, n int, err error) {
 	if len(buf) < 1 {
-		return 0, 0
+		return 0, 0, nil
 	}
 
-	if buf[0] <= 0x80 {
-		return uint32(buf[0]), 1
+	if buf[0] < 0x80 {
+		return uint32(buf[0]), 1, nil
 	}
 
 	var b byte
 	for n, b = range buf {
 		x = x << 7
-		x |= uint32(b) & 0x7F
+		x |= uint32(b) & 0x7f
 		if (b & 0x80) == 0 {
-			return x, n + 1
+			return x, n + 1, nil
 		}
 	}
 
-	return x, 0
+	return x, 0, ErrUnterminatedSequence
+}
+
+// EncodeVarint encodes a slice of uint32 into a var-int encoded bytes.
+func EncodeVarint(xa []uint32) []byte {
+	result := make([]byte, 0)
+	for _, x := range xa {
+		result = append(result, encodeInt(x)...)
+	}
+	return result
+}
+
+// DecodeVarint decodes a buffer of var-int encoded values into
+// a slice of uint32 values; an error is returned if buf doesn't
+// decode var-int successfully.
+func DecodeVarint(buf []byte) (ra []uint32, n int, err error) {
+	if len(buf) == 0 {
+		return []uint32{0}, 1, nil
+	}
+	usedBytes := 0
+	ra = make([]uint32, 0)
+	for usedBytes < len(buf) {
+		r, nUsed, err := decodeInt(buf[usedBytes:])
+		if err != nil {
+			return nil, usedBytes, err
+		}
+		ra = append(ra, r)
+		usedBytes += nUsed
+	}
+	return ra, usedBytes, nil
 }
diff --git a/exercises/word-count/.meta/gen.go b/exercises/word-count/.meta/gen.go
index 7775b964..6311b328 100644
--- a/exercises/word-count/.meta/gen.go
+++ b/exercises/word-count/.meta/gen.go
@@ -1,3 +1,5 @@
+// +build ignore
+
 package main
 
 import (


Test patch:
diff --git a/exercises/clock/example_clock_test.go b/exercises/clock/example_clock_test.go
index 4152d97c..11578548 100644
--- a/exercises/clock/example_clock_test.go
+++ b/exercises/clock/example_clock_test.go
@@ -2,53 +2,39 @@ package clock
 
 import "fmt"
 
-func ExampleClock_new() {
-	// a new clock
+func ExampleClock() {
+	// Create a clock
 	clock1 := New(10, 30)
 	fmt.Println(clock1.String())
 
-	// Output: 10:30
-}
-
-func ExampleClock_Add_add() {
-	// create a clock
-	clock := New(10, 30)
-
-	// add 30 minutes to it
-	clock = clock.Add(30)
-	fmt.Println(clock.String())
-
-	// Output: 11:00
-}
-
-func ExampleClock_Add_subtract() {
-	// create a clock
-	clock := New(10, 30)
-
-	// subtract an hour and a half from it
-	clock = clock.Add(-90)
-	fmt.Println(clock.String())
-
-	// Output: 09:00
-}
+	// Add 30 minutes to it
+	clock1 = clock1.Add(30)
+	fmt.Println(clock1.String())
 
-func ExampleClock_compare() {
-	// a new clock
-	clock1 := New(10, 30)
+	// Subtract an hour and a half from it
+	clock1 = clock1.Add(-90)
+	fmt.Println(clock1.String())
 
-	// a second clock, same as the first
-	clock2 := New(10, 30)
+	// Create a second clock
+	clock2 := New(9, 30)
+	fmt.Println(clock2.String())
 
-	// are the clocks equal?
+	// Are the clocks equal?
 	fmt.Println(clock2 == clock1)
 
-	// change the second clock
+	// Change the second clock
 	clock2 = clock2.Add(30)
+	fmt.Println(clock2.String())
 
-	// are the clocks equal now?
+	// Are the clocks equal now?
 	fmt.Println(clock2 == clock1)
 
 	// Output:
+	// 10:30
+	// 11:00
+	// 09:30
+	// 09:30
 	// true
+	// 10:00
 	// false
 }
diff --git a/exercises/variable-length-quantity/cases_test.go b/exercises/variable-length-quantity/cases_test.go
new file mode 100644
index 00000000..d4026fab
--- /dev/null
+++ b/exercises/variable-length-quantity/cases_test.go
@@ -0,0 +1,161 @@
+package variablelengthquantity
+
+// Source: exercism/x-common
+// Commit: d6a62f7 variable-length-quantity: Fix canonical-data.json formatting
+// x-common version: 1.0.0
+
+// Encode a series of integers, producing a series of bytes.
+var encodeTestCases = []struct {
+	description string
+	input       []uint32
+	output      []byte
+}{
+	{
+		"zero",
+		[]uint32{0x0},
+		[]byte{0x0},
+	},
+	{
+		"arbitrary single byte",
+		[]uint32{0x40},
+		[]byte{0x40},
+	},
+	{
+		"largest single byte",
+		[]uint32{0x7f},
+		[]byte{0x7f},
+	},
+	{
+		"smallest double byte",
+		[]uint32{0x80},
+		[]byte{0x81, 0x0},
+	},
+	{
+		"arbitrary double byte",
+		[]uint32{0x2000},
+		[]byte{0xc0, 0x0},
+	},
+	{
+		"largest double byte",
+		[]uint32{0x3fff},
+		[]byte{0xff, 0x7f},
+	},
+	{
+		"smallest triple byte",
+		[]uint32{0x4000},
+		[]byte{0x81, 0x80, 0x0},
+	},
+	{
+		"arbitrary triple byte",
+		[]uint32{0x100000},
+		[]byte{0xc0, 0x80, 0x0},
+	},
+	{
+		"largest triple byte",
+		[]uint32{0x1fffff},
+		[]byte{0xff, 0xff, 0x7f},
+	},
+	{
+		"smallest quadruple byte",
+		[]uint32{0x200000},
+		[]byte{0x81, 0x80, 0x80, 0x0},
+	},
+	{
+		"arbitrary quadruple byte",
+		[]uint32{0x8000000},
+		[]byte{0xc0, 0x80, 0x80, 0x0},
+	},
+	{
+		"largest quadruple byte",
+		[]uint32{0xfffffff},
+		[]byte{0xff, 0xff, 0xff, 0x7f},
+	},
+	{
+		"smallest quintuple byte",
+		[]uint32{0x10000000},
+		[]byte{0x81, 0x80, 0x80, 0x80, 0x0},
+	},
+	{
+		"arbitrary quintuple byte",
+		[]uint32{0xff000000},
+		[]byte{0x8f, 0xf8, 0x80, 0x80, 0x0},
+	},
+	{
+		"maximum 32-bit integer input",
+		[]uint32{0xffffffff},
+		[]byte{0x8f, 0xff, 0xff, 0xff, 0x7f},
+	},
+	{
+		"two single-byte values",
+		[]uint32{0x40, 0x7f},
+		[]byte{0x40, 0x7f},
+	},
+	{
+		"two multi-byte values",
+		[]uint32{0x4000, 0x123456},
+		[]byte{0x81, 0x80, 0x0, 0xc8, 0xe8, 0x56},
+	},
+	{
+		"many multi-byte values",
+		[]uint32{0x2000, 0x123456, 0xfffffff, 0x0, 0x3fff, 0x4000},
+		[]byte{0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0},
+	},
+}
+
+// Decode a series of bytes, producing a series of integers.
+var decodeTestCases = []struct {
+	description string
+	input       []byte
+	output      []uint32 // nil slice indicates error expected.
+	size        int
+}{
+
+	{
+		"one byte",
+		[]byte{0x7f},
+		[]uint32{0x7f},
+		1,
+	},
+	{
+		"two bytes",
+		[]byte{0xc0, 0x0},
+		[]uint32{0x2000},
+		2,
+	},
+	{
+		"three bytes",
+		[]byte{0xff, 0xff, 0x7f},
+		[]uint32{0x1fffff},
+		3,
+	},
+	{
+		"four bytes",
+		[]byte{0x81, 0x80, 0x80, 0x0},
+		[]uint32{0x200000},
+		4,
+	},
+	{
+		"maximum 32-bit integer",
+		[]byte{0x8f, 0xff, 0xff, 0xff, 0x7f},
+		[]uint32{0xffffffff},
+		5,
+	},
+	{
+		"incomplete sequence causes error",
+		[]byte{0xff},
+		[]uint32(nil),
+		1,
+	},
+	{
+		"incomplete sequence causes error, even if value is zero",
+		[]byte{0x80},
+		[]uint32(nil),
+		1,
+	},
+	{
+		"multiple values",
+		[]byte{0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0},
+		[]uint32{0x2000, 0x123456, 0xfffffff, 0x0, 0x3fff, 0x4000},
+		15,
+	},
+}
diff --git a/exercises/variable-length-quantity/variable_length_quantity_test.go b/exercises/variable-length-quantity/variable_length_quantity_test.go
index e31326ce..bee3e7b5 100644
--- a/exercises/variable-length-quantity/variable_length_quantity_test.go
+++ b/exercises/variable-length-quantity/variable_length_quantity_test.go
@@ -2,28 +2,11 @@ package variablelengthquantity
 
 import (
 	"bytes"
+	"reflect"
 	"testing"
 )
 
-const targetTestVersion = 2
-
-var testCases = []struct {
-	input  []byte
-	output uint32
-	size   int
-}{
-	0:  {[]byte{0x7F}, 127, 1},
-	1:  {[]byte{0x81, 0x00}, 128, 2},
-	2:  {[]byte{0xC0, 0x00}, 8192, 2},
-	3:  {[]byte{0xFF, 0x7F}, 16383, 2},
-	4:  {[]byte{0x81, 0x80, 0x00}, 16384, 3},
-	5:  {[]byte{0xFF, 0xFF, 0x7F}, 2097151, 3},
-	6:  {[]byte{0x81, 0x80, 0x80, 0x00}, 2097152, 4},
-	7:  {[]byte{0xC0, 0x80, 0x80, 0x00}, 134217728, 4},
-	8:  {[]byte{0xFF, 0xFF, 0xFF, 0x7F}, 268435455, 4},
-	9:  {[]byte{0x82, 0x00}, 256, 2},
-	10: {[]byte{0x81, 0x10}, 144, 2},
-}
+const targetTestVersion = 3
 
 func TestTestVersion(t *testing.T) {
 	if testVersion != targetTestVersion {
@@ -32,24 +15,29 @@ func TestTestVersion(t *testing.T) {
 }
 
 func TestDecodeVarint(t *testing.T) {
-	for i, tc := range testCases {
-		o, size := DecodeVarint(tc.input)
-		if o != tc.output {
-			t.Fatalf("FAIL: case %d - expected %d got %d\n", i, tc.output, o)
+	for i, tc := range decodeTestCases {
+		o, size, err := DecodeVarint(tc.input)
+		if err != nil {
+			var _ error = err
+			if tc.output != nil {
+				t.Fatalf("FAIL: case %d | %s\nexpected %#v got error: %q\n", i, tc.description, tc.output, err)
+			}
+		} else if tc.output == nil {
+			t.Fatalf("FAIL: case %d | %s\nexpected error, got %#v\n", i, tc.description, o)
+		} else if !reflect.DeepEqual(o, tc.output) {
+			t.Fatalf("FAIL: case %d | %s\nexpected\t%#v\ngot\t\t%#v\n", i, tc.description, tc.output, o)
 		} else if size != tc.size {
-			t.Fatalf("FAIL: case %d - expected encoding size of %d bytes\ngot %d bytes\n", i, tc.size, size)
-		} else {
-			t.Logf("PASS: case %d - %#v\n", i, tc.input)
+			t.Fatalf("FAIL: case %d | %s\n expected encoding size of %d bytes\ngot %d bytes\n", i, tc.description, tc.size, size)
 		}
+		t.Logf("PASS: case %d | %s\n", i, tc.description)
 	}
 }
 
 func TestEncodeVarint(t *testing.T) {
-	for i, tc := range testCases {
-		if encoded := EncodeVarint(tc.output); bytes.Compare(encoded, tc.input) != 0 {
-			t.Fatalf("FAIL: case %d - %d \nexpected\t%#v\ngot\t\t%#v\n", i, tc.output, tc.input, encoded)
-		} else {
-			t.Logf("PASS: case %d - %#v\n", i, tc.input)
+	for i, tc := range encodeTestCases {
+		if encoded := EncodeVarint(tc.input); bytes.Compare(encoded, tc.output) != 0 {
+			t.Fatalf("FAIL: case %d | %s\nexpected\t%#v\ngot\t\t%#v\n", i, tc.description, tc.output, encoded)
 		}
+		t.Logf("PASS: case %d | %s\n", i, tc.description)
 	}
 }