package sieve import ( "errors" "slices" "strconv" "testing" ) type ids []int type item struct { ID int Name string } func TestMap(t *testing.T) { t.Run("nil input", func(t *testing.T) { got := Map([]int(nil), func(v int) string { return strconv.Itoa(v) }) if got == nil || len(got) != 0 { t.Fatalf("Map(nil) = %#v, want non-nil empty", got) } }) t.Run("order and call count", func(t *testing.T) { in := []int{3, 1, 2} snap := slices.Clone(in) calls := 0 got := Map(in, func(v int) int { calls++; return v * 10 }) if !slices.Equal(got, []int{30, 10, 20}) { t.Fatalf("Map = %v", got) } if calls != len(in) { t.Fatalf("calls = %d, want %d", calls, len(in)) } got[0] = 99 if !slices.Equal(in, snap) { t.Fatalf("input changed: %v", in) } }) } func TestTryMap(t *testing.T) { t.Run("success", func(t *testing.T) { got, err := TryMap([]string{"1", "2", "3"}, strconv.Atoi) if err != nil || !slices.Equal(got, []int{1, 2, 3}) { t.Fatalf("TryMap = %v, %v", got, err) } }) t.Run("nil input", func(t *testing.T) { got, err := TryMap([]string(nil), strconv.Atoi) if err != nil || got == nil || len(got) != 0 { t.Fatalf("TryMap(nil) = %#v, %v", got, err) } }) sentinel := errors.New("boom") for _, failAt := range []int{0, 2} { t.Run("fails at "+strconv.Itoa(failAt), func(t *testing.T) { calls := 0 got, err := TryMap([]int{0, 1, 2, 3}, func(v int) (int, error) { calls++ if v == failAt { return 0, sentinel } return v, nil }) if got != nil { t.Fatalf("result = %#v, want nil", got) } if err != sentinel || !errors.Is(err, sentinel) { t.Fatalf("err = %v, want sentinel", err) } if calls != failAt+1 { t.Fatalf("calls = %d, want %d", calls, failAt+1) } }) } } func TestFilter(t *testing.T) { even := func(v int) bool { return v%2 == 0 } tests := []struct { name string in []int keep func(int) bool want []int }{ {"nil input", nil, even, []int{}}, {"no matches", []int{1, 3}, even, []int{}}, {"some matches", []int{1, 2, 3, 4}, even, []int{2, 4}}, {"all match", []int{2, 4}, even, []int{2, 4}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { snap := slices.Clone(tt.in) got := Filter(tt.in, tt.keep) if got == nil || !slices.Equal(got, tt.want) { t.Fatalf("Filter = %#v, want %v", got, tt.want) } if len(got) > 0 { got[0] = -1 } if !slices.Equal(tt.in, snap) { t.Fatalf("input changed: %v", tt.in) } }) } t.Run("named type", func(t *testing.T) { var got ids = Filter(ids{1, 2, 3}, even) if !slices.Equal(got, ids{2}) { t.Fatalf("Filter = %v", got) } }) } func TestReduce(t *testing.T) { sum := func(a, v int) int { return a + v } if got := Reduce([]int{1, 2, 3}, 0, sum); got != 6 { t.Fatalf("sum = %d", got) } if got := Reduce([]int(nil), 42, sum); got != 42 { t.Fatalf("Reduce(nil) = %d, want init", got) } if got := Reduce([]string{"a", "b", "c"}, "", func(a, v string) string { return a + v }); got != "abc" { t.Fatalf("concat = %q", got) } if got := Reduce([]int{1, 2}, "", func(a string, v int) string { return a + strconv.Itoa(v) }); got != "12" { t.Fatalf("string accumulator = %q", got) } counts := Reduce([]int{1, 2, 1}, map[int]int{}, func(m map[int]int, v int) map[int]int { m[v]++; return m }) if counts[1] != 2 || counts[2] != 1 { t.Fatalf("map accumulator = %v", counts) } } func TestKeyBy(t *testing.T) { in := []item{{1, "a"}, {2, "b"}, {1, "c"}} calls := 0 got := KeyBy(in, func(it item) int { calls++; return it.ID }) if len(got) != 2 || got[1].Name != "c" || got[2].Name != "b" { t.Fatalf("KeyBy = %v", got) } if calls != len(in) { t.Fatalf("calls = %d", calls) } empty := KeyBy([]item(nil), func(it item) int { return it.ID }) if empty == nil || len(empty) != 0 { t.Fatalf("KeyBy(nil) = %#v", empty) } } func TestGroupBy(t *testing.T) { in := ids{1, 2, 3, 4, 5} snap := slices.Clone(in) var got map[bool]ids = GroupBy(in, func(v int) bool { return v%2 == 0 }) if !slices.Equal(got[false], ids{1, 3, 5}) || !slices.Equal(got[true], ids{2, 4}) { t.Fatalf("GroupBy = %v", got) } odd := got[false] got[false] = append(got[false], 7) got[false][0] = 100 odd[1] = 200 if !slices.Equal(in, snap) { t.Fatalf("input changed: %v", in) } if !slices.Equal(got[true], ids{2, 4}) { t.Fatalf("other group changed: %v", got[true]) } empty := GroupBy([]int(nil), func(v int) int { return v }) if empty == nil || len(empty) != 0 { t.Fatalf("GroupBy(nil) = %#v", empty) } } func TestUniq(t *testing.T) { in := []int{3, 1, 3, 2, 1} snap := slices.Clone(in) if got := Uniq(in); !slices.Equal(got, []int{3, 1, 2}) { t.Fatalf("Uniq = %v", got) } if !slices.Equal(in, snap) { t.Fatalf("input changed: %v", in) } distinct := []int{1, 2, 3} got := Uniq(distinct) got[0] = 9 if distinct[0] != 1 { t.Fatalf("Uniq aliased its input") } empty := Uniq([]int(nil)) if empty == nil || len(empty) != 0 { t.Fatalf("Uniq(nil) = %#v", empty) } var named ids = Uniq(ids{1, 1}) if !slices.Equal(named, ids{1}) { t.Fatalf("Uniq(ids) = %v", named) } } func TestUniqBy(t *testing.T) { in := []item{{1, "a"}, {2, "b"}, {1, "c"}, {3, "d"}, {2, "e"}} calls := 0 got := UniqBy(in, func(it item) int { calls++; return it.ID }) want := []item{{1, "a"}, {2, "b"}, {3, "d"}} if !slices.Equal(got, want) { t.Fatalf("UniqBy = %v", got) } if calls != len(in) { t.Fatalf("calls = %d", calls) } empty := UniqBy([]item(nil), func(it item) int { return it.ID }) if empty == nil || len(empty) != 0 { t.Fatalf("UniqBy(nil) = %#v", empty) } } func TestPartition(t *testing.T) { even := func(v int) bool { return v%2 == 0 } matched, rest := Partition([]int{1, 2, 3, 4, 5}, even) if !slices.Equal(matched, []int{2, 4}) || !slices.Equal(rest, []int{1, 3, 5}) { t.Fatalf("Partition = %v, %v", matched, rest) } matched, rest = Partition([]int(nil), even) if matched == nil || rest == nil || len(matched) != 0 || len(rest) != 0 { t.Fatalf("Partition(nil) = %#v, %#v", matched, rest) } in := []int{2, 4} matched, rest = Partition(in, even) if rest == nil || len(rest) != 0 || !slices.Equal(matched, in) { t.Fatalf("Partition(all) = %#v, %#v", matched, rest) } matched[0] = 9 if in[0] != 2 { t.Fatalf("Partition aliased its input") } var m, r ids = Partition(ids{1, 2}, even) if !slices.Equal(m, ids{2}) || !slices.Equal(r, ids{1}) { t.Fatalf("Partition(ids) = %v, %v", m, r) } } func TestFind(t *testing.T) { calls := 0 got, ok := Find([]int{1, 4, 6, 8}, func(v int) bool { calls++; return v%2 == 0 }) if !ok || got != 4 { t.Fatalf("Find = %d, %v", got, ok) } if calls != 2 { t.Fatalf("calls = %d, want 2", calls) } got, ok = Find([]int{1, 3}, func(v int) bool { return v%2 == 0 }) if ok || got != 0 { t.Fatalf("Find(no match) = %d, %v", got, ok) } s, ok := Find([]string(nil), func(string) bool { return true }) if ok || s != "" { t.Fatalf("Find(nil) = %q, %v", s, ok) } } func TestSet(t *testing.T) { s := SetOf(1, 2, 2, 3) if len(s) != 3 { t.Fatalf("len = %d", len(s)) } if !s.Has(2) || s.Has(4) { t.Fatalf("Has wrong: %v", s) } s.Add(4, 5, 4) if len(s) != 5 || !s.Has(5) { t.Fatalf("Add wrong: %v", s) } delete(s, 1) if s.Has(1) { t.Fatalf("delete had no effect") } got := s.Slice() slices.Sort(got) if !slices.Equal(got, []int{2, 3, 4, 5}) { t.Fatalf("Slice = %v", got) } empty := SetOf[string]() if empty == nil { t.Fatalf("SetOf() is nil") } empty.Add("a") if !empty.Has("a") { t.Fatalf("Add on SetOf() failed") } from := SetOf(ids{7, 7, 8}...) if len(from) != 2 { t.Fatalf("SetOf(ids...) = %v", from) } var zero Set[int] if zero.Has(1) { t.Fatalf("nil Set Has true") } if sl := zero.Slice(); sl == nil || len(sl) != 0 { t.Fatalf("nil Set Slice = %#v", sl) } }