Skip to content

Commit

Permalink
Merge pull request #674 from leenipper/variable-length-quantity-add-g…
Browse files Browse the repository at this point in the history
…enerator

variable-length-quantity: create test case generator
  • Loading branch information
leenipper authored May 23, 2017
2 parents ed7fe4f + abe0668 commit 6325f89
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 42 deletions.
113 changes: 113 additions & 0 deletions exercises/variable-length-quantity/.meta/gen.go
Original file line number Diff line number Diff line change
@@ -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}}
}
`
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 // 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,
},
}
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 6325f89

Please sign in to comment.