Skip to content

Commit

Permalink
custom-set: update generator (#622)
Browse files Browse the repository at this point in the history
* custom-set: update generator

For issue #629.

Use .Header in generator template.
Align template with latest canonical-data.json.
Use a OneCase struct type to contain the whole
set of possible json objects for the various
test groups.
Add istrSlice as conversion helper for .Expected.

Rework each define & template call to also generate
the var for the test cases. Use .GroupComment helper function
to output the description for the test case group.
Use dict helper function to pass multiple parameters to a template.

Update test program to change hasCases => containsCases,
and eqCases => equalCases; the new templates
output each test case array var from within the template and simply
prepend the property to "Cases" for the variable name.
  • Loading branch information
leenipper authored Apr 23, 2017
1 parent be61c1e commit 81b2eaf
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 107 deletions.
223 changes: 122 additions & 101 deletions exercises/custom-set/.meta/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package main

import (
"errors"
"log"
"strconv"
"text/template"
Expand All @@ -12,8 +13,10 @@ import (

func main() {
t := template.New("").Funcs(template.FuncMap{
"str": str,
"strs": strSlice,
"str": str,
"strs": strSlice,
"istrs": istrSlice,
"dict": dict,
})
t, err := t.Parse(tmpl)
if err != nil {
Expand All @@ -25,13 +28,15 @@ func main() {
}
}

// str converts an integer 1..26 to a letter 'a'..'z'.
func str(n int) string {
if n >= 1 && n <= 26 {
return string('a' - 1 + n)
}
return strconv.Itoa(n)
}

// strSlice converts a slice of int to a slice of string.
func strSlice(ns []int) []string {
s := make([]string, len(ns))
for i, n := range ns {
Expand All @@ -40,66 +45,76 @@ func strSlice(ns []int) []string {
return s
}

// The JSON structure we expect to be able to unmarshal into
type js struct {
IsEmpty unaryBool `json:"empty"`
Contains eleBool
Subset binaryBool
Disjoint binaryBool
Equal binaryBool
Add eleOp
Intersection binaryOp
Difference binaryOp
Union binaryOp
// istrSlice converts a slice of interface{} values whose
// underlying members should be float64(JSON decoded integers)
// to a slice of string.
func istrSlice(ns []interface{}) []string {
s := make([]string, len(ns))
for i, n := range ns {
if fn, ok := n.(float64); ok {
s[i] = str(int(fn))
}
}
return s
}

type unaryBool struct {
Description string
Cases []struct {
Description string
Set []int
Expected bool
// dict sets up and returns a map for pairs of items given;
// it is used in order to pass multiple parameters in a template call.
func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call: odd number of values given")
}
valueMap := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("Expected string for dict key")
}
valueMap[key] = values[i+1]
}
return valueMap, nil
}

type eleBool struct {
Description string
Cases []struct {
Description string
Set []int
Element int
Expected bool
}
// The JSON structure we expect to be able to unmarshal into
type js struct {
Groups TestGroups `json:"Cases"`
}

type binaryBool struct {
type TestGroups []struct {
Description string
Cases []struct {
Description string
Set1 []int
Set2 []int
Expected bool
}
Cases []OneCase
}

type eleOp struct {
type OneCase struct {
Description string
Cases []struct {
Description string
Set []int
Element int
Expected []int
}
Property string
Set []int // "empty"/"contains"/"add" cases
Set1 []int // "subset"/"disjoint"/"equal"/"difference"/"intersection"/"union" cases
Set2 []int // "subset"/"disjoint"/"equal"/"difference"/"intersection"/"union" cases
Element int // "contains"/"add" cases
Expected interface{} // bool or []int
}

type binaryOp struct {
Description string
Cases []struct {
Description string
Set1 []int
Set2 []int
Expected []int
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 (groups TestGroups) GroupComment(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
Expand All @@ -110,72 +125,78 @@ type binaryOp struct {
var tmpl = `
{{/* nested templates for repeated stuff */}}
{{define "binaryBool"}} = []binBoolCase{
{{range .}}{ // {{.Description}}
{{strs .Set1 | printf "%#v"}},
{{strs .Set2 | printf "%#v"}},
{{define "unaryBool"}}{{$property := .PropertyType}}{{with .Groups}}
// {{ .GroupComment $property}}
var {{$property}}Cases = []unaryBoolCase{ {{range .}} {{range .Cases}}
{{if .PropertyMatch $property}} { // {{.Description}}
{{strs .Set | printf "%#v"}},
{{.Expected}},
},
{{end}}}{{end}}
{{define "binaryOp"}} = []binOpCase{
{{range .}}{ // {{.Description}}
{{strs .Set1 | printf "%#v"}},
{{strs .Set2 | printf "%#v"}},
{{strs .Expected | printf "%#v"}},
},
{{end}}}{{end}}
{{- end}}{{end}}{{end}}
}
{{end}}{{end}}
{{define "eleOp"}} = []eleOpCase{
{{range .}}{ // {{.Description}}
{{define "eleBool"}}{{$property := .PropertyType}}{{with .Groups}}
// {{ .GroupComment $property}}
var {{$property}}Cases = []eleBoolCase{ {{range .}} {{range .Cases}}
{{if .PropertyMatch $property}} { // {{.Description}}
{{strs .Set | printf "%#v"}},
{{str .Element | printf "%q"}},
{{strs .Expected | printf "%#v"}},
},
{{end}}}{{end}}
{{/* begin template body */}}
package stringset
// Source: {{.Ori}}
{{if .Commit}}// Commit: {{.Commit}}
{{end}}
// {{.J.IsEmpty.Description}}
var emptyCases = []unaryBoolCase{
{{range .J.IsEmpty.Cases}}{ // {{.Description}}
{{strs .Set | printf "%#v"}},
{{.Expected}},
},
{{end}}}
{{- end}}{{end}}{{end}}
}
{{end}}{{end}}
// {{.J.Contains.Description}}
var hasCases = []eleBoolCase{
{{range .J.Contains.Cases}}{ // {{.Description}}
{{define "eleOp"}}{{$property := .PropertyType}}{{with .Groups}}
// {{ .GroupComment $property}}
var {{$property}}Cases = []eleOpCase{ {{range .}} {{range .Cases}}
{{if .PropertyMatch $property}} { // {{.Description}}
{{strs .Set | printf "%#v"}},
{{str .Element | printf "%q"}},
{{.Expected}},
{{istrs .Expected | printf "%#v"}},
},
{{end}}}
// {{.J.Subset.Description}}
var subsetCases{{template "binaryBool" .J.Subset.Cases}}
// {{.J.Disjoint.Description}}
var disjointCases{{template "binaryBool" .J.Disjoint.Cases}}
// {{.J.Equal.Description}}
var eqCases{{template "binaryBool" .J.Equal.Cases}}
{{- end}}{{end}}{{end}}
}
{{end}}{{end}}
// {{.J.Add.Description}}
var addCases{{template "eleOp" .J.Add.Cases}}
{{define "binaryBool"}}{{$property := .PropertyType}}{{with .Groups}}
// {{ .GroupComment $property}}
var {{$property}}Cases = []binBoolCase{ {{range .}} {{range .Cases}}
{{if .PropertyMatch $property}} { // {{.Description}}
{{strs .Set1 | printf "%#v"}},
{{strs .Set2 | printf "%#v"}},
{{.Expected}},
},
{{- end}}{{end}}{{end}}
}
{{end}}{{end}}
// {{.J.Intersection.Description}}
var intersectionCases{{template "binaryOp" .J.Intersection.Cases}}
{{define "binaryOp"}}{{$property := .PropertyType}}{{with .Groups}}
// {{ .GroupComment $property}}
var {{$property}}Cases = []binOpCase{ {{range .}} {{range .Cases}}
{{if .PropertyMatch $property}} { // {{.Description}}
{{strs .Set1 | printf "%#v"}},
{{strs .Set2 | printf "%#v"}},
{{istrs .Expected | printf "%#v"}},
},
{{- end}}{{end}}{{end}}
}
{{end}}{{end}}
// {{.J.Difference.Description}}
var differenceCases{{template "binaryOp" .J.Difference.Cases}}
{{/* begin template body */}}
package stringset
// {{.J.Union.Description}}
var unionCases{{template "binaryOp" .J.Union.Cases}}
{{.Header}}
{{/* These template calls utilize a dict helper function in order to pass multiple parameters. */}}
{{template "unaryBool" dict "PropertyType" "empty" "Groups" .J.Groups}}
{{template "eleBool" dict "PropertyType" "contains" "Groups" .J.Groups}}
{{template "binaryBool" dict "PropertyType" "subset" "Groups" .J.Groups}}
{{template "binaryBool" dict "PropertyType" "disjoint" "Groups" .J.Groups}}
{{template "binaryBool" dict "PropertyType" "equal" "Groups" .J.Groups}}
{{template "eleOp" dict "PropertyType" "add" "Groups" .J.Groups}}
{{template "binaryOp" dict "PropertyType" "intersection" "Groups" .J.Groups}}
{{template "binaryOp" dict "PropertyType" "difference" "Groups" .J.Groups}}
{{template "binaryOp" dict "PropertyType" "union" "Groups" .J.Groups}}
`
17 changes: 13 additions & 4 deletions exercises/custom-set/cases_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package stringset

// Source: exercism/x-common
// Commit: cda8f98 Create new exercises structure
// Commit: 4527635 custom-set: Fix description to match property name
// x-common version: 1.0.1

// Returns true if the set contains no elements
var emptyCases = []unaryBoolCase{
Expand All @@ -16,7 +17,8 @@ var emptyCases = []unaryBoolCase{
}

// Sets can report if they contain an element
var hasCases = []eleBoolCase{
var containsCases = []eleBoolCase{

{ // nothing is contained in an empty set
[]string{},
"a",
Expand All @@ -36,6 +38,7 @@ var hasCases = []eleBoolCase{

// A set is a subset if all of its elements are contained in the other set
var subsetCases = []binBoolCase{

{ // empty set is a subset of another empty set
[]string{},
[]string{},
Expand Down Expand Up @@ -70,6 +73,7 @@ var subsetCases = []binBoolCase{

// Sets are disjoint if they share no elements
var disjointCases = []binBoolCase{

{ // the empty set is disjoint with itself
[]string{},
[]string{},
Expand Down Expand Up @@ -98,7 +102,8 @@ var disjointCases = []binBoolCase{
}

// Sets with the same elements are equal
var eqCases = []binBoolCase{
var equalCases = []binBoolCase{

{ // empty sets are equal
[]string{},
[]string{},
Expand Down Expand Up @@ -128,6 +133,7 @@ var eqCases = []binBoolCase{

// Unique elements can be added to a set
var addCases = []eleOpCase{

{ // add to empty set
[]string{},
"c",
Expand All @@ -145,8 +151,9 @@ var addCases = []eleOpCase{
},
}

// Intersect returns a set of all shared elements
// Intersection returns a set of all shared elements
var intersectionCases = []binOpCase{

{ // intersection of two empty sets is an empty set
[]string{},
[]string{},
Expand Down Expand Up @@ -176,6 +183,7 @@ var intersectionCases = []binOpCase{

// Difference (or Complement) of a set is a set of all elements that are only in the first set
var differenceCases = []binOpCase{

{ // difference of two empty sets is an empty set
[]string{},
[]string{},
Expand All @@ -200,6 +208,7 @@ var differenceCases = []binOpCase{

// Union returns a set of all elements in either set
var unionCases = []binOpCase{

{ // union of empty sets is an empty set
[]string{},
[]string{},
Expand Down
4 changes: 2 additions & 2 deletions exercises/custom-set/custom_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TestIsEmpty(t *testing.T) {
}

func TestHas(t *testing.T) {
for _, tc := range hasCases {
for _, tc := range containsCases {
s := NewFromSlice(tc.set)
got := s.Has(tc.ele)
if got != tc.want {
Expand Down Expand Up @@ -152,7 +152,7 @@ func TestDisjoint(t *testing.T) {
}

func TestEqual(t *testing.T) {
testBinBool("Equal", Equal, eqCases, t)
testBinBool("Equal", Equal, equalCases, t)
}

func TestAdd(t *testing.T) {
Expand Down

0 comments on commit 81b2eaf

Please sign in to comment.