212 lines
6.2 KiB
Go
212 lines
6.2 KiB
Go
// Kindergarten garden
|
|
//
|
|
// You must define a type Garden with constructor
|
|
//
|
|
// func NewGarden(diagram string, children []string) (*Garden, error)
|
|
//
|
|
// and method
|
|
//
|
|
// func (g *Garden) Plants(child string) ([]string, bool)
|
|
//
|
|
// The diagram argument starts each row with a '\n'. This allows Go's
|
|
// raw string literals to present diagrams in source code nicely as two
|
|
// rows flush left, for example,
|
|
//
|
|
// diagram := `
|
|
// VVCCGG
|
|
// VVCCGG`
|
|
|
|
package kindergarten
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
type lookup struct {
|
|
child string
|
|
plants []string
|
|
ok bool
|
|
}
|
|
|
|
type gardenTest struct {
|
|
number int
|
|
diagram string
|
|
children []string
|
|
ok bool
|
|
lookups []lookup
|
|
}
|
|
|
|
var tests = []gardenTest{
|
|
{1, `
|
|
RC
|
|
GG`, []string{"Alice"}, true, []lookup{
|
|
{"Alice", []string{"radishes", "clover", "grass", "grass"}, true},
|
|
}},
|
|
{2, `
|
|
VC
|
|
RC`, []string{"Alice"}, true, []lookup{
|
|
{"Alice", []string{"violets", "clover", "radishes", "clover"}, true},
|
|
}},
|
|
{3, `
|
|
VVCG
|
|
VVRC`, []string{"Alice", "Bob"}, true, []lookup{
|
|
{"Bob", []string{"clover", "grass", "radishes", "clover"}, true},
|
|
}},
|
|
{4, `
|
|
VVCCGG
|
|
VVCCGG`, []string{"Alice", "Bob", "Charlie"}, true, []lookup{
|
|
{"Bob", []string{"clover", "clover", "clover", "clover"}, true},
|
|
{"Charlie", []string{"grass", "grass", "grass", "grass"}, true},
|
|
}},
|
|
test5, // full garden test
|
|
test6, // out of order names test
|
|
|
|
// failure tests
|
|
{7, "RC\nGG", []string{"Alice"}, false, nil}, // wrong diagram format
|
|
{8, `
|
|
RCCC
|
|
GG`, []string{""}, false, nil}, // mismatched rows
|
|
{9, `
|
|
RCC
|
|
GGC`, []string{"Alice"}, false, nil}, // odd number of cups
|
|
{10, `
|
|
RCCC
|
|
GGCC`, []string{"Alice", "Alice"}, false, nil}, // duplicate name
|
|
{11, `
|
|
rc
|
|
gg`, []string{"Alice"}, false, nil}, // invaid cup codes
|
|
{12, `
|
|
RC
|
|
GG`, []string{"Alice"}, true, []lookup{ // lookup invalid name
|
|
{"Bob", []string{"radishes", "clover", "grass", "grass"}, false},
|
|
}},
|
|
}
|
|
|
|
// full garden test
|
|
var test5 = gardenTest{5, `
|
|
VRCGVVRVCGGCCGVRGCVCGCGV
|
|
VRCCCGCRRGVCGCRVVCVGCGCV`, []string{
|
|
"Alice", "Bob", "Charlie", "David", "Eve", "Fred",
|
|
"Ginny", "Harriet", "Ileana", "Joseph", "Kincaid", "Larry"}, true, []lookup{
|
|
{"Alice", []string{"violets", "radishes", "violets", "radishes"}, true},
|
|
{"Bob", []string{"clover", "grass", "clover", "clover"}, true},
|
|
{"Charlie", []string{"violets", "violets", "clover", "grass"}, true},
|
|
{"David", []string{"radishes", "violets", "clover", "radishes"}, true},
|
|
{"Eve", []string{"clover", "grass", "radishes", "grass"}, true},
|
|
{"Fred", []string{"grass", "clover", "violets", "clover"}, true},
|
|
{"Ginny", []string{"clover", "grass", "grass", "clover"}, true},
|
|
{"Harriet", []string{"violets", "radishes", "radishes", "violets"}, true},
|
|
{"Ileana", []string{"grass", "clover", "violets", "clover"}, true},
|
|
{"Joseph", []string{"violets", "clover", "violets", "grass"}, true},
|
|
{"Kincaid", []string{"grass", "clover", "clover", "grass"}, true},
|
|
{"Larry", []string{"grass", "violets", "clover", "violets"}, true},
|
|
}}
|
|
|
|
// out of order names test
|
|
var (
|
|
test6names = []string{"Samantha", "Patricia", "Xander", "Roger"}
|
|
test6 = gardenTest{6, `
|
|
VCRRGVRG
|
|
RVGCCGCV`, append([]string{}, test6names...), true, []lookup{
|
|
{"Patricia", []string{"violets", "clover", "radishes", "violets"}, true},
|
|
{"Roger", []string{"radishes", "radishes", "grass", "clover"}, true},
|
|
{"Samantha", []string{"grass", "violets", "clover", "grass"}, true},
|
|
{"Xander", []string{"radishes", "grass", "clover", "violets"}, true},
|
|
}}
|
|
)
|
|
|
|
func TestGarden(t *testing.T) {
|
|
for _, test := range tests {
|
|
g, err := NewGarden(test.diagram, test.children)
|
|
switch {
|
|
case err != nil:
|
|
if test.ok {
|
|
t.Fatalf("NewGarden test %d returned error %q. Error not expected.",
|
|
test.number, err)
|
|
}
|
|
case !test.ok:
|
|
t.Fatalf("NewGarden test %d error = %v. Expected error.",
|
|
test.number, err)
|
|
}
|
|
for _, l := range test.lookups {
|
|
switch plants, ok := g.Plants(l.child); {
|
|
case ok != l.ok:
|
|
t.Fatalf("Garden %d lookup %s returned ok = %t, want %t.",
|
|
test.number, l.child, ok, l.ok)
|
|
case ok && !reflect.DeepEqual(plants, l.plants):
|
|
t.Fatalf("Garden %d lookup %s = %v, want %v.",
|
|
test.number, l.child, plants, l.plants)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The lazy way to meet the alphabetizing requirement is with sort.Strings
|
|
// on the argument slice. That's an in-place sort though and it's bad practice
|
|
// to have a side effect.
|
|
func TestNamesNotModified(t *testing.T) {
|
|
cp := append([]string{}, test6names...)
|
|
_, err := NewGarden(test6.diagram, cp)
|
|
if err != nil {
|
|
t.Skip("TestNamesNotModified requires valid garden")
|
|
}
|
|
if !reflect.DeepEqual(cp, test6names) {
|
|
t.Fatalf("NewGarden modified children argment. " +
|
|
"Arguments should not be modified.")
|
|
}
|
|
sort.Strings(cp)
|
|
if reflect.DeepEqual(cp, test6names) {
|
|
t.Skip("TestNamesNotModified requires names out of order")
|
|
}
|
|
}
|
|
|
|
// A test taken from the Ruby tests. It checks that Garden objects
|
|
// are self-contained and do not rely on package variables.
|
|
func TestTwoGardens(t *testing.T) {
|
|
diagram := `
|
|
VCRRGVRG
|
|
RVGCCGCV`
|
|
g1, err1 := NewGarden(diagram, []string{"Alice", "Bob", "Charlie", "Dan"})
|
|
g2, err2 := NewGarden(diagram, []string{"Bob", "Charlie", "Dan", "Erin"})
|
|
if err1 != nil || err2 != nil {
|
|
t.Skip("Two garden test needs valid gardens")
|
|
}
|
|
tf := func(g *Garden, n int, child string, expPlants []string) {
|
|
switch plants, ok := g.Plants(child); {
|
|
case !ok:
|
|
t.Skip("Garden %d lookup %s returned ok = false, want true.",
|
|
n, child)
|
|
case !reflect.DeepEqual(plants, expPlants):
|
|
t.Fatalf("Garden %d lookup %s = %v, want %v.",
|
|
n, child, plants, expPlants)
|
|
}
|
|
}
|
|
tf(g1, 1, "Bob", []string{"radishes", "radishes", "grass", "clover"})
|
|
tf(g2, 2, "Bob", []string{"violets", "clover", "radishes", "violets"})
|
|
tf(g1, 1, "Charlie", []string{"grass", "violets", "clover", "grass"})
|
|
tf(g2, 2, "Charlie", []string{"radishes", "radishes", "grass", "clover"})
|
|
}
|
|
|
|
func BenchmarkNewGarden(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
for _, test := range tests {
|
|
NewGarden(test.diagram, test.children)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkGarden_Plants(b *testing.B) {
|
|
g, err := NewGarden(test5.diagram, test5.children)
|
|
if err != nil {
|
|
b.Skip("BenchmarkGarden_Plants requires valid garden")
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
for _, l := range test5.lookups {
|
|
g.Plants(l.child)
|
|
}
|
|
}
|
|
}
|