// 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) } } }