Skip to content

Commit

Permalink
test(envtest): Setup envtest for reconciler (#555)
Browse files Browse the repository at this point in the history
* setup envtest for reconciler

* move new reconciler tests to dedicated envtest

* add envtest workflow in tests per PR

* address comments
  • Loading branch information
randmonkey authored Sep 10, 2024
1 parent 6457d52 commit 83369ea
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 59 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,39 @@ jobs:
with:
name: tests-report
path: unit-tests.xml

envtest-tests:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v4

- name: setup golang
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- uses: jdx/mise-action@v2
with:
install: false

- name: run envtest tests
run: make test.envtest
env:
GOTESTSUM_JUNITFILE: "envtest-tests.xml"

- name: collect test coverage
uses: actions/upload-artifact@v3
with:
name: coverage-envtest
path: coverage.envtest.out

- name: collect test report
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: tests-report
path: envtest-tests.xml

conformance-tests:
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .tools_versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ gotestsum: "1.12.0"
crd-ref-docs: "0.1.0"
# renovate: datasource=github-releases depName=vektra/mockery
mockery: "2.45.0"
# renovate: datasource=github-releases depName=kubernetes-sigs/controller-runtime
setup-envtest: "0.19.0"
39 changes: 39 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ MISE := $(shell which mise)
mise:
@mise -V >/dev/null || (echo "mise - https://github.com/jdx/mise - not found. Please install it." && exit 1)

mise-plugin-install: mise
@$(MISE) plugin install --yes -q $(DEP) $(URL)

KIC_ROLE_GENERATOR = $(PROJECT_DIR)/bin/kic-role-generator
.PHONY: kic-role-generator
kic-role-generator:
Expand Down Expand Up @@ -133,6 +136,17 @@ mockery: mise yq ## Download mockery locally if necessary.
@$(MISE) plugin install --yes -q mockery https://github.com/cabify/asdf-mockery.git
@$(MISE) install -q mockery@$(MOCKERY_VERSION)

SETUP_ENVTEST_VERSION = $(shell $(YQ) -r '.setup-envtest' < $(TOOLS_VERSIONS_FILE))
SETUP_ENVTEST = $(PROJECT_DIR)/bin/installs/setup-envtest/$(SETUP_ENVTEST_VERSION)/bin/setup-envtest
.PHONY: setup-envtest
setup-envtest: mise ## Download setup-envtest locally if necessary.
@$(MAKE) mise-plugin-install DEP=setup-envtest URL=https://github.com/pmalek/mise-setup-envtest.git
@$(MISE) install setup-envtest@$(SETUP_ENVTEST_VERSION)

.PHONY: use-setup-envtest
use-setup-envtest:
$(SETUP_ENVTEST) use

# ------------------------------------------------------------------------------
# Build
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -328,6 +342,31 @@ test.unit:
test.unit.pretty:
@$(MAKE) _test.unit GOTESTSUM_FORMAT=pkgname GOTESTFLAGS="$(GOTESTFLAGS)" UNIT_TEST_PATHS="$(UNIT_TEST_PATHS)"

ENVTEST_TEST_PATHS := ./test/envtest/...
ENVTEST_TIMEOUT ?= 5m
PKG_LIST=./controller/...,./internal/...,./pkg/...,./modules/...

.PHONY: _test.envtest
_test.envtest: gotestsum setup-envtest use-setup-envtest
KUBEBUILDER_ASSETS="$(shell $(SETUP_ENVTEST) use -p path)" \
GOTESTSUM_FORMAT=$(GOTESTSUM_FORMAT) \
$(GOTESTSUM) -- $(GOTESTFLAGS) \
-race \
-timeout $(ENVTEST_TIMEOUT) \
-covermode=atomic \
-coverpkg=$(PKG_LIST) \
-coverprofile=coverage.envtest.out \
-ldflags "$(LDFLAGS_COMMON) $(LDFLAGS)" \
$(ENVTEST_TEST_PATHS)

.PHONY: test.envtest
test.envtest:
$(MAKE) _test.envtest GOTESTSUM_FORMAT=standard-verbose

.PHONY: test.envtest.pretty
test.envtest.pretty:
$(MAKE) _test.envtest GOTESTSUM_FORMAT=testname

.PHONY: _test.integration
_test.integration: webhook-certs-dir gotestsum
GOFLAGS=$(GOFLAGS) \
Expand Down
56 changes: 0 additions & 56 deletions controller/konnect/reconciler_generic_test.go

This file was deleted.

7 changes: 4 additions & 3 deletions pkg/utils/test/setup_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import (
)

const (
kubernetesConfigurationModuleName = "github.com/kong/kubernetes-configuration"
// KubernetesConfigurationModuleName is the name of the module where we import and install Kong configuration CRDs from.
KubernetesConfigurationModuleName = "github.com/kong/kubernetes-configuration"
)

func noOpClose() error {
Expand Down Expand Up @@ -227,9 +228,9 @@ func DeployCRDs(ctx context.Context, crdPath string, operatorClient *operatorcli

// CRDs for Kong configuration
// First extract version of `kong/kubernetes-configuration` module used
kongCRDVersion, err := ExtractModuleVersion(kubernetesConfigurationModuleName)
kongCRDVersion, err := ExtractModuleVersion(KubernetesConfigurationModuleName)
if err != nil {
return fmt.Errorf("failed to extract Kong CRDs (%s) module's version: %w", kubernetesConfigurationModuleName, err)
return fmt.Errorf("failed to extract Kong CRDs (%s) module's version: %w", KubernetesConfigurationModuleName, err)
}
// Then install CRDs from the module found in `$GOPATH`.
kongCRDPath := filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "kong",
Expand Down
146 changes: 146 additions & 0 deletions test/envtest/reconciler_setupwithmanager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package envtest

import (
"context"
"testing"
"time"

"github.com/samber/lo"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"

"github.com/kong/gateway-operator/controller/konnect"
"github.com/kong/gateway-operator/controller/konnect/conditions"
"github.com/kong/gateway-operator/controller/konnect/constraints"
"github.com/kong/gateway-operator/controller/konnect/ops"
"github.com/kong/gateway-operator/modules/manager/scheme"

configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1"
configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1"
configurationv1beta1 "github.com/kong/kubernetes-configuration/api/configuration/v1beta1"
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1"
)

func TestNewKonnectEntityReconciler(t *testing.T) {
testNewKonnectEntityReconciler(t, konnectv1alpha1.KonnectGatewayControlPlane{}, konnectGatewayControlPlaneTestCases)
testNewKonnectEntityReconciler(t, configurationv1alpha1.KongService{}, nil)
testNewKonnectEntityReconciler(t, configurationv1.KongConsumer{}, nil)
testNewKonnectEntityReconciler(t, configurationv1alpha1.KongRoute{}, nil)
testNewKonnectEntityReconciler(t, configurationv1beta1.KongConsumerGroup{}, nil)
testNewKonnectEntityReconciler(t, configurationv1alpha1.KongPluginBinding{}, nil)
}

const (
testNamespaceName = "test"
envTestWaitDuration = time.Second
envTestWaitTick = 20 * time.Millisecond
)

type konnectEntityReconcilerTestCase struct {
name string
objectOps func(ctx context.Context, t *testing.T, cl client.Client)
eventuallyPredicate func(ctx context.Context, t *testing.T, cl client.Client) bool
}

var konnectGatewayControlPlaneTestCases = []konnectEntityReconcilerTestCase{
{
name: "should resolve auth",
objectOps: func(ctx context.Context, t *testing.T, cl client.Client) {
auth := &konnectv1alpha1.KonnectAPIAuthConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "auth",
Namespace: testNamespaceName,
},
Spec: konnectv1alpha1.KonnectAPIAuthConfigurationSpec{
Type: konnectv1alpha1.KonnectAPIAuthTypeToken,
Token: "kpat_test",
},
}
require.NoError(t, cl.Create(ctx, auth))
cp := &konnectv1alpha1.KonnectGatewayControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "cp-1",
Namespace: testNamespaceName,
},
Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{
KonnectConfiguration: konnectv1alpha1.KonnectConfiguration{
APIAuthConfigurationRef: konnectv1alpha1.KonnectAPIAuthConfigurationRef{
Name: "auth",
},
},
},
}
require.NoError(t, cl.Create(ctx, cp))
},
eventuallyPredicate: func(ctx context.Context, t *testing.T, cl client.Client) bool {
cp := &konnectv1alpha1.KonnectGatewayControlPlane{}
err := cl.Get(ctx, k8stypes.NamespacedName{Namespace: testNamespaceName, Name: "cp-1"}, cp)
require.NoError(t, err)
// TODO: setup mock Konnect SDK and verify that Konnect CP is "Created".
return lo.ContainsBy(cp.Status.Conditions, func(condition metav1.Condition) bool {
return condition.Type == conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType && condition.Status == metav1.ConditionTrue
})
},
},
}

