iterationFunc, TimeSpan maxRunTime, TimeSpan iterationTimeout)
+ {
+ var min = long.MaxValue;
+ var max = long.MinValue;
+ var avg = 0L;
+
+ var i = 0;
+ var start = DateTime.Now;
+ while (DateTime.Now.Subtract(maxRunTime) < start)
+ {
+ var iterationStart = Stopwatch.GetTimestamp();
+ Logger.LogInformation($"**** Test iteration started: #{i} at {DateTime.Now}");
+
+ using var iterationCts = new CancellationTokenSource(iterationTimeout);
+ var iterationToken = iterationCts.Token;
+
+ await iterationFunc(i, iterationToken);
+ i++;
+
+ var duration = Stopwatch.GetTimestamp() - iterationStart;
+ min = Math.Min(min, duration);
+ max = Math.Max(max, duration);
+ avg = ((avg * (i - 1)) + duration) / i;
+ Logger.LogInformation($"**** Test iteration finished: #{i} in {TimeSpan.FromTicks(duration).TotalMilliseconds}ms");
+ Logger.LogInformation($"**** Test iteration duration: min={TimeSpan.FromTicks(min).TotalMilliseconds}ms, max={TimeSpan.FromTicks(max).TotalMilliseconds}ms, avg={TimeSpan.FromTicks(avg).TotalMilliseconds}ms");
+ }
+ }
+}
diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/RCLStressTests.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/RCLStressTests.cs
new file mode 100644
index 00000000000..c5ed75e06bf
--- /dev/null
+++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/RCLStressTests.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.WebTools.Languages.Shared.Editor.EditorHelpers;
+using Xunit.Abstractions;
+
+namespace Microsoft.VisualStudio.Razor.IntegrationTests;
+
+public class RCLStressTests(ITestOutputHelper testOutputHelper) : AbstractStressTest(testOutputHelper)
+{
+ protected override string TargetFramework => "net9.0";
+
+ protected override string ProjectZipFile => "Microsoft.VisualStudio.Razor.IntegrationTests.TestFiles.BlazorProjectWithRCL.zip";
+
+ [ManualRunOnlyIdeFact]
+ public async Task AddAndRemoveComponentInRCL()
+ {
+ await TestServices.SolutionExplorer.OpenFileAsync("RazorClassLibrary", @"Components\RCLComponent.razor", ControlledHangMitigatingCancellationToken);
+
+ await TestServices.Editor.PlaceCaretAsync("Iteration {index}{Environment.NewLine}", cancellationToken);
+
+ await TestServices.Editor.PlaceCaretAsync("h1", charsOffset: -1, cancellationToken);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 1, exact: true);
+
+ await TestServices.Editor.InvokeCodeActionAsync("Extract element to new component", cancellationToken);
+
+ await TestServices.Editor.WaitForActiveWindowByFileAsync("Component.razor", cancellationToken);
+
+ await TestServices.Editor.PlaceCaretAsync("
{Environment.NewLine}", cancellationToken);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 5, exact: true);
+
+ File.Delete(componentFileName);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 4, exact: true);
+
+ await TestServices.Editor.PlaceCaretAsync("", charsOffset: -1, cancellationToken);
+
+ await TestServices.Editor.InvokeDeleteLineAsync(cancellationToken);
+
+ await TestServices.Editor.InsertTextAsync($"Iteration {index}
{Environment.NewLine}", cancellationToken);
+
+ await TestServices.SolutionExplorer.OpenFileAsync("RazorClassLibrary", @"Components\RCLComponent.razor", ControlledHangMitigatingCancellationToken);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 1, exact: true);
+
+ await TestServices.Editor.PlaceCaretAsync("", charsOffset: -1, cancellationToken);
+
+ await TestServices.Editor.InvokeDeleteLineAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/StressTests.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/StressTests.cs
new file mode 100644
index 00000000000..dcf91f27ad4
--- /dev/null
+++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/StressTests/StressTests.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.WebTools.Languages.Shared.Editor.EditorHelpers;
+using Xunit.Abstractions;
+
+namespace Microsoft.VisualStudio.Razor.IntegrationTests;
+
+public class StressTests(ITestOutputHelper testOutputHelper) : AbstractStressTest(testOutputHelper)
+{
+ [ManualRunOnlyIdeFact]
+ public async Task AddAndRemoveComponent()
+ {
+ await TestServices.SolutionExplorer.OpenFileAsync(RazorProjectConstants.BlazorProjectName, RazorProjectConstants.CounterRazorFile, ControlledHangMitigatingCancellationToken);
+
+ await TestServices.Editor.PlaceCaretAsync("h1", charsOffset: -1, ControlledHangMitigatingCancellationToken);
+
+ await TestServices.Editor.InvokeDeleteLineAsync(ControlledHangMitigatingCancellationToken);
+
+ await RunStressTestAsync(RunIterationAsync);
+
+ async Task RunIterationAsync(int index, CancellationToken cancellationToken)
+ {
+ await TestServices.Editor.InsertTextAsync($"Iteration {index}
{Environment.NewLine}", cancellationToken);
+
+ await TestServices.Editor.PlaceCaretAsync("h1", charsOffset: -1, cancellationToken);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 2, exact: true);
+
+ await TestServices.Editor.InvokeCodeActionAsync("Extract element to new component", cancellationToken);
+
+ await TestServices.Editor.WaitForActiveWindowByFileAsync("Component.razor", cancellationToken);
+
+ var componentFileName = (await TestServices.Editor.GetActiveTextViewAsync(cancellationToken)).TextBuffer.GetFileName();
+
+ await TestServices.Editor.CloseCurrentlyFocusedWindowAsync(cancellationToken, save: true);
+
+ await TestServices.Editor.WaitForActiveWindowByFileAsync("Counter.razor", cancellationToken);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 3, exact: true);
+
+ await Task.Delay(500);
+
+ File.Delete(componentFileName);
+
+ await TestServices.Editor.WaitForComponentClassificationAsync(cancellationToken, count: 2, exact: true);
+
+ await Task.Delay(500);
+
+ await TestServices.Editor.PlaceCaretAsync("Component", charsOffset: -1, cancellationToken);
+
+ await TestServices.Editor.InvokeDeleteLineAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/TestFiles/BlazorProjectWithRCL.zip b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/TestFiles/BlazorProjectWithRCL.zip
new file mode 100644
index 00000000000..f05439c499d
Binary files /dev/null and b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/TestFiles/BlazorProjectWithRCL.zip differ