From c90c8d6b3458843e6c32fa8b01e32a8550dc8383 Mon Sep 17 00:00:00 2001 From: afscrome Date: Sat, 24 Oct 2026 22:42:40 +0100 Subject: [PATCH] test(kind): remove timer race from CRD timeout coverage Replace the delayed-response deadline test with hung-read coverage for clients that honour or ignore cancellation, under strict and best-effort policies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../KindManifestTests.cs | 83 +++++-------------- 1 file changed, 22 insertions(+), 61 deletions(-) diff --git a/tests/CommunityToolkit.Aspire.Hosting.Kind.Tests/KindManifestTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Kind.Tests/KindManifestTests.cs index f1e0a0a92..de9fb96e0 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.Kind.Tests/KindManifestTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.Kind.Tests/KindManifestTests.cs @@ -1521,18 +1521,18 @@ public async Task ManifestPostApplyCheck_UsesParentKubeconfigWhenCreatingClient( } [Theory] - [InlineData(CrdWaitBehavior.Fail)] - [InlineData(CrdWaitBehavior.BestEffort)] - public async Task ManifestPostApplyCheck_StopsHungWaitAtConfiguredTimeout(CrdWaitBehavior behavior) + [InlineData(CrdWaitBehavior.Fail, false)] + [InlineData(CrdWaitBehavior.Fail, true)] + [InlineData(CrdWaitBehavior.BestEffort, false)] + [InlineData(CrdWaitBehavior.BestEffort, true)] + public async Task ManifestPostApplyCheck_StopsHungWaitAtConfiguredTimeout(CrdWaitBehavior behavior, bool ignoresCancellation) { var runner = new FakeProcessRunner(); runner.Results.Enqueue(new(0, "cluster is running", "")); runner.Results.Enqueue(new(0, "customresourcedefinition.apiextensions.k8s.io/widgets.example.com", "")); - var kubernetes = new FakeCrdClient(async (name, token) => - { - await Task.Delay(Timeout.InfiniteTimeSpan, token); - return FakeCrdClient.Definition(name, established: false); - }); + TaskCompletionSource read = new(TaskCreationOptions.RunContinuationsAsynchronously); + var kubernetes = new FakeCrdClient((_, token) => + ignoresCancellation ? read.Task : read.Task.WaitAsync(token)); using var builder = TestDistributedApplicationBuilder.Create(); var resource = builder.AddKindCluster("test-cluster") .AddManifest("crds", Path.Combine(AppContext.BaseDirectory, "crds.yaml")) @@ -1549,62 +1549,23 @@ public async Task ManifestPostApplyCheck_StopsHungWaitAtConfiguredTimeout(CrdWai using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); cts.CancelAfter(TimeSpan.FromSeconds(3)); - if (behavior == CrdWaitBehavior.BestEffort) - { - await app.Services.GetRequiredService().RunAsync(resource, cts.Token); - } - else + try { - await Assert.ThrowsAsync(() => - app.Services.GetRequiredService().RunAsync(resource, cts.Token)); + if (behavior == CrdWaitBehavior.BestEffort) + { + await app.Services.GetRequiredService().RunAsync(resource, cts.Token); + } + else + { + await Assert.ThrowsAsync(() => + app.Services.GetRequiredService().RunAsync(resource, cts.Token)); + } + Assert.Equal(2, runner.Commands.Count); + Assert.Equal(["widgets.example.com"], kubernetes.ReadNames); } - Assert.Equal(2, runner.Commands.Count); - Assert.Equal(["widgets.example.com"], kubernetes.ReadNames); - } - - [Fact] - public async Task ManifestPostApplyCheck_RejectsEstablishedResponseAfterDeadline() - { - using var builder = TestDistributedApplicationBuilder.Create(); - var resource = builder.AddKindCluster("test-cluster") - .AddManifestFromContent("crds", "kind: CustomResourceDefinition") - .WithCrdWait(options => options.Timeout = TimeSpan.FromSeconds(1)).Resource; - var outcomes = KindDeploymentOutcomes.GetOrCreate(resource); - outcomes.CrdNames = - ["customresourcedefinition.apiextensions.k8s.io/widgets.example.com"]; - CancellationToken? waitCancellationToken = null; - var kubernetes = new FakeCrdClient(async (name, token) => - { - waitCancellationToken = token; - await Task.Delay(TimeSpan.FromMilliseconds(1250), TestContext.Current.CancellationToken); - return FakeCrdClient.Definition(name, established: true); - }); - builder.Services.AddSingleton>(_ => _ => kubernetes.Client); - using var app = builder.Build(); - - var stopwatch = Stopwatch.StartNew(); - var exception = await Record.ExceptionAsync(() => - app.Services.GetRequiredService().RunAsync(resource, TestContext.Current.CancellationToken)); - var elapsed = stopwatch.Elapsed; - - if (exception?.GetType() != typeof(TimeoutException)) + finally { - resource.TryGetLastAnnotation(out var policy); - var crdNames = outcomes.CrdNames ?? []; - var waitCancellationRequested = waitCancellationToken is { } token - ? token.IsCancellationRequested.ToString() - : ""; - - Assert.Fail($""" - Expected TimeoutException, but the post-apply check returned a different result. - Effective CRD wait policy: Timeout={policy?.Options.Timeout.ToString() ?? ""}, FailureBehavior={policy?.Options.FailureBehavior.ToString() ?? ""} - CRD outcomes: Count={crdNames.Count}, Names=[{string.Join(", ", crdNames)}] - Fake CRD client reads: {kubernetes.ReadCount} - RunAsync elapsed: {elapsed} - Wait cancellation requested: {waitCancellationRequested} - Actual exception: - {exception?.ToString() ?? ""} - """); + read.TrySetResult(FakeCrdClient.Definition("widgets.example.com", established: true)); } }