func testNewKonnectEntityReconciler[
T constraints.SupportedKonnectEntityType,
TEnt constraints.EntityType[T],
](
t *testing.T,
ent T,
testCases []konnectEntityReconcilerTestCase,
) {
t.Helper()

sdkFactory := &ops.MockSDKFactory{}

t.Run(ent.GetTypeName(), func(t *testing.T) {
s := scheme.Get()
cfg := Setup(t, s)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: s,
Metrics: metricsserver.Options{
// We do not need metrics server so we set BindAddress to 0 to disable it.
BindAddress: "0",
},
})
require.NoError(t, err)

cl := mgr.GetClient()
reconciler := konnect.NewKonnectEntityReconciler[T, TEnt](sdkFactory, false, cl)
require.NoError(t, reconciler.SetupWithManager(ctx, mgr))

err = cl.Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
})
require.NoError(t, err)

t.Logf("Starting manager for test case %s", t.Name())
go func() {
err := mgr.Start(ctx)
require.NoError(t, err)
}()

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.objectOps(ctx, t, cl)
require.Eventually(t, func() bool {
return tc.eventuallyPredicate(ctx, t, cl)
}, envTestWaitDuration, envTestWaitTick)
})
}
})
}
Loading

0 comments on commit 83369ea

Please sign in to comment.