diff --git a/JitHub.WinUI.Tests/Services/ProductPerformanceObservationTests.cs b/JitHub.WinUI.Tests/Services/ProductPerformanceObservationTests.cs index 88e36397c..48c441d9d 100644 --- a/JitHub.WinUI.Tests/Services/ProductPerformanceObservationTests.cs +++ b/JitHub.WinUI.Tests/Services/ProductPerformanceObservationTests.cs @@ -6,8 +6,6 @@ namespace JitHub.WinUI.Tests.Services; public sealed class ProductPerformanceObservationTests { - private static readonly object ReadinessGate = new(); - [Fact] public void ContentTransition_DoesNotPassWhenInvokeReturnsButDestinationIdentityDoesNotChange() { @@ -210,75 +208,6 @@ public void ContentTransition_UsesAppSideCompositionTimestampsInsteadOfObserverL Assert.Equal(TimeSpan.FromMilliseconds(38), tracker.SettledDataContent); } - [Fact] - public void TraversalCommitEndsGenerationAndRejectsLaterStages() - { - lock (ReadinessGate) - { - string? previous = Environment.GetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE"); - List stages = []; - EventHandler handler = (_, stage) => stages.Add(stage); - ProductPerformanceReadiness.TraversalStageRecorded += handler; - try - { - Environment.SetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE", "1"); - ProductPerformanceReadiness.BeginTraversal("stars", "repo:42", "repo_code"); - ProductPerformanceReadiness.RecordTraversalStage("before.commit"); - ProductPerformanceReadiness.CommitTraversal("repo_code", "repo:42"); - ProductPerformanceReadiness.RecordTraversalStage("after.commit"); - - ProductPerformanceTraversalStage stage = Assert.Single( - stages, - candidate => candidate.Stage is "before.commit" or "after.commit"); - Assert.Equal("before.commit", stage.Stage); - Assert.True(stage.Generation > 0); - } - finally - { - ProductPerformanceReadiness.CancelTraversal(); - ProductPerformanceReadiness.TraversalStageRecorded -= handler; - Environment.SetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE", previous); - } - } - } - - [Fact] - public void TraversalMeasurement_ResumesProbeAfterCommitOrCancellation() - { - lock (ReadinessGate) - { - string? previous = Environment.GetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE"); - int armed = 0; - int completed = 0; - EventHandler armedHandler = (_, _) => armed++; - EventHandler completedHandler = (_, _) => completed++; - ProductPerformanceReadiness.TraversalMeasurementArmed += armedHandler; - ProductPerformanceReadiness.TraversalMeasurementCompleted += completedHandler; - try - { - Environment.SetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE", "1"); - - ProductPerformanceReadiness.ArmTraversalMeasurement(); - ProductPerformanceReadiness.BeginTraversal("repo_code", "file:1", "repo_code"); - ProductPerformanceReadiness.CommitTraversal("repo_code", "file:1"); - - ProductPerformanceReadiness.ArmTraversalMeasurement(); - ProductPerformanceReadiness.BeginTraversal("repo_code", "file:2", "repo_code"); - ProductPerformanceReadiness.CancelTraversal(); - - Assert.Equal(2, armed); - Assert.Equal(2, completed); - } - finally - { - ProductPerformanceReadiness.CancelTraversal(); - ProductPerformanceReadiness.TraversalMeasurementArmed -= armedHandler; - ProductPerformanceReadiness.TraversalMeasurementCompleted -= completedHandler; - Environment.SetEnvironmentVariable("JITHUB_PERFORMANCE_FIXTURE", previous); - } - } - } - private static ProductPerformanceContentObservation Observation( string identity, long frame, diff --git a/JitHub.WinUI.Tests/Services/ProductPerformanceReadinessTraversalTests.cs b/JitHub.WinUI.Tests/Services/ProductPerformanceReadinessTraversalTests.cs new file mode 100644 index 000000000..1ed14e538 --- /dev/null +++ b/JitHub.WinUI.Tests/Services/ProductPerformanceReadinessTraversalTests.cs @@ -0,0 +1,91 @@ +using System.Collections.Concurrent; +using JitHub.Services; +using Xunit; + +namespace JitHub.WinUI.Tests.Services; + +/// +/// Tests that turn on JITHUB_PERFORMANCE_FIXTURE and drive the static +/// traversal state. Both are process-wide: +/// while the variable is set, product code under test in other classes (for example +/// RepoCodePageViewModel.SelectFileAsync) records traversal stages into the same +/// static traversal, and the variable changes +/// for every test in the process. xUnit runs this collection on its own, after the +/// parallel collections finish. +/// +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class ProductPerformanceReadinessCollection +{ + public const string Name = "Product performance readiness (process-wide)"; +} + +[Collection(ProductPerformanceReadinessCollection.Name)] +public sealed class ProductPerformanceReadinessTraversalTests +{ + private const string FixtureVariable = "JITHUB_PERFORMANCE_FIXTURE"; + + [Fact] + public void TraversalCommitEndsGenerationAndRejectsLaterStages() + { + string? previous = Environment.GetEnvironmentVariable(FixtureVariable); + // The static event may be raised from any thread, so collect stages thread-safely + // and assert on one snapshot. + ConcurrentQueue stages = new(); + EventHandler handler = (_, stage) => stages.Enqueue(stage); + ProductPerformanceReadiness.TraversalStageRecorded += handler; + try + { + Environment.SetEnvironmentVariable(FixtureVariable, "1"); + ProductPerformanceReadiness.BeginTraversal("stars", "repo:42", "repo_code"); + ProductPerformanceReadiness.RecordTraversalStage("before.commit"); + ProductPerformanceReadiness.CommitTraversal("repo_code", "repo:42"); + ProductPerformanceReadiness.RecordTraversalStage("after.commit"); + + ProductPerformanceTraversalStage stage = Assert.Single( + stages.ToArray(), + candidate => candidate.Stage is "before.commit" or "after.commit"); + Assert.Equal("before.commit", stage.Stage); + Assert.True(stage.Generation > 0); + } + finally + { + ProductPerformanceReadiness.CancelTraversal(); + ProductPerformanceReadiness.TraversalStageRecorded -= handler; + Environment.SetEnvironmentVariable(FixtureVariable, previous); + } + } + + [Fact] + public void TraversalMeasurement_ResumesProbeAfterCommitOrCancellation() + { + string? previous = Environment.GetEnvironmentVariable(FixtureVariable); + int armed = 0; + int completed = 0; + EventHandler armedHandler = (_, _) => Interlocked.Increment(ref armed); + EventHandler completedHandler = (_, _) => Interlocked.Increment(ref completed); + ProductPerformanceReadiness.TraversalMeasurementArmed += armedHandler; + ProductPerformanceReadiness.TraversalMeasurementCompleted += completedHandler; + try + { + Environment.SetEnvironmentVariable(FixtureVariable, "1"); + + ProductPerformanceReadiness.ArmTraversalMeasurement(); + ProductPerformanceReadiness.BeginTraversal("repo_code", "file:1", "repo_code"); + ProductPerformanceReadiness.CommitTraversal("repo_code", "file:1"); + + ProductPerformanceReadiness.ArmTraversalMeasurement(); + ProductPerformanceReadiness.BeginTraversal("repo_code", "file:2", "repo_code"); + ProductPerformanceReadiness.CancelTraversal(); + + Assert.Equal(2, Volatile.Read(ref armed)); + Assert.Equal(2, Volatile.Read(ref completed)); + } + finally + { + ProductPerformanceReadiness.CancelTraversal(); + ProductPerformanceReadiness.TraversalMeasurementArmed -= armedHandler; + ProductPerformanceReadiness.TraversalMeasurementCompleted -= completedHandler; + Environment.SetEnvironmentVariable(FixtureVariable, previous); + } + } +}