Skip to content

Commit

Permalink
Add Filter function and update unit tests
Browse files Browse the repository at this point in the history
Signed-off-by: Per Goncalves da Silva <[email protected]>
  • Loading branch information
Per Goncalves da Silva committed Feb 10, 2025
1 parent 5b1d0f8 commit 29547aa
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 51 deletions.
11 changes: 11 additions & 0 deletions internal/util/slices/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import (
// Predicate returns true if the object should be kept when filtering
type Predicate[T any] func(entity T) bool

// Filter creates a new slice with all elements from s for which the test returns true
func Filter[T any](s []T, test Predicate[T]) []T {
out := make([]T, 0, len(s))
for i := 0; i < len(s); i++ {
if test(s[i]) {
out = append(out, s[i])
}
}
return slices.Clip(out)
}

// RemoveInPlace removes all elements from s for which test returns true.
// Elements between new length and original length are zeroed out.
func RemoveInPlace[T any](s []T, test Predicate[T]) []T {
Expand Down
262 changes: 211 additions & 51 deletions internal/util/slices/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,77 +3,237 @@ package slices_test
import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/operator-framework/operator-registry/alpha/declcfg"
"github.com/stretchr/testify/require"

"github.com/operator-framework/operator-controller/internal/util/slices"
)

func TestFilter(t *testing.T) {
for _, tt := range []struct {
name string
predicate slices.Predicate[declcfg.Bundle]
want []declcfg.Bundle
func TestAnd(t *testing.T) {
tests := []struct {
name string
predicates []slices.Predicate[int]
input int
want bool
}{
{
name: "simple filter with one predicate",
predicate: func(bundle declcfg.Bundle) bool {
return bundle.Name == "extension1.v1"
name: "all true",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 0 },
func(i int) bool { return i < 10 },
},
want: []declcfg.Bundle{
{Name: "extension1.v1", Package: "extension1", Image: "fake1"},
input: 5,
want: true,
},
{
name: "one false",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 0 },
func(i int) bool { return i < 5 },
},
input: 5,
want: false,
},
{
name: "all false",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 10 },
func(i int) bool { return i < 0 },
},
input: 5,
want: false,
},
{
name: "no predicates",
predicates: []slices.Predicate[int]{},
input: 5,
want: true,
},
{
name: "nil predicates",
predicates: nil,
input: 5,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := slices.And(tt.predicates...)(tt.input)
require.Equal(t, tt.want, got, "And() = %v, want %v", got, tt.want)
})
}
}

func TestOr(t *testing.T) {
tests := []struct {
name string
predicates []slices.Predicate[int]
input int
want bool
}{
{
name: "filter with Not predicate",
predicate: slices.Not(func(bundle declcfg.Bundle) bool {
return bundle.Name == "extension1.v1"
}),
want: []declcfg.Bundle{
{Name: "extension1.v2", Package: "extension1", Image: "fake2"},
{Name: "extension2.v1", Package: "extension2", Image: "fake1"},
name: "all true",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 0 },
func(i int) bool { return i < 10 },
},
input: 5,
want: true,
},
{
name: "filter with And predicate",
predicate: slices.And(
func(bundle declcfg.Bundle) bool {
return bundle.Name == "extension1.v1"
},
func(bundle declcfg.Bundle) bool {
return bundle.Image == "fake1"
},
),
want: []declcfg.Bundle{
{Name: "extension1.v1", Package: "extension1", Image: "fake1"},
name: "one false",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 0 },
func(i int) bool { return i < 5 },
},
input: 5,
want: true,
},
{
name: "filter with Or predicate",
predicate: slices.Or(
func(bundle declcfg.Bundle) bool {
return bundle.Name == "extension1.v1"
},
func(bundle declcfg.Bundle) bool {
return bundle.Image == "fake1"
},
),
want: []declcfg.Bundle{
{Name: "extension1.v1", Package: "extension1", Image: "fake1"},
{Name: "extension2.v1", Package: "extension2", Image: "fake1"},
name: "all false",
predicates: []slices.Predicate[int]{
func(i int) bool { return i > 10 },
func(i int) bool { return i < 0 },
},
input: 5,
want: false,
},
{
name: "no predicates",
predicates: []slices.Predicate[int]{},
input: 5,
want: false,
},
{
name: "nil predicates",
predicates: nil,
input: 5,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := slices.Or(tt.predicates...)(tt.input)
require.Equal(t, tt.want, got, "Or() = %v, want %v", got, tt.want)
})
}
}

func TestNot(t *testing.T) {
tests := []struct {
name string
predicate slices.Predicate[int]
input int
want bool
}{
{
name: "predicate is true",
predicate: func(i int) bool { return i > 0 },
input: 5,
want: false,
},
{
name: "predicate is false",
predicate: func(i int) bool { return i > 3 },
input: 2,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := slices.Not(tt.predicate)(tt.input)
require.Equal(t, tt.want, got, "Not() = %v, want %v", got, tt.want)
})
}
}

func TestFilter(t *testing.T) {
tests := []struct {
name string
slice []int
predicate slices.Predicate[int]
want []int
}{
{
name: "all match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 0 },
want: []int{1, 2, 3, 4, 5},
},
{
name: "some match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 3 },
want: []int{4, 5},
},
{
name: "none match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 5 },
want: []int{},
},
{
name: "empty slice",
slice: []int{},
predicate: func(i int) bool { return i > 5 },
want: []int{},
},
{
name: "nil slice",
slice: nil,
predicate: func(i int) bool { return i > 5 },
want: []int{},
},
} {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in := []declcfg.Bundle{
{Name: "extension1.v1", Package: "extension1", Image: "fake1"},
{Name: "extension1.v2", Package: "extension1", Image: "fake2"},
{Name: "extension2.v1", Package: "extension2", Image: "fake1"},
}
got := slices.Filter(tt.slice, tt.predicate)
require.Equal(t, tt.want, got, "Filter() = %v, want %v", got, tt.want)
})
}
}

actual := slices.RemoveInPlace(in, tt.predicate)
assert.Equal(t, tt.want, actual)
func TestRemoveInPlace(t *testing.T) {
tests := []struct {
name string
slice []int
predicate slices.Predicate[int]
want []int
}{
{
name: "all match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 0 },
want: []int{1, 2, 3, 4, 5},
},
{
name: "some match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 3 },
want: []int{4, 5, 0, 0, 0},
},
{
name: "none match",
slice: []int{1, 2, 3, 4, 5},
predicate: func(i int) bool { return i > 5 },
want: []int{0, 0, 0, 0, 0},
},
{
name: "empty slice",
slice: []int{},
predicate: func(i int) bool { return i > 5 },
want: []int{},
},
{
name: "nil slice",
slice: nil,
predicate: func(i int) bool { return i > 5 },
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = slices.RemoveInPlace(tt.slice, tt.predicate)
require.Equal(t, tt.want, tt.slice, "Filter() = %v, want %v", tt.slice, tt.want)
})
}
}

0 comments on commit 29547aa

Please sign in to comment.