Skip to content

Commit c8b52fb

Browse files
authored
Add unit tests for utils packages (#482)
* Drop `client.DeduplicateWarnings` in favor of controller-runtime `client.New` configures `logf.NewKubeAPIWarningLogger` on the client by default. * Add unit tests for `pkg/utils` * Add unit tests for `pkg/utils/client` * Add unit tests for `pkg/utils/errors` * Add unit tests for `pkg/utils/healthz` * Drop custom `komega` package Using the upstream `komega` package should be good enough for our purposes. While using a global `context.Background` doesn't integrate nicely with ginkgo/gomega's context handling. However, we expect client calls to return fast enough in test that this doesn't really matter. * Fix `EachListItemWithAlloc` and use it * Add unit tests for `pkg/utils/pager`
1 parent 56e5b3d commit c8b52fb

26 files changed

+576
-1545
lines changed

cmd/sharder/main.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@ import (
2323
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
2424

2525
"github.com/timebertt/kubernetes-controller-sharding/cmd/sharder/app"
26-
"github.com/timebertt/kubernetes-controller-sharding/pkg/utils/client"
2726
)
2827

2928
func main() {
30-
client.DeduplicateWarnings()
31-
3229
if err := app.NewCommand().ExecuteContext(signals.SetupSignalHandler()); err != nil {
3330
fmt.Println(err)
3431
os.Exit(1)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ toolchain go1.24.0
77
require (
88
github.com/cespare/xxhash/v2 v2.3.0
99
github.com/go-logr/logr v1.4.2
10-
github.com/google/go-cmp v0.6.0
1110
github.com/google/uuid v1.6.0
1211
github.com/hashicorp/go-multierror v1.1.1
1312
github.com/onsi/ginkgo/v2 v2.22.2
@@ -54,6 +53,7 @@ require (
5453
github.com/google/btree v1.1.3 // indirect
5554
github.com/google/cel-go v0.22.0 // indirect
5655
github.com/google/gnostic-models v0.6.8 // indirect
56+
github.com/google/go-cmp v0.6.0 // indirect
5757
github.com/google/gofuzz v1.2.0 // indirect
5858
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
5959
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect

pkg/controller/sharder/reconciler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func (r *Reconciler) resyncResource(
166166

167167
list := &metav1.PartialObjectMetadataList{}
168168
list.SetGroupVersionKind(gvks[0])
169-
err = pager.New(r.Reader).EachListItem(ctx, list,
169+
err = pager.New(r.Reader).EachListItemWithAlloc(ctx, list,
170170
func(obj client.Object) error {
171171
if !namespaces.Has(obj.GetNamespace()) {
172172
return nil
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 Tim Ebert.
2+
Copyright 2025 Tim Ebert.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,20 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package client
17+
package client_test
1818

1919
import (
20-
"os"
20+
"testing"
2121

22-
"k8s.io/client-go/rest"
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
2324
)
2425

25-
// DeduplicateWarnings configures a client-go warning handler that deduplicates API warnings in order to not spam logs.
26-
func DeduplicateWarnings() {
27-
rest.SetDefaultWarningHandler(
28-
rest.NewWarningWriter(os.Stderr, rest.WarningWriterOptions{
29-
// only print a given warning the first time we receive it
30-
Deduplicate: true,
31-
}),
32-
)
26+
func TestClient(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "Client Utils Suite")
3329
}

pkg/utils/client/options_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2025 Tim Ebert.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package client_test
18+
19+
import (
20+
. "github.com/onsi/ginkgo/v2"
21+
. "github.com/onsi/gomega"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
24+
. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/client"
25+
)
26+
27+
var _ = Describe("ResourceVersion", func() {
28+
It("should set the resourceVersion on GetOptions", func() {
29+
opts := &client.GetOptions{}
30+
opts.ApplyOptions([]client.GetOption{ResourceVersion("1")})
31+
32+
Expect(opts.Raw.ResourceVersion).To(Equal("1"))
33+
})
34+
35+
It("should set the resourceVersion on ListOptions", func() {
36+
opts := &client.ListOptions{}
37+
opts.ApplyOptions([]client.ListOption{ResourceVersion("1")})
38+
39+
Expect(opts.Raw.ResourceVersion).To(Equal("1"))
40+
})
41+
})
Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 Tim Ebert.
2+
Copyright 2025 Tim Ebert.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -14,12 +14,16 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package komega is a modified version of sigs.k8s.io/controller-runtime/pkg/envtest/komega.
18-
// Instead of requiring users to set a global context, the returned functions accept a context to integrate nicely with
19-
// ginkgo's/gomega's timeout/interrupt handling and polling.
20-
// For this, users must pass a spec-specific context, e.g.:
21-
//
22-
// It("...", func(ctx SpecContext) {
23-
// Eventually(ctx, Get(...)).Should(Succeed())
24-
// })
25-
package komega
17+
package errors_test
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestErrors(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "Errors Utils Suite")
29+
}

pkg/utils/errors/multi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
)
2323

2424
// FormatErrors is like multierror.ListFormatFunc without the noisy newlines and tabs.
25-
// It also simplies the format for a single error.
25+
// It also simplifies the format for a single error.
2626
func FormatErrors(es []error) string {
2727
if len(es) == 1 {
2828
return es[0].Error()

pkg/utils/errors/multi_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2025 Tim Ebert.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package errors_test
18+
19+
import (
20+
"fmt"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
25+
. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/errors"
26+
)
27+
28+
var _ = Describe("FormatErrors", func() {
29+
It("should return the single error", func() {
30+
Expect(FormatErrors([]error{fmt.Errorf("foo")})).To(Equal("foo"))
31+
})
32+
33+
It("should return the error count and comma separated error list", func() {
34+
Expect(
35+
FormatErrors([]error{fmt.Errorf("foo"), fmt.Errorf("bar")}),
36+
).To(
37+
Equal("2 errors occurred: foo, bar"),
38+
)
39+
})
40+
})

pkg/utils/healthz/cache_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
Copyright 2025 Tim Ebert.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package healthz_test
18+
19+
import (
20+
"context"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
25+
. "github.com/timebertt/kubernetes-controller-sharding/pkg/utils/healthz"
26+
)
27+
28+
var _ = Describe("CacheSync", func() {
29+
It("should succeed if all informers sync", func() {
30+
checker := CacheSync(fakeSyncWaiter(true))
31+
Expect(checker(nil)).NotTo(HaveOccurred())
32+
})
33+
It("should fail if informers don't sync", func() {
34+
checker := CacheSync(fakeSyncWaiter(false))
35+
Expect(checker(nil)).To(MatchError(ContainSubstring("not synced")))
36+
})
37+
})
38+
39+
type fakeSyncWaiter bool
40+
41+
func (f fakeSyncWaiter) WaitForCacheSync(_ context.Context) bool {
42+
return bool(f)
43+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025 Tim Ebert.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package healthz_test
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestHealthz(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "Healthz Suite")
29+
}

0 commit comments

Comments
 (0)