Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variable-length-quantity: create test case generator #674

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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