Skip to content

Commit

Permalink
variable-length-quantity: create test case generator
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
leenipper committed May 22, 2017
1 parent 925762f commit 5fd7847
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 42 deletions.
115 changes: 115 additions & 0 deletions exercises/variable-length-quantity/.meta/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// +build ignore

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
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}}
}
`
161 changes: 161 additions & 0 deletions exercises/variable-length-quantity/cases_test.go
Original file line number Diff line number Diff line change
@@ -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
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,
},
}
65 changes: 54 additions & 11 deletions exercises/variable-length-quantity/example.go
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -25,34 +29,73 @@ 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),
byte(127 & x),
}
}

// 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
}
Loading

0 comments on commit 5fd7847

Please sign in to comment.