exercism/go/custom-set/custom_set_test.go

269 lines
6.4 KiB
Go
Raw Normal View History

2016-08-15 19:08:39 +00:00
package stringset
// Implement Set as a collection of unique string values.
//
// API:
//
// New() Set
// NewFromSlice([]string) Set
// (s Set) Add(string) // modify s
// (s Set) Delete(string) // modify s
// (s Set) Has(string) bool
// (s Set) IsEmpty() bool
// (s Set) Len() int
// (s Set) Slice() []string
// (s Set) String() string
// Equal(s1, s2 Set) bool
// Subset(s1, s2 Set) bool // return s1 ⊆ s2
// Disjoint(s1, s2 Set) bool
// Intersection(s1, s2 Set) Set
// Union(s1, s2 Set) Set
// Difference(s1, s2 Set) Set // return s1 s2
// SymmetricDifference(s1, s2 Set) Set
//
// For Set.String, use '{' and '}', output elements as double-quoted strings
// safely escaped with Go syntax, and use a comma and a single space between
// elements. For example {"a", "b"}.
// Format the empty set as {}.
import (
"math/rand"
"reflect"
"strconv"
"testing"
)
const targetTestVersion = 3
func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
}
}
// A first set of tests uses Set.String() to judge correctness.
func TestNew(t *testing.T) {
// New must return an empty set.
want := "{}"
if got := New().String(); got != want {
t.Fatalf(`New().String() = %s, want %s.`, got, want)
}
}
func TestNewFromSlice(t *testing.T) {
// nil slice should give empty set
want := "{}"
if got := NewFromSlice(nil).String(); got != want {
t.Fatalf(`NewFromSlice(nil) = %s, want %s.`, got, want)
}
// slice with one element:
want = `{"a"}`
if got := NewFromSlice([]string{"a"}).String(); got != want {
t.Fatalf(`NewFromSlice([]string{"a"}) = %s, want %s.`, got, want)
}
// slice with repeated element:
if got := NewFromSlice([]string{"a", "a"}).String(); got != want {
t.Fatalf(`NewFromSlice([]string{"a", "a"}) = %s, want %s.`, got, want)
}
// slice with two elements:
got := NewFromSlice([]string{"a", "b"}).String()
want1 := `{"a", "b"}`
want2 := `{"b", "a"}`
if got != want1 && got != want2 { // order undefined
t.Fatalf(`NewFromSlice([]string{"a", "b"}) = %s, want %s or (%s).`,
got, want1, want2)
}
}
func TestSlice(t *testing.T) {
// empty set should produce empty slice
s := New()
if l := s.Slice(); len(l) != 0 {
t.Fatalf(`s.Slice() = %q, want []`, l)
}
// one element:
want := []string{"a"}
s = NewFromSlice(want)
got := s.Slice()
if !reflect.DeepEqual(got, want) {
t.Fatalf(`%v Slice = %q, want %q`, s, got, want)
}
// two elements:
w1 := []string{"a", "b"}
w2 := []string{"b", "a"}
s = NewFromSlice(w1)
got = s.Slice()
if !reflect.DeepEqual(got, w1) && !reflect.DeepEqual(got, w2) {
t.Fatalf(`%v Slice = %q, want %q`, s, got, w1)
}
}
// Trusting NewFromSlice now, remaining tests are table driven, taking data
// from cases_test.go and building sets with NewFromSlice.
// test case types used in cases_test.go
type (
// binary function, bool result (Equal, Subset, Disjoint)
binBoolCase struct {
set1 []string
set2 []string
want bool
}
// unary function, bool result (IsEmpty)
unaryBoolCase struct {
set []string
want bool
}
// unary function, int result (Len)
unaryIntCase struct {
set []string
want int
}
// set-element function, bool result (Has)
eleBoolCase struct {
set []string
ele string
want bool
}
// set-element operator (Add, Delete)
eleOpCase struct {
set []string
ele string
want []string
}
// set-set operator (Union, Intersection, Difference, Symmetric-Difference)
binOpCase struct {
set1 []string
set2 []string
want []string
}
)
// helper for testing Equal, Subset, Disjoint
func testBinBool(name string, f func(Set, Set) bool, cases []binBoolCase, t *testing.T) {
for _, tc := range cases {
s1 := NewFromSlice(tc.set1)
s2 := NewFromSlice(tc.set2)
got := f(s1, s2)
if got != tc.want {
t.Fatalf("%s(%v, %v) = %t, want %t", name, s1, s2, got, tc.want)
}
}
}
func TestEqual(t *testing.T) {
testBinBool("Equal", Equal, eqCases, t)
}
// With Equal tested, remaining tests use it to judge correctness.
// helper for testing Add, Delete
func testEleOp(name string, op func(Set, string), cases []eleOpCase, t *testing.T) {
for _, tc := range cases {
s := NewFromSlice(tc.set)
op(s, tc.ele)
want := NewFromSlice(tc.want)
if !Equal(s, want) {
t.Fatalf("%v %s %q = %v, want %v",
NewFromSlice(tc.set), name, tc.ele, s, want)
}
}
}
func TestAdd(t *testing.T) {
testEleOp("Add", Set.Add, addCases, t)
}
func TestDelete(t *testing.T) {
testEleOp("Delete", Set.Delete, delCases, t)
}
func TestHas(t *testing.T) {
for _, tc := range hasCases {
s := NewFromSlice(tc.set)
got := s.Has(tc.ele)
if got != tc.want {
t.Fatalf("%v Has %q = %t, want %t", s, tc.ele, got, tc.want)
}
}
}
func TestIsEmpty(t *testing.T) {
for _, tc := range emptyCases {
s := NewFromSlice(tc.set)
got := s.IsEmpty()
if got != tc.want {
t.Fatalf("%v IsEmpty = %t, want %t", s, got, tc.want)
}
}
}
func TestLen(t *testing.T) {
for _, tc := range lenCases {
s := NewFromSlice(tc.set)
got := s.Len()
if got != tc.want {
t.Fatalf("%v Len = %d, want %d", s, got, tc.want)
}
}
}
func TestSubset(t *testing.T) {
testBinBool("Subset", Subset, subsetCases, t)
}
func TestDisjoint(t *testing.T) {
testBinBool("Disjoint", Disjoint, disjointCases, t)
}
// helper for testing Union, Intersection, Difference, SymmetricDifference
func testBinOp(name string, f func(Set, Set) Set, cases []binOpCase, t *testing.T) {
for _, tc := range cases {
s1 := NewFromSlice(tc.set1)
s2 := NewFromSlice(tc.set2)
want := NewFromSlice(tc.want)
got := f(s1, s2)
if !Equal(got, want) {
t.Fatalf("%s(%v, %v) = %v, want %v", name, s1, s2, got, want)
}
}
}
func TestUnion(t *testing.T) {
testBinOp("Union", Union, unionCases, t)
}
func TestIntersection(t *testing.T) {
testBinOp("Intersection", Intersection, intersectionCases, t)
}
func TestDifference(t *testing.T) {
testBinOp("Difference", Difference, differenceCases, t)
}
func TestSymmetricDifference(t *testing.T) {
testBinOp("SymmetricDifference", SymmetricDifference, symmetricDifferenceCases, t)
}
func BenchmarkNewFromSlice1e1(b *testing.B) { bench(1e1, b) }
func BenchmarkNewFromSlice1e2(b *testing.B) { bench(1e2, b) }
func BenchmarkNewFromSlice1e3(b *testing.B) { bench(1e3, b) }
func BenchmarkNewFromSlice1e4(b *testing.B) { bench(1e4, b) }
func bench(nAdd int, b *testing.B) {
s := make([]string, nAdd)
for i := range s {
s[i] = strconv.Itoa(rand.Intn(len(s)))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
NewFromSlice(s)
}
}