-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Split ApplyDeltaTests into multiple types #53158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
1,408 changes: 0 additions & 1,408 deletions
1,408
test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
This file was deleted.
Oops, something went wrong.
176 changes: 176 additions & 0 deletions
176
test/dotnet-watch.Tests/HotReload/AspireHotReloadTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #nullable disable | ||
|
|
||
| using System.Text.RegularExpressions; | ||
|
|
||
| namespace Microsoft.DotNet.Watch.UnitTests | ||
| { | ||
| public class AspireHotReloadTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) | ||
| { | ||
| [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/53058, https://github.com/dotnet/sdk/issues/53061, https://github.com/dotnet/sdk/issues/53114 | ||
| public async Task Aspire_BuildError_ManualRestart() | ||
| { | ||
| var tfm = ToolsetInfo.CurrentTargetFramework; | ||
| var testAsset = TestAssets.CopyTestAsset("WatchAspire") | ||
| .WithSource(); | ||
|
|
||
| var serviceSourcePath = Path.Combine(testAsset.Path, "WatchAspire.ApiService", "Program.cs"); | ||
| var serviceProjectPath = Path.Combine(testAsset.Path, "WatchAspire.ApiService", "WatchAspire.ApiService.csproj"); | ||
| var serviceSource = File.ReadAllText(serviceSourcePath, Encoding.UTF8); | ||
|
|
||
| var webSourcePath = Path.Combine(testAsset.Path, "WatchAspire.Web", "Program.cs"); | ||
| var webProjectPath = Path.Combine(testAsset.Path, "WatchAspire.Web", "WatchAspire.Web.csproj"); | ||
|
|
||
| App.Start(testAsset, ["-lp", "http"], relativeProjectDirectory: "WatchAspire.AppHost", testFlags: TestFlags.ReadKeyFromStdin); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| // check that Aspire server output is logged via dotnet-watch reporter: | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ Now listening on:"); | ||
|
|
||
| // wait until after all DCP sessions have started: | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Session started"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#2] Session started"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#3] Session started"); | ||
|
|
||
| // MigrationService terminated: | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Sending 'sessionTerminated'"); | ||
|
|
||
| // working directory of the service should be its project directory: | ||
| await App.WaitUntilOutputContains($"ApiService working directory: '{Path.GetDirectoryName(serviceProjectPath)}'"); | ||
|
|
||
| // Service -- valid code change: | ||
| UpdateSourceFile( | ||
| serviceSourcePath, | ||
| serviceSource.Replace("Enumerable.Range(1, 5)", "Enumerable.Range(1, 10)")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); | ||
|
|
||
| await App.WaitUntilOutputContains("Using Aspire process launcher."); | ||
|
|
||
| // Only one browser should be launched (dashboard). The child process shouldn't launch a browser. | ||
| Assert.Equal(1, App.Process.Output.Count(line => line.StartsWith("dotnet watch ⌚ Launching browser: "))); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // rude edit with build error: | ||
| UpdateSourceFile( | ||
| serviceSourcePath, | ||
| serviceSource.Replace("record WeatherForecast", "record WeatherForecast2")); | ||
|
|
||
| // the prompt is printed into stdout while the error is printed into stderr, so they might arrive in any order: | ||
| await App.WaitUntilOutputContains(" ❔ Do you want to restart these projects? Yes (y) / No (n) / Always (a) / Never (v)"); | ||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); | ||
|
|
||
| await App.WaitUntilOutputContains($"dotnet watch ❌ {serviceSourcePath}(40,1): error ENC0020: Renaming record 'WeatherForecast' requires restarting the application."); | ||
| await App.WaitUntilOutputContains("dotnet watch ⌚ Affected projects:"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⌚ WatchAspire.ApiService"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| App.SendKey('y'); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.FixBuildError); | ||
|
|
||
| await App.WaitUntilOutputContains("Application is shutting down..."); | ||
|
|
||
| await App.WaitUntilOutputContains($"[WatchAspire.ApiService ({tfm})] Exited"); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.Building.GetMessage(serviceProjectPath)); | ||
| await App.WaitUntilOutputContains("error CS0246: The type or namespace name 'WeatherForecast' could not be found"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // fix build error: | ||
| UpdateSourceFile( | ||
| serviceSourcePath, | ||
| serviceSource.Replace("WeatherForecast", "WeatherForecast2")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRestarted.GetMessage(1)); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.BuildSucceeded.GetMessage(serviceProjectPath)); | ||
| await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRebuilt); | ||
| await App.WaitUntilOutputContains($"Starting: '{serviceProjectPath}'"); | ||
|
|
||
| // Wait for the process to start before shutting down, so we can reliably verify Exited message below. | ||
| // The agent startup hook might not be initialized yet (signal handlers registered), | ||
| // so the process might need to be forcefully killed. We could wait until the agent is initialized | ||
| // but it's good to test this scenario. | ||
| await App.WaitUntilOutputContains(MessageDescriptor.LaunchedProcess, $"WatchAspire.ApiService ({tfm})"); | ||
|
|
||
| App.Process.ClearOutput(); | ||
|
|
||
| App.SendControlC(); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ShutdownRequested); | ||
|
|
||
| await App.WaitUntilOutputContains($"[WatchAspire.ApiService ({tfm})] Exited"); | ||
| await App.WaitUntilOutputContains($"[WatchAspire.Web ({tfm})] Exited"); | ||
| await App.WaitUntilOutputContains($"[WatchAspire.AppHost ({tfm})] Exited"); | ||
|
|
||
| await App.WaitUntilOutputContains("dotnet watch ⭐ Waiting for server to shutdown ..."); | ||
|
|
||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Stop session"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#2] Stop session"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#3] Stop session"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#2] Sending 'sessionTerminated'"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#3] Sending 'sessionTerminated'"); | ||
| } | ||
|
|
||
| [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/53058, https://github.com/dotnet/sdk/issues/53061, https://github.com/dotnet/sdk/issues/53114 | ||
| public async Task Aspire_NoEffect_AutoRestart() | ||
| { | ||
| var tfm = ToolsetInfo.CurrentTargetFramework; | ||
| var testAsset = TestAssets.CopyTestAsset("WatchAspire") | ||
| .WithSource(); | ||
|
|
||
| var webSourcePath = Path.Combine(testAsset.Path, "WatchAspire.Web", "Program.cs"); | ||
| var webProjectPath = Path.Combine(testAsset.Path, "WatchAspire.Web", "WatchAspire.Web.csproj"); | ||
| var webSource = File.ReadAllText(webSourcePath, Encoding.UTF8); | ||
|
|
||
| App.Start(testAsset, ["-lp", "http", "--non-interactive"], relativeProjectDirectory: "WatchAspire.AppHost"); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Session started"); | ||
| await App.WaitUntilOutputContains(MessageDescriptor.Exited, $"WatchAspire.MigrationService ({tfm})"); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Sending 'sessionTerminated'"); | ||
|
|
||
| // migration service output should not be printed to dotnet-watch output, it should be sent via DCP as a notification: | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#1] Sending 'serviceLogs': log_message=' Migration complete', is_std_err=False"); | ||
|
|
||
| // wait until after DCP sessions have been started for all projects: | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#3] Session started"); | ||
|
|
||
| App.AssertOutputDoesNotContain(new Regex("^ +Migration complete")); | ||
|
|
||
| App.Process.ClearOutput(); | ||
|
|
||
| // no-effect edit: | ||
| UpdateSourceFile(webSourcePath, src => src.Replace("/* top-level placeholder */", "builder.Services.AddRazorComponents();")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); | ||
| await App.WaitUntilOutputContains("dotnet watch ⭐ [#3] Session started"); | ||
| await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRestarted.GetMessage(1)); | ||
| App.AssertOutputDoesNotContain("⚠"); | ||
|
|
||
| // The process exited and should not participate in Hot Reload: | ||
| App.AssertOutputDoesNotContain($"[WatchAspire.MigrationService ({tfm})]"); | ||
| App.AssertOutputDoesNotContain("dotnet watch ⭐ [#1]"); | ||
|
|
||
| App.Process.ClearOutput(); | ||
|
|
||
| // lambda body edit: | ||
| UpdateSourceFile(webSourcePath, src => src.Replace("Hello world!", "<Updated>")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); | ||
| await App.WaitUntilOutputContains($"dotnet watch 🕵️ [WatchAspire.Web ({tfm})] Updates applied."); | ||
| App.AssertOutputDoesNotContain(MessageDescriptor.ProjectsRebuilt); | ||
| App.AssertOutputDoesNotContain(MessageDescriptor.ProjectsRestarted); | ||
| App.AssertOutputDoesNotContain("⚠"); | ||
|
|
||
| // The process exited and should not participate in Hot Reload: | ||
| App.AssertOutputDoesNotContain($"[WatchAspire.MigrationService ({tfm})]"); | ||
| App.AssertOutputDoesNotContain("dotnet watch ⭐ [#1]"); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #nullable disable | ||
|
|
||
| using System.Text.RegularExpressions; | ||
|
|
||
| namespace Microsoft.DotNet.Watch.UnitTests | ||
| { | ||
|
tmat marked this conversation as resolved.
|
||
| public class AutoRestartTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) | ||
| { | ||
| [Theory] | ||
| [CombinatorialData] | ||
| public async Task AutoRestartOnRudeEdit(bool nonInteractive) | ||
| { | ||
| var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: nonInteractive.ToString()) | ||
| .WithSource(); | ||
|
|
||
| if (!nonInteractive) | ||
| { | ||
| testAsset = testAsset | ||
| .WithProjectChanges(project => | ||
| { | ||
| project.Root.Descendants() | ||
| .First(e => e.Name.LocalName == "PropertyGroup") | ||
| .Add(XElement.Parse(""" | ||
| <HotReloadAutoRestart>true</HotReloadAutoRestart> | ||
| """)); | ||
| }); | ||
| } | ||
|
|
||
| var programPath = Path.Combine(testAsset.Path, "Program.cs"); | ||
|
|
||
| App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // rude edit: adding virtual method | ||
| UpdateSourceFile(programPath, src => src.Replace("/* member placeholder */", "public virtual void F() {}")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); | ||
| await App.WaitUntilOutputContains($"⌚ [auto-restart] {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // valid edit: | ||
| UpdateSourceFile(programPath, src => src.Replace("public virtual void F() {}", "public virtual void F() { Console.WriteLine(1); }")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); | ||
| } | ||
|
|
||
| [Theory(Skip = "https://github.com/dotnet/sdk/issues/51469")] | ||
| [CombinatorialData] | ||
| public async Task AutoRestartOnRuntimeRudeEdit(bool nonInteractive) | ||
| { | ||
| var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: nonInteractive.ToString()) | ||
| .WithSource(); | ||
|
|
||
| var tfm = ToolsetInfo.CurrentTargetFramework; | ||
| var programPath = Path.Combine(testAsset.Path, "Program.cs"); | ||
|
|
||
| // Changes the type of lambda without updating top-level code. | ||
| // The loop will end up calling the old version of the lambda resulting in runtime rude edit. | ||
|
|
||
| File.WriteAllText(programPath, """ | ||
| using System; | ||
| using System.Threading; | ||
|
|
||
| var d = C.F(); | ||
|
|
||
| while (true) | ||
| { | ||
| Thread.Sleep(250); | ||
| d(1); | ||
| } | ||
|
|
||
| class C | ||
| { | ||
| public static Action<int> F() | ||
| { | ||
| return a => | ||
| { | ||
| Console.WriteLine(a.GetType()); | ||
| }; | ||
| } | ||
| } | ||
| """); | ||
|
|
||
| App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
| await App.WaitUntilOutputContains("System.Int32"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| UpdateSourceFile(programPath, src => src.Replace("Action<int>", "Action<byte>")); | ||
|
|
||
| // The following agent messages must be reported in order. | ||
| // The HotReloadException handler needs to be installed and update handlers invoked and completed before the | ||
| // HotReloadException handler may proceed with runtime rude edit processing and application restart. | ||
| await App.WaitForOutputLineContaining($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] HotReloadException handler installed."); | ||
| await App.WaitForOutputLineContaining($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] Invoking metadata update handlers."); | ||
| await App.WaitForOutputLineContaining($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] Updates applied."); | ||
| await App.WaitForOutputLineContaining($"dotnet watch 🕵️ [WatchHotReloadApp ({tfm})] Runtime rude edit detected:"); | ||
|
|
||
| await App.WaitUntilOutputContains($"dotnet watch ⚠ [WatchHotReloadApp ({tfm})] " + | ||
| "Attempted to invoke a deleted lambda or local function implementation. " + | ||
| "This can happen when lambda or local function is deleted while the application is running."); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartingApplication, $"WatchHotReloadApp ({tfm})"); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
| await App.WaitUntilOutputContains("System.Byte"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task AutoRestartOnRudeEditAfterRestartPrompt() | ||
| { | ||
| var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") | ||
| .WithSource(); | ||
|
|
||
| var programPath = Path.Combine(testAsset.Path, "Program.cs"); | ||
|
|
||
| App.Start(testAsset, [], testFlags: TestFlags.ReadKeyFromStdin); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // rude edit: adding virtual method | ||
| UpdateSourceFile(programPath, src => src.Replace("/* member placeholder */", "public virtual void F() {}")); | ||
|
|
||
| // the prompt is printed into stdout while the error is printed into stderr, so they might arrive in any order: | ||
| await App.WaitUntilOutputContains(" ❔ Do you want to restart your app? Yes (y) / No (n) / Always (a) / Never (v)"); | ||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); | ||
|
|
||
| await App.WaitUntilOutputContains($"❌ {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| App.SendKey('a'); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); | ||
| App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // rude edit: deleting virtual method | ||
| UpdateSourceFile(programPath, src => src.Replace("public virtual void F() {}", "")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); | ||
| await App.WaitUntilOutputContains($"⌚ [auto-restart] {programPath}(39,1): error ENC0033: Deleting method 'F()' requires restarting the application."); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); | ||
| } | ||
|
|
||
| [Theory] | ||
| [CombinatorialData] | ||
| public async Task AutoRestartOnNoEffectEdit(bool nonInteractive) | ||
| { | ||
| var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp", identifier: nonInteractive.ToString()) | ||
| .WithSource(); | ||
|
|
||
| if (!nonInteractive) | ||
| { | ||
| testAsset = testAsset | ||
| .WithProjectChanges(project => | ||
| { | ||
| project.Root.Descendants() | ||
| .First(e => e.Name.LocalName == "PropertyGroup") | ||
| .Add(XElement.Parse(""" | ||
| <HotReloadAutoRestart>true</HotReloadAutoRestart> | ||
| """)); | ||
| }); | ||
| } | ||
|
|
||
| var programPath = Path.Combine(testAsset.Path, "Program.cs"); | ||
|
|
||
| App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // top-level code change: | ||
| UpdateSourceFile(programPath, src => src.Replace("Started", "<Updated>")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); | ||
| await App.WaitUntilOutputContains($"⌚ [auto-restart] {programPath}(17,19): warning ENC0118: Changing 'top-level code' might not have any effect until the application is restarted."); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); | ||
| await App.WaitUntilOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); | ||
| await App.WaitUntilOutputContains("<Updated>"); | ||
| App.Process.ClearOutput(); | ||
|
|
||
| // valid edit: | ||
| UpdateSourceFile(programPath, src => src.Replace("/* member placeholder */", "public void F() {}")); | ||
|
|
||
| await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.