From 887faf6cca154cd2e6d57c033898c2938be587fa Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 16 Dec 2025 09:58:42 -0800 Subject: [PATCH 1/8] WIP: speed up unit tests Signed-off-by: willdavsmith --- build/test.mk | 7 +- pkg/components/testhost/clients.go | 67 ++++++++++++------- .../reconciler/deployment_reconciler_test.go | 22 +----- .../deploymentresource_reconciler_test.go | 11 +-- .../deploymenttemplate_reconciler_test.go | 14 +--- pkg/controller/reconciler/shared_test.go | 14 +--- pkg/ucp/integrationtests/radius/proxy_test.go | 2 +- .../resourceproviders_test.go | 10 +++ .../resourceproviders/resourcetypes_test.go | 6 ++ test/ucp/kubeenv/testenv.go | 10 ++- test/ucp/queuetest/shared.go | 2 +- 11 files changed, 76 insertions(+), 89 deletions(-) diff --git a/build/test.mk b/build/test.mk index 0a68676334..da2e1d3d9e 100644 --- a/build/test.mk +++ b/build/test.mk @@ -35,6 +35,7 @@ REL_VERSION ?=latest DOCKER_REGISTRY ?=ghcr.io/radius-project/dev ENVTEST_ASSETS_DIR=$(shell pwd)/bin K8S_VERSION=1.30.* +ENVTEST_ARCH ?= $(shell go env GOHOSTARCH) ENV_SETUP=$(GOBIN)/setup-envtest$(BINARY_EXT) # Use gotestsum if available, otherwise use go test. We want to enable testing with just 'make test' @@ -55,8 +56,8 @@ GOTEST_TOOL ?= gotestsum $(GOTESTSUM_OPTS) -- endif .PHONY: test -test: test-get-envtools test-helm ## Runs unit tests, excluding kubernetes controller tests - KUBEBUILDER_ASSETS="$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch amd64)" CGO_ENABLED=1 $(GOTEST_TOOL) -v ./pkg/... $(GOTEST_OPTS) +test: test-get-envtools test-helm ## Runs Go tests + KUBEBUILDER_ASSETS="$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch $(ENVTEST_ARCH))" CGO_ENABLED=1 $(GOTEST_TOOL) -v ./pkg/... $(GOTEST_OPTS) .PHONY: test-get-envtools test-get-envtools: @@ -64,7 +65,7 @@ test-get-envtools: $(call go-install-tool,$(ENV_SETUP),sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.20) @echo "$(ARROW) Instructions:" @echo "$(ARROW) Set environment variable KUBEBUILDER_ASSETS for tests." - @echo "$(ARROW) KUBEBUILDER_ASSETS=\"$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch amd64)\"" + @echo "$(ARROW) KUBEBUILDER_ASSETS=\"$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch $(ENVTEST_ARCH))\"" .PHONY: test-validate-cli test-validate-cli: ## Run cli integration tests diff --git a/pkg/components/testhost/clients.go b/pkg/components/testhost/clients.go index 3f534c6d96..02486b32be 100644 --- a/pkg/components/testhost/clients.go +++ b/pkg/components/testhost/clients.go @@ -72,6 +72,10 @@ func (th *TestHost) MakeTypedRequest(method string, pathAndQuery string, body an // MakeRequest sends a request to the server. func (th *TestHost) MakeRequest(method string, pathAndQuery string, body []byte) *TestResponse { + return th.makeRequest(method, pathAndQuery, body, true) +} + +func (th *TestHost) makeRequest(method string, pathAndQuery string, body []byte, logResponse bool) *TestResponse { // Prepend the base path if this is a relative URL. requestUrl := pathAndQuery parsed, err := url.Parse(pathAndQuery) @@ -98,15 +102,17 @@ func (th *TestHost) MakeRequest(method string, pathAndQuery string, body []byte) response.Body = io.NopCloser(responseBuffer) - // Pretty-print response for logs. - if len(responseBuffer.Bytes()) > 0 { - var data any - err = json.Unmarshal(responseBuffer.Bytes(), &data) - require.NoError(th.t, err, "unmarshalling response failed") + if logResponse { + // Pretty-print response for logs. + if len(responseBuffer.Bytes()) > 0 { + var data any + err = json.Unmarshal(responseBuffer.Bytes(), &data) + require.NoError(th.t, err, "unmarshalling response failed") - text, err := json.MarshalIndent(&data, "", " ") - require.NoError(th.t, err, "marshalling response failed") - th.t.Log("Response Body: \n" + string(text)) + text, err := json.MarshalIndent(&data, "", " ") + require.NoError(th.t, err, "marshalling response failed") + th.t.Log("Response Body: \n" + string(text)) + } } var errorResponse *v1.ErrorResponse @@ -223,27 +229,38 @@ func (tr *TestResponse) WaitForOperationComplete(timeout *time.Duration) *TestRe timeout = &x } - timer := time.After(*timeout) - poller := time.NewTicker(1 * time.Second) - defer poller.Stop() + operationURL := tr.Raw.Header.Get("Azure-AsyncOperation") + require.NotEmpty(tr.t, operationURL, "expected Azure-AsyncOperation header") + + deadline := time.Now().Add(*timeout) + + pollInterval := 50 * time.Millisecond + maxPollInterval := 1 * time.Second + for { - select { - case <-timer: + if time.Now().After(deadline) { tr.t.Fatalf("timed out waiting for operation to complete") return nil // unreachable - case <-poller.C: - // The Location header should give us the operation status URL. - response := tr.host.MakeRequest(http.MethodGet, tr.Raw.Header.Get("Azure-AsyncOperation"), nil) - - // To determine if the response is terminal we need to read the provisioning state field. - operationStatus := v1.AsyncOperationStatus{} - response.ReadAs(&operationStatus) - if operationStatus.Status.IsTerminal() { - // Response is terminal. - return response - } + } + + // Poll the async-operation status endpoint. + // + // This uses a "quiet" request path to avoid flooding `-v` output when polling. + response := tr.host.makeRequest(http.MethodGet, operationURL, nil, false) - continue + // To determine if the response is terminal we need to read the provisioning state field. + operationStatus := v1.AsyncOperationStatus{} + response.ReadAs(&operationStatus) + if operationStatus.Status.IsTerminal() { + return response + } + + time.Sleep(pollInterval) + if pollInterval < maxPollInterval { + pollInterval *= 2 + if pollInterval > maxPollInterval { + pollInterval = maxPollInterval + } } } } diff --git a/pkg/controller/reconciler/deployment_reconciler_test.go b/pkg/controller/reconciler/deployment_reconciler_test.go index 49f02207e5..d7a41cf960 100644 --- a/pkg/controller/reconciler/deployment_reconciler_test.go +++ b/pkg/controller/reconciler/deployment_reconciler_test.go @@ -42,7 +42,7 @@ import ( const ( deploymentTestWaitDuration = time.Second * 10 - deploymentTestWaitInterval = time.Second * 1 + deploymentTestWaitInterval = 200 * time.Millisecond deploymentTestControllerDelayInterval = time.Millisecond * 100 ) @@ -546,10 +546,8 @@ func Test_DeploymentReconciler_RadiusDisabled_ThenRadiusDisabled(t *testing.T) { func waitForStateWaiting(t *testing.T, client client.Client, name types.NamespacedName) *deploymentAnnotations { ctx := testcontext.New(t) - logger := t var annotations deploymentAnnotations require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Deployment: %+v", name) current := &appsv1.Deployment{} err := client.Get(ctx, name, current) require.NoError(t, err) @@ -557,7 +555,6 @@ func waitForStateWaiting(t *testing.T, client client.Client, name types.Namespac annotations, err = readAnnotations(current) require.NoError(t, err) assert.NotNil(t, annotations) - logger.Logf("Annotations.Status: %+v", annotations.Status) if assert.NotNil(t, annotations.Status) && assert.Equal(t, deploymentPhraseWaiting, annotations.Status.Phrase) { assert.Empty(t, annotations.Status.Operation) @@ -570,10 +567,8 @@ func waitForStateWaiting(t *testing.T, client client.Client, name types.Namespac func waitForStateUpdating(t *testing.T, client client.Client, name types.NamespacedName) *deploymentAnnotations { ctx := testcontext.New(t) - logger := t var annotations deploymentAnnotations require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Deployment: %+v", name) current := &appsv1.Deployment{} err := client.Get(ctx, name, current) require.NoError(t, err) @@ -581,7 +576,6 @@ func waitForStateUpdating(t *testing.T, client client.Client, name types.Namespa annotations, err = readAnnotations(current) require.NoError(t, err) assert.NotNil(t, annotations) - logger.Logf("Annotations.Status: %+v", annotations.Status) if assert.NotNil(t, annotations.Status) && assert.Equal(t, deploymentPhraseUpdating, annotations.Status.Phrase) { assert.NotEmpty(t, annotations.Status.Operation) @@ -594,10 +588,8 @@ func waitForStateUpdating(t *testing.T, client client.Client, name types.Namespa func waitForStateReady(t *testing.T, client client.Client, name types.NamespacedName) *deploymentAnnotations { ctx := testcontext.New(t) - logger := t var annotations deploymentAnnotations require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Deployment: %+v", name) current := &appsv1.Deployment{} err := client.Get(ctx, name, current) require.NoError(t, err) @@ -605,7 +597,6 @@ func waitForStateReady(t *testing.T, client client.Client, name types.Namespaced annotations, err = readAnnotations(current) require.NoError(t, err) assert.NotNil(t, annotations) - logger.Logf("Annotations.Status: %+v", annotations.Status) if assert.NotNil(t, annotations.Status) && assert.Equal(t, deploymentPhraseReady, annotations.Status.Phrase) { assert.Empty(t, annotations.Status.Operation) @@ -618,10 +609,8 @@ func waitForStateReady(t *testing.T, client client.Client, name types.Namespaced func waitForStateDeleting(t *testing.T, client client.Client, name types.NamespacedName) *deploymentAnnotations { ctx := testcontext.New(t) - logger := t var annotations deploymentAnnotations require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Deployment: %+v", name) current := &appsv1.Deployment{} err := client.Get(ctx, name, current) require.NoError(t, err) @@ -629,7 +618,6 @@ func waitForStateDeleting(t *testing.T, client client.Client, name types.Namespa annotations, err = readAnnotations(current) require.NoError(t, err) assert.NotNil(t, annotations) - logger.Logf("Annotations.Status: %+v", annotations.Status) if assert.NotNil(t, annotations.Status) && assert.Equal(t, deploymentPhraseDeleting, annotations.Status.Phrase) { assert.NotEmpty(t, annotations.Status.Operation) @@ -651,11 +639,8 @@ type expectedEvent struct { // We can have multiple events as the result of the List function but we are only interested in the expected event. func waitForEvent(t *testing.T, client client.Client, event expectedEvent) { ctx := testcontext.New(t) - logger := t require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Log("Fetching Events") - events := &corev1.EventList{} err := client.List(ctx, events) require.NoError(t, err) @@ -675,15 +660,12 @@ func waitForEvent(t *testing.T, client client.Client, event expectedEvent) { func waitForRadiusContainerDeleted(t *testing.T, client client.Client, name types.NamespacedName) *deploymentAnnotations { ctx := testcontext.New(t) - logger := t var annotations *deploymentAnnotations require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Deployment: %+v", name) current := &appsv1.Deployment{} err := client.Get(ctx, name, current) require.NoError(t, err) - logger.Logf("Annotations: %+v", current.Annotations) assert.NotContains(t, current.Annotations, AnnotationRadiusStatus) assert.NotContains(t, current.Annotations, AnnotationRadiusConfigurationHash) }, deploymentTestWaitDuration, deploymentTestWaitInterval, "waiting for state to be Deleting") @@ -694,9 +676,7 @@ func waitForRadiusContainerDeleted(t *testing.T, client client.Client, name type func waitForDeploymentDeleted(t *testing.T, client client.Client, name types.NamespacedName) { ctx := testcontext.New(t) - logger := t require.Eventuallyf(t, func() bool { - logger.Logf("Fetching Deployment: %+v", name) err := client.Get(ctx, name, &appsv1.Deployment{}) return apierrors.IsNotFound(err) }, deploymentTestWaitDuration, deploymentTestWaitInterval, "waiting for deployment to be deleted") diff --git a/pkg/controller/reconciler/deploymentresource_reconciler_test.go b/pkg/controller/reconciler/deploymentresource_reconciler_test.go index 3131a76b19..f45e3ceec3 100644 --- a/pkg/controller/reconciler/deploymentresource_reconciler_test.go +++ b/pkg/controller/reconciler/deploymentresource_reconciler_test.go @@ -38,7 +38,7 @@ import ( const ( DeploymentResourceTestWaitDuration = time.Second * 10 - DeploymentResourceTestWaitInterval = time.Second * 1 + DeploymentResourceTestWaitInterval = 200 * time.Millisecond DeploymentResourceTestControllerDelayInterval = time.Millisecond * 100 TestDeploymentResourceNamespace = "deploymentresource-basic" @@ -125,16 +125,13 @@ func Test_DeploymentResourceReconciler_Basic(t *testing.T) { func waitForDeploymentResourceStateReady(t *testing.T, client k8sClient.Client, name types.NamespacedName) *radappiov1alpha3.DeploymentResourceStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.DeploymentResourceStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching DeploymentResource: %+v", name) current := &radappiov1alpha3.DeploymentResource{} err := client.Get(ctx, name, current) require.NoError(t, err) status = ¤t.Status - logger.Logf("DeploymentResource.Status: %+v", current.Status) if assert.Equal(t, radappiov1alpha3.DeploymentResourcePhraseReady, current.Status.Phrase) { assert.Empty(t, current.Status.Operation) } @@ -146,16 +143,13 @@ func waitForDeploymentResourceStateReady(t *testing.T, client k8sClient.Client, func waitForDeploymentResourceStateDeleting(t *testing.T, client k8sClient.Client, name types.NamespacedName, oldOperation *radappiov1alpha3.ResourceOperation) *radappiov1alpha3.DeploymentResourceStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.DeploymentResourceStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching DeploymentResource: %+v", name) current := &radappiov1alpha3.DeploymentResource{} err := client.Get(ctx, name, current) assert.NoError(t, err) status = ¤t.Status - logger.Logf("DeploymentResource.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.DeploymentResourcePhraseDeleting, current.Status.Phrase) { @@ -170,16 +164,13 @@ func waitForDeploymentResourceStateDeleting(t *testing.T, client k8sClient.Clien func waitForDeploymentResourceDeleted(t *testing.T, client k8sClient.Client, name types.NamespacedName) { ctx := testcontext.New(t) - logger := t require.Eventuallyf(t, func() bool { - logger.Logf("Fetching DeploymentResource: %+v", name) current := &radappiov1alpha3.DeploymentResource{} err := client.Get(ctx, name, current) if apierrors.IsNotFound(err) { return true } - logger.Logf("DeploymentResource.Status: %+v", current.Status) return false }, DeploymentResourceTestWaitDuration, DeploymentResourceTestWaitInterval, "DeploymentResource still exists") diff --git a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go index 3cbfcb0404..a85ba406bc 100644 --- a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go +++ b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go @@ -42,7 +42,7 @@ import ( const ( deploymentTemplateTestWaitDuration = time.Second * 10 - deploymentTemplateTestWaitInterval = time.Second * 1 + deploymentTemplateTestWaitInterval = 200 * time.Millisecond deploymentTemplateTestControllerDelayInterval = time.Millisecond * 100 ) @@ -770,10 +770,8 @@ func Test_DeploymentTemplateReconciler_OutputResources(t *testing.T) { func waitForDeploymentTemplateStateUpdating(t *testing.T, client k8sclient.Client, name types.NamespacedName, oldOperation *radappiov1alpha3.ResourceOperation) *radappiov1alpha3.DeploymentTemplateStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.DeploymentTemplateStatus{} require.EventuallyWithT(t, func(t *assert.CollectT) { - logger.Logf("Fetching DeploymentTemplate: %+v", name) current := &radappiov1alpha3.DeploymentTemplate{ Status: radappiov1alpha3.DeploymentTemplateStatus{ Phrase: radappiov1alpha3.DeploymentTemplatePhrase(radappiov1alpha3.DeploymentResourcePhraseDeleting), @@ -783,7 +781,6 @@ func waitForDeploymentTemplateStateUpdating(t *testing.T, client k8sclient.Clien require.NoError(t, err) status = ¤t.Status - logger.Logf("DeploymentTemplate.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.DeploymentTemplatePhraseUpdating, current.Status.Phrase) { @@ -799,16 +796,13 @@ func waitForDeploymentTemplateStateUpdating(t *testing.T, client k8sclient.Clien func waitForDeploymentTemplateStateReady(t *testing.T, client k8sclient.Client, name types.NamespacedName) *radappiov1alpha3.DeploymentTemplateStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.DeploymentTemplateStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching DeploymentTemplate: %+v", name) current := &radappiov1alpha3.DeploymentTemplate{} err := client.Get(ctx, name, current) require.NoError(t, err) status = ¤t.Status - logger.Logf("DeploymentTemplate.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.DeploymentTemplatePhraseReady, current.Status.Phrase) { @@ -822,16 +816,13 @@ func waitForDeploymentTemplateStateReady(t *testing.T, client k8sclient.Client, func waitForDeploymentTemplateStateDeleting(t *testing.T, client k8sclient.Client, name types.NamespacedName) *radappiov1alpha3.DeploymentTemplateStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.DeploymentTemplateStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching DeploymentTemplate: %+v", name) current := &radappiov1alpha3.DeploymentTemplate{} err := client.Get(ctx, name, current) assert.NoError(t, err) status = ¤t.Status - logger.Logf("DeploymentTemplate.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") assert.Equal(t, radappiov1alpha3.DeploymentTemplatePhraseDeleting, current.Status.Phrase) @@ -843,16 +834,13 @@ func waitForDeploymentTemplateStateDeleting(t *testing.T, client k8sclient.Clien func waitForDeploymentTemplateStateDeleted(t *testing.T, client k8sclient.Client, name types.NamespacedName) { ctx := testcontext.New(t) - logger := t require.Eventuallyf(t, func() bool { - logger.Logf("Fetching DeploymentTemplate: %+v", name) current := &radappiov1alpha3.DeploymentTemplate{} err := client.Get(ctx, name, current) if apierrors.IsNotFound(err) { return true } - logger.Logf("DeploymentTemplate.Status: %+v", current.Status) return false }, deploymentTemplateTestWaitDuration, deploymentTemplateTestWaitInterval, "DeploymentTemplate still exists") diff --git a/pkg/controller/reconciler/shared_test.go b/pkg/controller/reconciler/shared_test.go index 6990f7adbe..402eee789d 100644 --- a/pkg/controller/reconciler/shared_test.go +++ b/pkg/controller/reconciler/shared_test.go @@ -39,7 +39,7 @@ import ( const ( recipeTestWaitDuration = time.Second * 10 - recipeTestWaitInterval = time.Second * 1 + recipeTestWaitInterval = 200 * time.Millisecond recipeTestControllerDelayInterval = time.Millisecond * 100 ) @@ -69,16 +69,13 @@ func makeRecipe(name types.NamespacedName, resourceType string) *radappiov1alpha func waitForRecipeStateUpdating(t *testing.T, client client.Client, name types.NamespacedName, oldOperation *radappiov1alpha3.ResourceOperation) *radappiov1alpha3.RecipeStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.RecipeStatus{} require.EventuallyWithT(t, func(t *assert.CollectT) { - logger.Logf("Fetching Recipe: %+v", name) current := &radappiov1alpha3.Recipe{} err := client.Get(ctx, name, current) require.NoError(t, err) status = ¤t.Status - logger.Logf("Recipe.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.PhraseUpdating, current.Status.Phrase) { @@ -94,16 +91,13 @@ func waitForRecipeStateUpdating(t *testing.T, client client.Client, name types.N func waitForRecipeStateReady(t *testing.T, client client.Client, name types.NamespacedName) *radappiov1alpha3.RecipeStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.RecipeStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Recipe: %+v", name) current := &radappiov1alpha3.Recipe{} err := client.Get(ctx, name, current) require.NoError(t, err) status = ¤t.Status - logger.Logf("Recipe.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.PhraseReady, current.Status.Phrase) { @@ -117,16 +111,13 @@ func waitForRecipeStateReady(t *testing.T, client client.Client, name types.Name func waitForRecipeStateDeleting(t *testing.T, client client.Client, name types.NamespacedName, oldOperation *radappiov1alpha3.ResourceOperation) *radappiov1alpha3.RecipeStatus { ctx := testcontext.New(t) - logger := t status := &radappiov1alpha3.RecipeStatus{} require.EventuallyWithTf(t, func(t *assert.CollectT) { - logger.Logf("Fetching Recipe: %+v", name) current := &radappiov1alpha3.Recipe{} err := client.Get(ctx, name, current) assert.NoError(t, err) status = ¤t.Status - logger.Logf("Recipe.Status: %+v", current.Status) assert.Equal(t, status.ObservedGeneration, current.Generation, "Status is not updated") if assert.Equal(t, radappiov1alpha3.PhraseDeleting, current.Status.Phrase) { @@ -141,16 +132,13 @@ func waitForRecipeStateDeleting(t *testing.T, client client.Client, name types.N func waitForRecipeDeleted(t *testing.T, client client.Client, name types.NamespacedName) { ctx := testcontext.New(t) - logger := t require.Eventuallyf(t, func() bool { - logger.Logf("Fetching Recipe: %+v", name) current := &radappiov1alpha3.Recipe{} err := client.Get(ctx, name, current) if apierrors.IsNotFound(err) { return true } - logger.Logf("Recipe.Status: %+v", current.Status) return false }, recipeTestWaitDuration, recipeTestWaitInterval, "recipe still exists") diff --git a/pkg/ucp/integrationtests/radius/proxy_test.go b/pkg/ucp/integrationtests/radius/proxy_test.go index 5c93754d61..9e494daedf 100644 --- a/pkg/ucp/integrationtests/radius/proxy_test.go +++ b/pkg/ucp/integrationtests/radius/proxy_test.go @@ -46,7 +46,7 @@ const ( locationID = testResourceProviderID + "/locations/global" assertTimeout = time.Second * 10 - assertRetry = time.Second * 2 + assertRetry = 200 * time.Millisecond ) func Test_RadiusPlane_Proxy_ResourceGroupDoesNotExist(t *testing.T) { diff --git a/pkg/ucp/integrationtests/resourceproviders/resourceproviders_test.go b/pkg/ucp/integrationtests/resourceproviders/resourceproviders_test.go index a44b6fcd1d..58b70cedb6 100644 --- a/pkg/ucp/integrationtests/resourceproviders/resourceproviders_test.go +++ b/pkg/ucp/integrationtests/resourceproviders/resourceproviders_test.go @@ -88,11 +88,21 @@ func Test_ResourceProvider_CascadingDelete(t *testing.T) { deleteResourceProvider(server) // The resource type should be gone now. + require.Eventually(t, func() bool { + response := server.MakeRequest(http.MethodGet, resourceTypeURL, nil) + return response.Raw.StatusCode == http.StatusNotFound + }, 10*time.Second, 100*time.Millisecond) + response := server.MakeRequest(http.MethodGet, resourceTypeURL, nil) response.EqualsErrorCode(404, "NotFound") require.Equal(t, resourceTypeID, response.Error.Error.Target) // The location should be gone now. + require.Eventually(t, func() bool { + response := server.MakeRequest(http.MethodGet, locationURL, nil) + return response.Raw.StatusCode == http.StatusNotFound + }, 10*time.Second, 100*time.Millisecond) + response = server.MakeRequest(http.MethodGet, locationURL, nil) response.EqualsErrorCode(404, "NotFound") require.Equal(t, locationID, response.Error.Error.Target) diff --git a/pkg/ucp/integrationtests/resourceproviders/resourcetypes_test.go b/pkg/ucp/integrationtests/resourceproviders/resourcetypes_test.go index 6db34e64ce..af0145349d 100644 --- a/pkg/ucp/integrationtests/resourceproviders/resourcetypes_test.go +++ b/pkg/ucp/integrationtests/resourceproviders/resourcetypes_test.go @@ -19,6 +19,7 @@ package resourceproviders import ( "net/http" "testing" + "time" "github.com/radius-project/radius/pkg/ucp/testhost" "github.com/stretchr/testify/require" @@ -81,6 +82,11 @@ func Test_ResourceType_CascadingDelete(t *testing.T) { deleteResourceType(server) // The API version should be gone now. + require.Eventually(t, func() bool { + response := server.MakeRequest(http.MethodGet, apiVersionURL, nil) + return response.Raw.StatusCode == http.StatusNotFound + }, 10*time.Second, 100*time.Millisecond) + response := server.MakeRequest(http.MethodGet, apiVersionURL, nil) response.EqualsErrorCode(404, "NotFound") require.Equal(t, apiVersionID, response.Error.Error.Target) diff --git a/test/ucp/kubeenv/testenv.go b/test/ucp/kubeenv/testenv.go index 04ad83c807..c1b9f8aa5a 100644 --- a/test/ucp/kubeenv/testenv.go +++ b/test/ucp/kubeenv/testenv.go @@ -22,6 +22,8 @@ import ( "fmt" "os" "os/exec" + goruntime "runtime" + "strings" ucpv1alpha1 "github.com/radius-project/radius/pkg/components/database/apiserverstore/api/ucp.dev/v1alpha1" v1 "k8s.io/api/core/v1" @@ -76,14 +78,18 @@ func getKubeAssetsDir() (string, error) { // We require one or more versions of the test assets to be installed already. This // will use whatever's latest of the installed versions. - cmd := exec.Command("setup-envtest", "use", "-i", "-p", "path", "--arch", "amd64") + cmd := exec.Command("setup-envtest", "use", "-i", "-p", "path", "--arch", goruntime.GOARCH) var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return "", fmt.Errorf("failed to call setup-envtest to find path: %w", err) } else { - return out.String(), err + assetsPath := strings.TrimSpace(out.String()) + if assetsPath == "" { + return "", fmt.Errorf("failed to call setup-envtest to find path: empty output") + } + return assetsPath, nil } } diff --git a/test/ucp/queuetest/shared.go b/test/ucp/queuetest/shared.go index 2bd0214871..6ee70d7441 100644 --- a/test/ucp/queuetest/shared.go +++ b/test/ucp/queuetest/shared.go @@ -30,7 +30,7 @@ import ( ) const ( - TestMessageLockTime = time.Duration(1) * time.Second + TestMessageLockTime = 200 * time.Millisecond pollingInterval = time.Duration(100) * time.Millisecond From 4a17adc472b26853ed4a1d6fb156f060947f10dd Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 14:14:28 -0800 Subject: [PATCH 2/8] PR Signed-off-by: willdavsmith --- .../reconciler/deploymentresource_reconciler_test.go | 6 +----- .../reconciler/deploymenttemplate_reconciler_test.go | 6 +----- pkg/controller/reconciler/shared_test.go | 6 +----- pkg/ucp/integrationtests/radius/proxy_test.go | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/pkg/controller/reconciler/deploymentresource_reconciler_test.go b/pkg/controller/reconciler/deploymentresource_reconciler_test.go index f45e3ceec3..15a98df05d 100644 --- a/pkg/controller/reconciler/deploymentresource_reconciler_test.go +++ b/pkg/controller/reconciler/deploymentresource_reconciler_test.go @@ -167,11 +167,7 @@ func waitForDeploymentResourceDeleted(t *testing.T, client k8sClient.Client, nam require.Eventuallyf(t, func() bool { current := &radappiov1alpha3.DeploymentResource{} err := client.Get(ctx, name, current) - if apierrors.IsNotFound(err) { - return true - } - - return false + return apierrors.IsNotFound(err) }, DeploymentResourceTestWaitDuration, DeploymentResourceTestWaitInterval, "DeploymentResource still exists") } diff --git a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go index a85ba406bc..1f67589f22 100644 --- a/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go +++ b/pkg/controller/reconciler/deploymenttemplate_reconciler_test.go @@ -837,11 +837,7 @@ func waitForDeploymentTemplateStateDeleted(t *testing.T, client k8sclient.Client require.Eventuallyf(t, func() bool { current := &radappiov1alpha3.DeploymentTemplate{} err := client.Get(ctx, name, current) - if apierrors.IsNotFound(err) { - return true - } - - return false + return apierrors.IsNotFound(err) }, deploymentTemplateTestWaitDuration, deploymentTemplateTestWaitInterval, "DeploymentTemplate still exists") } diff --git a/pkg/controller/reconciler/shared_test.go b/pkg/controller/reconciler/shared_test.go index 402eee789d..395acbf904 100644 --- a/pkg/controller/reconciler/shared_test.go +++ b/pkg/controller/reconciler/shared_test.go @@ -135,11 +135,7 @@ func waitForRecipeDeleted(t *testing.T, client client.Client, name types.Namespa require.Eventuallyf(t, func() bool { current := &radappiov1alpha3.Recipe{} err := client.Get(ctx, name, current) - if apierrors.IsNotFound(err) { - return true - } - - return false + return apierrors.IsNotFound(err) }, recipeTestWaitDuration, recipeTestWaitInterval, "recipe still exists") } diff --git a/pkg/ucp/integrationtests/radius/proxy_test.go b/pkg/ucp/integrationtests/radius/proxy_test.go index 9e494daedf..5c93754d61 100644 --- a/pkg/ucp/integrationtests/radius/proxy_test.go +++ b/pkg/ucp/integrationtests/radius/proxy_test.go @@ -46,7 +46,7 @@ const ( locationID = testResourceProviderID + "/locations/global" assertTimeout = time.Second * 10 - assertRetry = 200 * time.Millisecond + assertRetry = time.Second * 2 ) func Test_RadiusPlane_Proxy_ResourceGroupDoesNotExist(t *testing.T) { From d8bdb63026df867b6c60607a866a99bce81e58d7 Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 15:43:44 -0800 Subject: [PATCH 3/8] Test Signed-off-by: willdavsmith --- pkg/components/testhost/clients.go | 67 +++++++++++------------------- 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/pkg/components/testhost/clients.go b/pkg/components/testhost/clients.go index 02486b32be..3f534c6d96 100644 --- a/pkg/components/testhost/clients.go +++ b/pkg/components/testhost/clients.go @@ -72,10 +72,6 @@ func (th *TestHost) MakeTypedRequest(method string, pathAndQuery string, body an // MakeRequest sends a request to the server. func (th *TestHost) MakeRequest(method string, pathAndQuery string, body []byte) *TestResponse { - return th.makeRequest(method, pathAndQuery, body, true) -} - -func (th *TestHost) makeRequest(method string, pathAndQuery string, body []byte, logResponse bool) *TestResponse { // Prepend the base path if this is a relative URL. requestUrl := pathAndQuery parsed, err := url.Parse(pathAndQuery) @@ -102,17 +98,15 @@ func (th *TestHost) makeRequest(method string, pathAndQuery string, body []byte, response.Body = io.NopCloser(responseBuffer) - if logResponse { - // Pretty-print response for logs. - if len(responseBuffer.Bytes()) > 0 { - var data any - err = json.Unmarshal(responseBuffer.Bytes(), &data) - require.NoError(th.t, err, "unmarshalling response failed") + // Pretty-print response for logs. + if len(responseBuffer.Bytes()) > 0 { + var data any + err = json.Unmarshal(responseBuffer.Bytes(), &data) + require.NoError(th.t, err, "unmarshalling response failed") - text, err := json.MarshalIndent(&data, "", " ") - require.NoError(th.t, err, "marshalling response failed") - th.t.Log("Response Body: \n" + string(text)) - } + text, err := json.MarshalIndent(&data, "", " ") + require.NoError(th.t, err, "marshalling response failed") + th.t.Log("Response Body: \n" + string(text)) } var errorResponse *v1.ErrorResponse @@ -229,38 +223,27 @@ func (tr *TestResponse) WaitForOperationComplete(timeout *time.Duration) *TestRe timeout = &x } - operationURL := tr.Raw.Header.Get("Azure-AsyncOperation") - require.NotEmpty(tr.t, operationURL, "expected Azure-AsyncOperation header") - - deadline := time.Now().Add(*timeout) - - pollInterval := 50 * time.Millisecond - maxPollInterval := 1 * time.Second - + timer := time.After(*timeout) + poller := time.NewTicker(1 * time.Second) + defer poller.Stop() for { - if time.Now().After(deadline) { + select { + case <-timer: tr.t.Fatalf("timed out waiting for operation to complete") return nil // unreachable - } - - // Poll the async-operation status endpoint. - // - // This uses a "quiet" request path to avoid flooding `-v` output when polling. - response := tr.host.makeRequest(http.MethodGet, operationURL, nil, false) - - // To determine if the response is terminal we need to read the provisioning state field. - operationStatus := v1.AsyncOperationStatus{} - response.ReadAs(&operationStatus) - if operationStatus.Status.IsTerminal() { - return response - } - - time.Sleep(pollInterval) - if pollInterval < maxPollInterval { - pollInterval *= 2 - if pollInterval > maxPollInterval { - pollInterval = maxPollInterval + case <-poller.C: + // The Location header should give us the operation status URL. + response := tr.host.MakeRequest(http.MethodGet, tr.Raw.Header.Get("Azure-AsyncOperation"), nil) + + // To determine if the response is terminal we need to read the provisioning state field. + operationStatus := v1.AsyncOperationStatus{} + response.ReadAs(&operationStatus) + if operationStatus.Status.IsTerminal() { + // Response is terminal. + return response } + + continue } } } From 14eb59907bcf06f2c3e20f81f83fc6c6ba8a6d1d Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 16:08:50 -0800 Subject: [PATCH 4/8] Fix Signed-off-by: willdavsmith --- test/ucp/kubeenv/testenv.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/ucp/kubeenv/testenv.go b/test/ucp/kubeenv/testenv.go index c1b9f8aa5a..5430f6b076 100644 --- a/test/ucp/kubeenv/testenv.go +++ b/test/ucp/kubeenv/testenv.go @@ -22,7 +22,6 @@ import ( "fmt" "os" "os/exec" - goruntime "runtime" "strings" ucpv1alpha1 "github.com/radius-project/radius/pkg/components/database/apiserverstore/api/ucp.dev/v1alpha1" @@ -78,7 +77,7 @@ func getKubeAssetsDir() (string, error) { // We require one or more versions of the test assets to be installed already. This // will use whatever's latest of the installed versions. - cmd := exec.Command("setup-envtest", "use", "-i", "-p", "path", "--arch", goruntime.GOARCH) + cmd := exec.Command("setup-envtest", "use", "-i", "-p", "path", "--arch", "amd64") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() From 88e0172ebf3844d9ae00dea77f79cf33898d3287 Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 17:39:37 -0800 Subject: [PATCH 5/8] temp: disable gitops test Signed-off-by: willdavsmith --- test/functional-portable/kubernetes/noncloud/flux_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/functional-portable/kubernetes/noncloud/flux_test.go b/test/functional-portable/kubernetes/noncloud/flux_test.go index 14fec527c7..dc6103dcf8 100644 --- a/test/functional-portable/kubernetes/noncloud/flux_test.go +++ b/test/functional-portable/kubernetes/noncloud/flux_test.go @@ -69,6 +69,7 @@ const ( ) func Test_Flux_Basic(t *testing.T) { + t.Skip("skipping flux/gitops tests") testName := "flux-basic" steps := []GitOpsTestStep{ { @@ -95,6 +96,7 @@ func Test_Flux_Basic(t *testing.T) { } func Test_Flux_Complex(t *testing.T) { + t.Skip("skipping flux/gitops tests") testName := "flux-complex" steps := []GitOpsTestStep{ { From 0661857e7d903c66809d3ace4de3caecbbf02389 Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 19:08:17 -0800 Subject: [PATCH 6/8] Re-enable flux tests Signed-off-by: willdavsmith --- test/functional-portable/kubernetes/noncloud/flux_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/functional-portable/kubernetes/noncloud/flux_test.go b/test/functional-portable/kubernetes/noncloud/flux_test.go index dc6103dcf8..14fec527c7 100644 --- a/test/functional-portable/kubernetes/noncloud/flux_test.go +++ b/test/functional-portable/kubernetes/noncloud/flux_test.go @@ -69,7 +69,6 @@ const ( ) func Test_Flux_Basic(t *testing.T) { - t.Skip("skipping flux/gitops tests") testName := "flux-basic" steps := []GitOpsTestStep{ { @@ -96,7 +95,6 @@ func Test_Flux_Basic(t *testing.T) { } func Test_Flux_Complex(t *testing.T) { - t.Skip("skipping flux/gitops tests") testName := "flux-complex" steps := []GitOpsTestStep{ { From 5a06fead65a0b27299101d4cf0d2a2f445aa4feb Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Tue, 6 Jan 2026 19:10:32 -0800 Subject: [PATCH 7/8] Disable terraformrecipe tests Signed-off-by: willdavsmith --- .../corerp/noncloud/resources/recipe_terraform_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go index d0a3ec6132..01d635b349 100644 --- a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go +++ b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go @@ -55,6 +55,7 @@ var ( // - Create an extender resource using a Terraform recipe that deploys Redis on Kubernetes. // - The recipe deployment creates a Kubernetes deployment and a Kubernetes service. func Test_TerraformRecipe_KubernetesRedis(t *testing.T) { + t.Skip() template := "testdata/corerp-resources-terraform-redis.bicep" name := "corerp-resources-terraform-redis" appName := "corerp-resources-terraform-redis-app" @@ -234,6 +235,7 @@ func Test_TerraformRecipe_KubernetesPostgres(t *testing.T) { } func Test_TerraformRecipe_Context(t *testing.T) { + t.Skip() template := "testdata/corerp-resources-terraform-context.bicep" name := "corerp-resources-terraform-context" appNamespace := "corerp-resources-terraform-context-app" From 7488eeb30ddc40b1e66bc5d801ef20930a33dafc Mon Sep 17 00:00:00 2001 From: willdavsmith Date: Mon, 26 Jan 2026 11:16:05 -0800 Subject: [PATCH 8/8] Un-disable tests Signed-off-by: willdavsmith --- .../corerp/noncloud/resources/recipe_terraform_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go index 01d635b349..d0a3ec6132 100644 --- a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go +++ b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go @@ -55,7 +55,6 @@ var ( // - Create an extender resource using a Terraform recipe that deploys Redis on Kubernetes. // - The recipe deployment creates a Kubernetes deployment and a Kubernetes service. func Test_TerraformRecipe_KubernetesRedis(t *testing.T) { - t.Skip() template := "testdata/corerp-resources-terraform-redis.bicep" name := "corerp-resources-terraform-redis" appName := "corerp-resources-terraform-redis-app" @@ -235,7 +234,6 @@ func Test_TerraformRecipe_KubernetesPostgres(t *testing.T) { } func Test_TerraformRecipe_Context(t *testing.T) { - t.Skip() template := "testdata/corerp-resources-terraform-context.bicep" name := "corerp-resources-terraform-context" appNamespace := "corerp-resources-terraform-context-app"