From 81b2eaf329bdd56a2c01f6b8c9b4dec485e9b84d Mon Sep 17 00:00:00 2001 From: Lee Nipper Date: Sun, 23 Apr 2017 07:52:12 -0500 Subject: [PATCH] custom-set: update generator (#622) * 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. --- exercises/custom-set/.meta/gen.go | 223 +++++++++++++----------- exercises/custom-set/cases_test.go | 17 +- exercises/custom-set/custom_set_test.go | 4 +- 3 files changed, 137 insertions(+), 107 deletions(-) diff --git a/exercises/custom-set/.meta/gen.go b/exercises/custom-set/.meta/gen.go index 9750b8aae..1388b9c7d 100644 --- a/exercises/custom-set/.meta/gen.go +++ b/exercises/custom-set/.meta/gen.go @@ -3,6 +3,7 @@ package main import ( + "errors" "log" "strconv" "text/template" @@ -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 { @@ -25,6 +28,7 @@ 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) @@ -32,6 +36,7 @@ func str(n int) string { 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 { @@ -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 @@ -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}} ` diff --git a/exercises/custom-set/cases_test.go b/exercises/custom-set/cases_test.go index 97978df43..c1066ac1d 100644 --- a/exercises/custom-set/cases_test.go +++ b/exercises/custom-set/cases_test.go @@ -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{ @@ -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", @@ -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{}, @@ -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{}, @@ -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{}, @@ -128,6 +133,7 @@ var eqCases = []binBoolCase{ // Unique elements can be added to a set var addCases = []eleOpCase{ + { // add to empty set []string{}, "c", @@ -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{}, @@ -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{}, @@ -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{}, diff --git a/exercises/custom-set/custom_set_test.go b/exercises/custom-set/custom_set_test.go index b6063ab2b..985d2eccd 100644 --- a/exercises/custom-set/custom_set_test.go +++ b/exercises/custom-set/custom_set_test.go @@ -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 { @@ -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) {