From 2b87dc859445936d26fd5a8d943bcc04ca627695 Mon Sep 17 00:00:00 2001 From: tmat Date: Sat, 7 Feb 2026 12:14:18 -0800 Subject: [PATCH] Use WaitUntilOutputContains for most test output checks --- src/BuiltInTools/Watch/Build/BuildNames.cs | 1 + .../Watch/Build/EvaluationResult.cs | 8 +- .../Browser/BrowserTests.cs | 22 +- .../HotReload/ApplyDeltaTests.cs | 348 +++++++++--------- 4 files changed, 199 insertions(+), 180 deletions(-) diff --git a/src/BuiltInTools/Watch/Build/BuildNames.cs b/src/BuiltInTools/Watch/Build/BuildNames.cs index 0b2b8d0ccd00..a2d0b790ce0d 100644 --- a/src/BuiltInTools/Watch/Build/BuildNames.cs +++ b/src/BuiltInTools/Watch/Build/BuildNames.cs @@ -22,6 +22,7 @@ internal static class PropertyNames public const string DesignTimeBuild = nameof(DesignTimeBuild); public const string SkipCompilerExecution = nameof(SkipCompilerExecution); public const string ProvideCommandLineArgs = nameof(ProvideCommandLineArgs); + public const string NonExistentFile = nameof(NonExistentFile); } internal static class ItemNames diff --git a/src/BuiltInTools/Watch/Build/EvaluationResult.cs b/src/BuiltInTools/Watch/Build/EvaluationResult.cs index 829f527774e0..cc0566e49b39 100644 --- a/src/BuiltInTools/Watch/Build/EvaluationResult.cs +++ b/src/BuiltInTools/Watch/Build/EvaluationResult.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; +using System.Diagnostics; using Microsoft.Build.Execution; using Microsoft.Build.Graph; using Microsoft.DotNet.HotReload; @@ -51,7 +52,9 @@ public static ImmutableDictionary GetGlobalBuildOptions(IEnumera .SetItem(PropertyNames.DotNetWatchBuild, "true") .SetItem(PropertyNames.DesignTimeBuild, "true") .SetItem(PropertyNames.SkipCompilerExecution, "true") - .SetItem(PropertyNames.ProvideCommandLineArgs, "true"); + .SetItem(PropertyNames.ProvideCommandLineArgs, "true") + // this will force CoreCompile task to execute and return command line args even if all inputs and outputs are up to date: + .SetItem(PropertyNames.NonExistentFile, "__NonExistentSubDir__\\__NonExistentFile__"); } /// @@ -126,6 +129,9 @@ public static ImmutableDictionary GetGlobalBuildOptions(IEnumera } } + // command line args items should be available: + Debug.Assert(Path.GetExtension(projectInstance.FullPath) != ".csproj" || projectInstance.GetItems("CscCommandLineArgs").Any()); + var projectPath = projectInstance.FullPath; var projectDirectory = Path.GetDirectoryName(projectPath)!; diff --git a/test/dotnet-watch.Tests/Browser/BrowserTests.cs b/test/dotnet-watch.Tests/Browser/BrowserTests.cs index db850da520d2..18511ab7f734 100644 --- a/test/dotnet-watch.Tests/Browser/BrowserTests.cs +++ b/test/dotnet-watch.Tests/Browser/BrowserTests.cs @@ -16,7 +16,7 @@ public async Task LaunchesBrowserOnStart() App.Start(testAsset, [], testFlags: TestFlags.MockBrowser); // check that all app output is printed out: - await App.WaitForOutputLineContaining("Content root path:"); + await App.WaitUntilOutputContains("Content root path:"); Assert.Contains(App.Process.Output, line => line.Contains("Application started. Press Ctrl+C to shut down.")); Assert.Contains(App.Process.Output, line => line.Contains("Hosting environment: Development")); @@ -38,9 +38,9 @@ public async Task BrowserDiagnostics() App.Start(testAsset, ["--urls", url], relativeProjectDirectory: "RazorApp", testFlags: TestFlags.ReadKeyFromStdin); - await App.WaitForOutputLineContaining(MessageDescriptor.ConfiguredToUseBrowserRefresh); - await App.WaitForOutputLineContaining(MessageDescriptor.ConfiguredToLaunchBrowser); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // Verify the browser has been launched. await App.WaitUntilOutputContains($"๐Ÿงช Test browser opened at '{url}'."); @@ -60,9 +60,9 @@ public async Task BrowserDiagnostics() var errorMessage = $"{homePagePath}(13,9): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."; var jsonErrorMessage = JsonSerializer.Serialize(errorMessage); - await App.WaitForOutputLineContaining(errorMessage); + await App.WaitUntilOutputContains(errorMessage); - await App.WaitForOutputLineContaining("Do you want to restart your app?"); + await App.WaitUntilOutputContains("Do you want to restart your app?"); await App.WaitUntilOutputContains($$""" ๐Ÿงช Received: {"type":"ReportDiagnostics","diagnostics":[{{jsonErrorMessage}}]} @@ -72,7 +72,7 @@ await App.WaitUntilOutputContains($$""" App.SendKey('a'); // browser page is reloaded when the app restarts: - await App.WaitForOutputLineContaining(MessageDescriptor.ReloadingBrowser, $"RazorApp ({tfm})"); + await App.WaitUntilOutputContains(MessageDescriptor.ReloadingBrowser, $"RazorApp ({tfm})"); // browser page was reloaded after the app restarted: await App.WaitUntilOutputContains(""" @@ -82,7 +82,7 @@ await App.WaitUntilOutputContains(""" // no other browser message sent: Assert.Equal(2, App.Process.Output.Count(line => line.Contains("๐Ÿงช"))); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); App.Process.ClearOutput(); @@ -90,13 +90,13 @@ await App.WaitUntilOutputContains(""" UpdateSourceFile(homePagePath, src => src.Replace("public virtual int F() => 1;", "/* member placeholder */")); errorMessage = $"{homePagePath}(11,5): error ENC0033: Deleting method 'F()' requires restarting the application."; - await App.WaitForOutputLineContaining("[auto-restart] " + errorMessage); + await App.WaitUntilOutputContains("[auto-restart] " + errorMessage); await App.WaitUntilOutputContains($$""" ๐Ÿงช Received: {"type":"ReportDiagnostics","diagnostics":["Restarting application to apply changes ..."]} """); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // browser page was reloaded after the app restarted: await App.WaitUntilOutputContains(""" @@ -111,7 +111,7 @@ await App.WaitUntilOutputContains(""" // valid edit: UpdateSourceFile(homePagePath, src => src.Replace("/* member placeholder */", "public int F() => 1;")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); await App.WaitUntilOutputContains($$""" ๐Ÿงช Received: {"type":"ReportDiagnostics","diagnostics":[]} diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index 792ced5064af..75b77ee7d96e 100644 --- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs +++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs @@ -9,7 +9,7 @@ namespace Microsoft.DotNet.Watch.UnitTests { public class ApplyDeltaTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) { - [Fact(Skip = "https://github.com/dotnet/sdk/issues/52576")] + [Fact] public async Task AddSourceFile() { Log("AddSourceFile started"); @@ -21,7 +21,7 @@ public async Task AddSourceFile() App.Start(testAsset, [], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // add a new file: UpdateSourceFile(Path.Combine(dependencyDir, "AnotherLib.cs"), """ @@ -32,7 +32,7 @@ public static void Print() } """); - await App.WaitForOutputLineContaining(MessageDescriptor.ReEvaluationCompleted); + await App.WaitUntilOutputContains(MessageDescriptor.ReEvaluationCompleted); // update existing file: UpdateSourceFile(Path.Combine(dependencyDir, "Foo.cs"), """ @@ -43,7 +43,7 @@ public static void Print() } """); - await App.AssertOutputLineStartsWith("Changed!"); + await App.WaitUntilOutputContains("Changed!"); } [Fact] @@ -56,7 +56,7 @@ public async Task ChangeFileInDependency() App.Start(testAsset, [], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); var newSrc = """ public class Lib @@ -68,12 +68,12 @@ public static void Print() UpdateSourceFile(Path.Combine(dependencyDir, "Foo.cs"), newSrc); - await App.AssertOutputLineStartsWith("Changed!"); + await App.WaitUntilOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddFieldRva AddInstanceFieldToExistingType AddMethodToExistingType AddStaticFieldToExistingType Baseline ChangeCustomAttributes GenericAddFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod NewTypeDefinition UpdateParameters."); - App.AssertOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddFieldRva AddInstanceFieldToExistingType AddMethodToExistingType AddStaticFieldToExistingType Baseline ChangeCustomAttributes GenericAddFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod NewTypeDefinition UpdateParameters."); + await App.WaitUntilOutputContains("Changed!"); } - [Fact(Skip = "https://github.com/dotnet/sdk/issues/52576")] + [Fact] public async Task ProjectChange_UpdateDirectoryBuildPropsThenUpdateSource() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") @@ -83,14 +83,15 @@ public async Task ProjectChange_UpdateDirectoryBuildPropsThenUpdateSource() App.Start(testAsset, [], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); UpdateSourceFile( Path.Combine(testAsset.Path, "Directory.Build.props"), src => src.Replace("false", "true")); - await App.WaitForOutputLineContaining(MessageDescriptor.NoCSharpChangesToApply); - App.AssertOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); + await App.WaitUntilOutputContains(MessageDescriptor.NoCSharpChangesToApply); + await App.WaitUntilOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); + App.Process.ClearOutput(); var newSrc = """ @@ -107,8 +108,8 @@ public static unsafe void Print() UpdateSourceFile(Path.Combine(dependencyDir, "Foo.cs"), newSrc); - await App.AssertOutputLineStartsWith("Changed!"); await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains("Changed!"); } [Theory] @@ -140,15 +141,15 @@ public static void Print() App.Start(testAsset, ["--non-interactive"], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains($"{symbolName} set"); App.Process.ClearOutput(); UpdateSourceFile(buildFilePath, src => src.Replace(symbolName, "")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); - App.AssertOutputContains("dotnet watch โŒš [auto-restart] error ENC1102: Changing project setting 'DefineConstants'"); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); + await App.WaitUntilOutputContains("dotnet watch โŒš [auto-restart] error ENC1102: Changing project setting 'DefineConstants'"); await App.WaitUntilOutputContains($"{symbolName} not set"); } @@ -173,7 +174,7 @@ public static void Print() { #if BUILD_CONST_IN_PROPS System.Console.WriteLine("BUILD_CONST_IN_PROPS set"); - #else + #else System.Console.WriteLine("BUILD_CONST_IN_PROPS not set"); #endif } @@ -182,7 +183,7 @@ public static void Print() App.Start(testAsset, [], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains("BUILD_CONST_IN_PROPS set"); App.Process.ClearOutput(); @@ -193,10 +194,10 @@ public static void Print() await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); await App.WaitUntilOutputContains("BUILD_CONST not set"); - App.AssertOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); + await App.WaitUntilOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); } - [Fact(Skip = "https://github.com/dotnet/sdk/issues/49545")] + [Fact] public async Task ProjectChange_DirectoryBuildProps_Delete() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") @@ -220,19 +221,20 @@ public static void Print() } """); - App.Start(testAsset, [], "AppWithDeps"); + App.Start(testAsset, ["--non-interactive"], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains("BUILD_CONST_IN_PROPS set"); + // delete Directory.Build.props that defines BUILD_CONST_IN_PROPS Log($"Deleting {directoryBuildProps}"); File.Delete(directoryBuildProps); - await App.WaitForOutputLineContaining(MessageDescriptor.NoCSharpChangesToApply); - App.AssertOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); + // Project needs to be re-evaluated: + await App.WaitUntilOutputContains(MessageDescriptor.ProjectChangeTriggeredReEvaluation); App.Process.ClearOutput(); - await App.AssertOutputLineStartsWith("BUILD_CONST_IN_PROPS not set"); + await App.WaitUntilOutputContains("BUILD_CONST_IN_PROPS not set"); } [Fact] @@ -256,8 +258,8 @@ public async Task DefaultItemExcludes_DefaultItemsEnabled() App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(new Regex(@"dotnet watch โŒš Exclusion glob: 'AppData/[*][*]/[*][.][*];bin[/\\]+Debug[/\\]+[*][*];obj[/\\]+Debug[/\\]+[*][*];bin[/\\]+[*][*];obj[/\\]+[*][*]")); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(new Regex(@"dotnet watch โŒš Exclusion glob: 'AppData/[*][*]/[*][.][*];bin[/\\]+Debug[/\\]+[*][*];obj[/\\]+Debug[/\\]+[*][*];bin[/\\]+[*][*];obj[/\\]+[*][*]")); App.Process.ClearOutput(); UpdateSourceFile(appDataFilePath, """ @@ -297,9 +299,9 @@ public async Task DefaultItemExcludes_DefaultItemsDisabled() App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains($"dotnet watch โŒš Excluded directory: '{binDir}'"); - App.AssertOutputContains($"dotnet watch โŒš Excluded directory: '{objDir}'"); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains($"dotnet watch โŒš Excluded directory: '{binDir}'"); + await App.WaitUntilOutputContains($"dotnet watch โŒš Excluded directory: '{objDir}'"); App.Process.ClearOutput(); UpdateSourceFile(binDirFilePath, "class X;"); @@ -320,7 +322,7 @@ public async Task ProjectChange_GlobalUsings() App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // missing System.Linq import: UpdateSourceFile(programPath, content => content.Replace(""" @@ -330,7 +332,8 @@ public async Task ProjectChange_GlobalUsings() Console.WriteLine($">>> {typeof(XDocument)}"); """)); - await App.WaitForOutputLineContaining(MessageDescriptor.UnableToApplyChanges); + await App.WaitUntilOutputContains(MessageDescriptor.UnableToApplyChanges); + App.Process.ClearOutput(); UpdateSourceFile(projectPath, content => content.Replace(""" @@ -339,11 +342,11 @@ public async Task ProjectChange_GlobalUsings() """)); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); await App.WaitUntilOutputContains(">>> System.Xml.Linq.XDocument"); - App.AssertOutputContains(MessageDescriptor.ReEvaluationCompleted); + await App.WaitUntilOutputContains(MessageDescriptor.ReEvaluationCompleted); } [Fact] @@ -362,7 +365,7 @@ public async Task BinaryLogs() App.SuppressVerboseLogging(); App.Start(testAsset, ["--verbose", $"-bl:{binLogPath}"], testFlags: TestFlags.None); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); var expectedLogs = new List() { @@ -383,7 +386,7 @@ public async Task BinaryLogs() """)); - await App.WaitForOutputLineContaining(MessageDescriptor.ReEvaluationCompleted); + await App.WaitUntilOutputContains(MessageDescriptor.ReEvaluationCompleted); // project update triggered restore and DTB: expectedLogs.Add(binLogPathBase + "-dotnet-watch.Restore.WatchHotReloadApp.csproj.2.binlog"); @@ -423,23 +426,24 @@ public async Task AutoRestartOnRudeEdit(bool nonInteractive) App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + 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.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains($"โŒš [auto-restart] {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); + 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.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); } [Theory(Skip = "https://github.com/dotnet/sdk/issues/51469")] @@ -458,7 +462,7 @@ public async Task AutoRestartOnRuntimeRudeEdit(bool nonInteractive) File.WriteAllText(programPath, """ using System; using System.Threading; - + var d = C.F(); while (true) @@ -481,23 +485,28 @@ public static Action F() App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains("System.Int32"); App.Process.ClearOutput(); UpdateSourceFile(programPath, src => src.Replace("Action", "Action")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - await App.WaitUntilOutputContains("System.Byte"); - - App.AssertOutputContains($"dotnet watch ๐Ÿ•ต๏ธ [WatchHotReloadApp ({tfm})] HotReloadException handler installed."); - App.AssertOutputContains($"dotnet watch ๐Ÿ•ต๏ธ [WatchHotReloadApp ({tfm})] Runtime rude edit detected:"); + // 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:"); - App.AssertOutputContains($"dotnet watch โš  [WatchHotReloadApp ({tfm})] " + + 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."); - App.AssertOutputContains(MessageDescriptor.RestartingApplication, $"WatchHotReloadApp ({tfm})"); + await App.WaitUntilOutputContains(MessageDescriptor.RestartingApplication, $"WatchHotReloadApp ({tfm})"); + + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains("System.Byte"); } [Fact] @@ -510,22 +519,22 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt() App.Start(testAsset, [], testFlags: TestFlags.ReadKeyFromStdin); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + 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.AssertOutputLineStartsWith(" โ” Do you want to restart your app? Yes (y) / No (n) / Always (a) / Never (v)", failure: _ => false); + await App.WaitUntilOutputContains(" โ” Do you want to restart your app? Yes (y) / No (n) / Always (a) / Never (v)"); await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains($"โŒ {programPath}(39,11): error ENC0023: Adding an abstract method or overriding an inherited method requires restarting the application."); + 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.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); @@ -534,12 +543,12 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt() // rude edit: deleting virtual method UpdateSourceFile(programPath, src => src.Replace("public virtual void F() {}", "")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains($"โŒš [auto-restart] {programPath}(39,1): error ENC0033: Deleting method 'F()' requires restarting the application."); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); + 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] @@ -566,25 +575,25 @@ public async Task AutoRestartOnNoEffectEdit(bool nonInteractive) App.Start(testAsset, nonInteractive ? ["--non-interactive"] : []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); App.Process.ClearOutput(); // top-level code change: UpdateSourceFile(programPath, src => src.Replace("Started", "")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains($"โŒš [auto-restart] {programPath}(17,19): warning ENC0118: Changing 'top-level code' might not have any effect until the application is restarted."); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exited"); - App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); - App.AssertOutputContains(""); + 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(""); App.Process.ClearOutput(); // valid edit: UpdateSourceFile(programPath, src => src.Replace("/* member placeholder */", "public void F() {}")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); } /// @@ -604,7 +613,7 @@ public async Task BaselineCompilationError() App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForFileChangeBeforeRestarting); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForFileChangeBeforeRestarting); UpdateSourceFile(programPath, """ System.Console.WriteLine(""); @@ -621,7 +630,7 @@ public async Task ChangeFileInFSharpProject() App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForFileChangeBeforeRestarting); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForFileChangeBeforeRestarting); UpdateSourceFile(Path.Combine(testAsset.Path, "Program.fs"), content => content.Replace("Hello World!", "")); @@ -654,17 +663,19 @@ open System.Threading App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + App.Process.ClearOutput(); UpdateSourceFile(sourcePath, content => content.Replace("Waiting", "")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - await App.AssertOutputLineStartsWith(""); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(""); + App.Process.ClearOutput(); UpdateSourceFile(sourcePath, content => content.Replace("", "")); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); - await App.AssertOutputLineStartsWith(""); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(""); } // Test is timing out on .NET Framework: https://github.com/dotnet/sdk/issues/41669 @@ -676,7 +687,7 @@ public async Task HandleTypeLoadFailure() App.Start(testAsset, [], "App"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); var newSrc = """ class DepSubType : Dep @@ -695,7 +706,7 @@ public static void Print() UpdateSourceFile(Path.Combine(testAsset.Path, "App", "Update.cs"), newSrc); - await App.AssertOutputLineStartsWith("Updated types: Printer"); + await App.WaitUntilOutputContains("Updated types: Printer"); } [Fact] @@ -720,11 +731,11 @@ class AppUpdateHandler App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); UpdateSourceFile(sourcePath, source.Replace("Console.WriteLine(\".\");", "Console.WriteLine(\"\");")); - await App.WaitForOutputLineContaining(""); + await App.WaitUntilOutputContains(""); await App.WaitUntilOutputContains( $"dotnet watch โš  [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Expected to find a static method 'ClearCache', 'UpdateApplication' or 'UpdateContent' on type 'AppUpdateHandler, WatchHotReloadApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' but neither exists."); @@ -759,11 +770,11 @@ class AppUpdateHandler App.Start(testAsset, []); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); UpdateSourceFile(sourcePath, source.Replace("Console.WriteLine(\".\");", "Console.WriteLine(\"\");")); - await App.WaitForOutputLineContaining(""); + await App.WaitUntilOutputContains(""); await App.WaitUntilOutputContains($"dotnet watch โš  [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exception from 'AppUpdateHandler.ClearCache': System.InvalidOperationException: Bug!"); @@ -799,7 +810,7 @@ public async Task GracefulTermination_Windows() App.Start(testAsset, [], testFlags: TestFlags.ReadKeyFromStdin); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains($"dotnet watch ๐Ÿ•ต๏ธ [WatchHotReloadApp ({tfm})] Windows Ctrl+C handling enabled."); @@ -807,7 +818,7 @@ public async Task GracefulTermination_Windows() App.SendControlC(); - await App.WaitForOutputLineContaining("Ctrl+C detected! Performing cleanup..."); + await App.WaitUntilOutputContains("Ctrl+C detected! Performing cleanup..."); await App.WaitUntilOutputContains("exited with exit code 0."); } @@ -830,7 +841,7 @@ public async Task GracefulTermination_Unix() App.Start(testAsset, [], testFlags: TestFlags.ReadKeyFromStdin); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); await App.WaitUntilOutputContains($"dotnet watch ๐Ÿ•ต๏ธ [WatchHotReloadApp ({tfm})] Posix signal handlers registered."); @@ -838,7 +849,7 @@ public async Task GracefulTermination_Unix() App.SendControlC(); - await App.WaitForOutputLineContaining("SIGTERM detected! Performing cleanup..."); + await App.WaitUntilOutputContains("SIGTERM detected! Performing cleanup..."); await App.WaitUntilOutputContains("exited with exit code 0."); } @@ -864,21 +875,21 @@ public async Task BlazorWasm(bool projectSpecifiesCapabilities) var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port], testFlags: TestFlags.MockBrowser); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); - App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); // Browser is launched based on blazor-devserver output "Now listening on: ...". await App.WaitUntilOutputContains(MessageDescriptor.LaunchingBrowser.GetMessage($"http://localhost:{port}", "")); // Middleware should have been loaded to blazor-devserver before the browser is launched: - App.AssertOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorWasmHotReloadMiddleware[0]"); - App.AssertOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserScriptMiddleware[0]"); - App.AssertOutputContains("Middleware loaded. Script /_framework/aspnetcore-browser-refresh.js"); - App.AssertOutputContains("Middleware loaded. Script /_framework/blazor-hotreload.js"); - App.AssertOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware"); - App.AssertOutputContains("Middleware loaded: DOTNET_MODIFIABLE_ASSEMBLIES=debug, __ASPNETCORE_BROWSER_TOOLS=true"); + await App.WaitUntilOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BlazorWasmHotReloadMiddleware[0]"); + await App.WaitUntilOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserScriptMiddleware[0]"); + await App.WaitUntilOutputContains("Middleware loaded. Script /_framework/aspnetcore-browser-refresh.js"); + await App.WaitUntilOutputContains("Middleware loaded. Script /_framework/blazor-hotreload.js"); + await App.WaitUntilOutputContains("dbug: Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware"); + await App.WaitUntilOutputContains("Middleware loaded: DOTNET_MODIFIABLE_ASSEMBLIES=debug, __ASPNETCORE_BROWSER_TOOLS=true"); // shouldn't see any agent messages (agent is not loaded into blazor-devserver): App.AssertOutputDoesNotContain("๐Ÿ•ต๏ธ"); @@ -889,16 +900,16 @@ public async Task BlazorWasm(bool projectSpecifiesCapabilities) """; UpdateSourceFile(Path.Combine(testAsset.Path, "Pages", "Index.razor"), newSource); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); // check project specified capapabilities: if (projectSpecifiesCapabilities) { - App.AssertOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddMethodToExistingType Baseline."); + await App.WaitUntilOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddMethodToExistingType Baseline."); } else { - App.AssertOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddFieldRva AddInstanceFieldToExistingType AddMethodToExistingType AddStaticFieldToExistingType Baseline ChangeCustomAttributes GenericAddFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod NewTypeDefinition UpdateParameters."); + await App.WaitUntilOutputContains("dotnet watch ๐Ÿ”ฅ Hot reload capabilities: AddExplicitInterfaceImplementation AddFieldRva AddInstanceFieldToExistingType AddMethodToExistingType AddStaticFieldToExistingType Baseline ChangeCustomAttributes GenericAddFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod NewTypeDefinition UpdateParameters."); } } @@ -920,8 +931,8 @@ public async Task BlazorWasm_MSBuildWarning() var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port], testFlags: TestFlags.MockBrowser); - await App.AssertOutputLineStartsWith("dotnet watch โš  msbuild: [Warning] Duplicate source file"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains("dotnet watch โš  msbuild: [Warning] Duplicate source file"); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); } [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/aspnetcore/issues/63759 @@ -933,11 +944,11 @@ public async Task BlazorWasm_Restart() var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port, "--non-interactive"], testFlags: TestFlags.ReadKeyFromStdin | TestFlags.MockBrowser); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); - App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); - App.AssertOutputContains(MessageDescriptor.PressCtrlRToRestart); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); + await App.WaitUntilOutputContains(MessageDescriptor.PressCtrlRToRestart); // Browser is launched based on blazor-devserver output "Now listening on: ...". await App.WaitUntilOutputContains(MessageDescriptor.LaunchingBrowser.GetMessage($"http://localhost:{port}", "")); @@ -958,17 +969,17 @@ public async Task BlazorWasmHosted() var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port], "blazorhosted", testFlags: TestFlags.MockBrowser); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); - App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); - App.AssertOutputContains(MessageDescriptor.ApplicationKind_BlazorHosted); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); + await App.WaitUntilOutputContains(MessageDescriptor.ApplicationKind_BlazorHosted); // client capabilities: - App.AssertOutputContains($"dotnet watch โŒš [blazorhosted ({tfm})] Project specifies capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType AddExplicitInterfaceImplementation."); + await App.WaitUntilOutputContains($"dotnet watch โŒš [blazorhosted ({tfm})] Project specifies capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType NewTypeDefinition ChangeCustomAttributes AddInstanceFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod UpdateParameters GenericAddFieldToExistingType AddExplicitInterfaceImplementation."); // server capabilities: - App.AssertOutputContains($"dotnet watch โŒš [blazorhosted ({tfm})] Capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition ChangeCustomAttributes UpdateParameters GenericUpdateMethod GenericAddMethodToExistingType GenericAddFieldToExistingType AddFieldRva AddExplicitInterfaceImplementation."); + await App.WaitUntilOutputContains($"dotnet watch โŒš [blazorhosted ({tfm})] Capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition ChangeCustomAttributes UpdateParameters GenericUpdateMethod GenericAddMethodToExistingType GenericAddFieldToExistingType AddFieldRva AddExplicitInterfaceImplementation."); } [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/aspnetcore/issues/63759 @@ -980,11 +991,11 @@ public async Task Razor_Component_ScopedCssAndStaticAssets() var port = TestOptions.GetTestPort(); App.Start(testAsset, ["--urls", "http://localhost:" + port], relativeProjectDirectory: "RazorApp", testFlags: TestFlags.MockBrowser); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); - App.AssertOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); - App.AssertOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); - App.AssertOutputContains(MessageDescriptor.LaunchingBrowser.GetMessage($"http://localhost:{port}", "")); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToUseBrowserRefresh); + await App.WaitUntilOutputContains(MessageDescriptor.ConfiguredToLaunchBrowser); + await App.WaitUntilOutputContains(MessageDescriptor.LaunchingBrowser.GetMessage($"http://localhost:{port}", "")); App.Process.ClearOutput(); var scopedCssPath = Path.Combine(testAsset.Path, "RazorClassLibrary", "Components", "Example.razor.css"); @@ -996,19 +1007,19 @@ public async Task Razor_Component_ScopedCssAndStaticAssets() """; UpdateSourceFile(scopedCssPath, newCss); - await App.WaitForOutputLineContaining(MessageDescriptor.StaticAssetsChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.StaticAssetsChangesApplied); await App.WaitUntilOutputContains(MessageDescriptor.NoCSharpChangesToApply); - App.AssertOutputContains(MessageDescriptor.SendingStaticAssetUpdateRequest.GetMessage("wwwroot/RazorClassLibrary.bundle.scp.css")); + await App.WaitUntilOutputContains(MessageDescriptor.SendingStaticAssetUpdateRequest.GetMessage("wwwroot/RazorClassLibrary.bundle.scp.css")); App.Process.ClearOutput(); var cssPath = Path.Combine(testAsset.Path, "RazorApp", "wwwroot", "app.css"); UpdateSourceFile(cssPath, content => content.Replace("background-color: white;", "background-color: red;")); - await App.WaitForOutputLineContaining(MessageDescriptor.StaticAssetsChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.StaticAssetsChangesApplied); await App.WaitUntilOutputContains(MessageDescriptor.NoCSharpChangesToApply); - App.AssertOutputContains(MessageDescriptor.SendingStaticAssetUpdateRequest.GetMessage("wwwroot/app.css")); + await App.WaitUntilOutputContains(MessageDescriptor.SendingStaticAssetUpdateRequest.GetMessage("wwwroot/app.css")); App.Process.ClearOutput(); } @@ -1034,33 +1045,33 @@ public async Task MauiBlazor() var tfm = $"{ToolsetInfo.CurrentTargetFramework}-{platform}"; App.Start(testAsset, ["-f", tfm]); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // update code file: var razorPath = Path.Combine(testAsset.Path, "Components", "Pages", "Home.razor"); UpdateSourceFile(razorPath, content => content.Replace("Hello, world!", "Updated")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); - App.AssertOutputContains("Microsoft.AspNetCore.Components.HotReload.HotReloadManager.UpdateApplication"); + await App.WaitUntilOutputContains("Microsoft.AspNetCore.Components.HotReload.HotReloadManager.UpdateApplication"); App.Process.ClearOutput(); // update static asset: var cssPath = Path.Combine(testAsset.Path, "wwwroot", "css", "app.css"); UpdateSourceFile(cssPath, content => content.Replace("background-color: white;", "background-color: red;")); - await App.WaitForOutputLineContaining(MessageDescriptor.StaticAssetsChangesApplied); - App.AssertOutputContains("Microsoft.AspNetCore.Components.WebView.StaticContentHotReloadManager.UpdateContent"); - App.AssertOutputContains(MessageDescriptor.NoCSharpChangesToApply); + await App.WaitUntilOutputContains(MessageDescriptor.StaticAssetsChangesApplied); + await App.WaitUntilOutputContains("Microsoft.AspNetCore.Components.WebView.StaticContentHotReloadManager.UpdateContent"); + await App.WaitUntilOutputContains(MessageDescriptor.NoCSharpChangesToApply); App.Process.ClearOutput(); // update scoped css: var scopedCssPath = Path.Combine(testAsset.Path, "Components", "Pages", "Counter.razor.css"); UpdateSourceFile(scopedCssPath, content => content.Replace("background-color: green", "background-color: red")); - await App.WaitForOutputLineContaining(MessageDescriptor.StaticAssetsChangesApplied); - App.AssertOutputContains("Microsoft.AspNetCore.Components.WebView.StaticContentHotReloadManager.UpdateContent"); - App.AssertOutputContains(MessageDescriptor.NoCSharpChangesToApply); + await App.WaitUntilOutputContains(MessageDescriptor.StaticAssetsChangesApplied); + await App.WaitUntilOutputContains("Microsoft.AspNetCore.Components.WebView.StaticContentHotReloadManager.UpdateContent"); + await App.WaitUntilOutputContains(MessageDescriptor.NoCSharpChangesToApply); } // Test is timing out on .NET Framework: https://github.com/dotnet/sdk/issues/41669 @@ -1072,7 +1083,7 @@ public async Task HandleMissingAssemblyFailure() App.Start(testAsset, [], "App"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); var newSrc = /* lang=c#-test */""" using System; @@ -1097,7 +1108,7 @@ public static void Print() File.WriteAllText(Path.Combine(testAsset.Path, "App", "Update.cs"), newSrc); - await App.AssertOutputLineStartsWith("Updated types: Printer"); + await App.WaitUntilOutputContains("Updated types: Printer"); } [Theory] @@ -1134,7 +1145,7 @@ public static void PrintFileName([CallerFilePathAttribute] string filePath = nul App.Start(testAsset, [], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // rename the file: if (useMove) @@ -1188,7 +1199,7 @@ public static void PrintDirectoryName([CallerFilePathAttribute] string filePath App.Start(testAsset, ["--non-interactive"], "AppWithDeps"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); // rename the directory: if (useMove) @@ -1207,7 +1218,7 @@ public static void PrintDirectoryName([CallerFilePathAttribute] string filePath // dotnet-watch may observe the delete separately from the new file write. // If so, rude edit is reported, the app is auto-restarted and we should observe the final result. - await App.AssertOutputLineStartsWith("> NewSubdir", failure: _ => false); + await App.WaitUntilOutputContains("> NewSubdir"); } [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/aspnetcore/issues/63759 @@ -1226,18 +1237,18 @@ public async Task Aspire_BuildError_ManualRestart() App.Start(testAsset, ["-lp", "http"], relativeProjectDirectory: "WatchAspire.AppHost", testFlags: TestFlags.ReadKeyFromStdin); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + 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 โญ Session started: #3"); - App.AssertOutputContains("dotnet watch โญ Session started: #1"); - App.AssertOutputContains("dotnet watch โญ Session started: #2"); + await App.WaitUntilOutputContains("dotnet watch โญ Session started: #1"); + await App.WaitUntilOutputContains("dotnet watch โญ Session started: #2"); // MigrationService terminated: - App.AssertOutputContains("dotnet watch โญ [#1] Sending 'sessionTerminated'"); + await App.WaitUntilOutputContains("dotnet watch โญ [#1] Sending 'sessionTerminated'"); // working directory of the service should be it's project directory: await App.WaitUntilOutputContains($"ApiService working directory: '{Path.GetDirectoryName(serviceProjectPath)}'"); @@ -1247,9 +1258,9 @@ public async Task Aspire_BuildError_ManualRestart() serviceSourcePath, serviceSource.Replace("Enumerable.Range(1, 5)", "Enumerable.Range(1, 10)")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); - App.AssertOutputContains("Using Aspire process launcher."); + 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: "))); @@ -1261,24 +1272,24 @@ public async Task Aspire_BuildError_ManualRestart() 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.WaitForOutputLineContaining(" โ” Do you want to restart these projects? Yes (y) / No (n) / Always (a) / Never (v)"); + await App.WaitUntilOutputContains(" โ” Do you want to restart these projects? Yes (y) / No (n) / Always (a) / Never (v)"); await App.WaitUntilOutputContains(MessageDescriptor.RestartNeededToApplyChanges); - App.AssertOutputContains($"dotnet watch โŒ {serviceSourcePath}(40,1): error ENC0020: Renaming record 'WeatherForecast' requires restarting the application."); - App.AssertOutputContains("dotnet watch โŒš Affected projects:"); - App.AssertOutputContains("dotnet watch โŒš WatchAspire.ApiService"); + 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.WaitForOutputLineContaining(MessageDescriptor.FixBuildError); + await App.WaitUntilOutputContains(MessageDescriptor.FixBuildError); - App.AssertOutputContains("Application is shutting down..."); + await App.WaitUntilOutputContains("Application is shutting down..."); - App.AssertOutputContains($"[WatchAspire.ApiService ({tfm})] Exited"); + await App.WaitUntilOutputContains($"[WatchAspire.ApiService ({tfm})] Exited"); - App.AssertOutputContains(MessageDescriptor.Building.GetMessage(serviceProjectPath)); - App.AssertOutputContains("error CS0246: The type or namespace name 'WeatherForecast' could not be found"); + 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: @@ -1286,27 +1297,28 @@ public async Task Aspire_BuildError_ManualRestart() serviceSourcePath, serviceSource.Replace("WeatherForecast", "WeatherForecast2")); - await App.WaitForOutputLineContaining(MessageDescriptor.ProjectsRestarted.GetMessage(1)); + await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRestarted.GetMessage(1)); - App.AssertOutputContains(MessageDescriptor.BuildSucceeded.GetMessage(serviceProjectPath)); - App.AssertOutputContains(MessageDescriptor.ProjectsRebuilt); - App.AssertOutputContains($"dotnet watch โญ Starting project: {serviceProjectPath}"); + await App.WaitUntilOutputContains(MessageDescriptor.BuildSucceeded.GetMessage(serviceProjectPath)); + await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRebuilt); + await App.WaitUntilOutputContains($"dotnet watch โญ Starting project: {serviceProjectPath}"); App.Process.ClearOutput(); App.SendControlC(); - await App.WaitForOutputLineContaining(MessageDescriptor.ShutdownRequested); + 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 ..."); - App.AssertOutputContains("dotnet watch โญ Stop session #1"); - App.AssertOutputContains("dotnet watch โญ Stop session #2"); - App.AssertOutputContains("dotnet watch โญ Stop session #3"); - App.AssertOutputContains("dotnet watch โญ [#2] Sending 'sessionTerminated'"); - App.AssertOutputContains("dotnet watch โญ [#3] Sending 'sessionTerminated'"); + await App.WaitUntilOutputContains("dotnet watch โญ Stop session #1"); + await App.WaitUntilOutputContains("dotnet watch โญ Stop session #2"); + await App.WaitUntilOutputContains("dotnet watch โญ Stop session #3"); + await App.WaitUntilOutputContains("dotnet watch โญ [#2] Sending 'sessionTerminated'"); + await App.WaitUntilOutputContains("dotnet watch โญ [#3] Sending 'sessionTerminated'"); } [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/aspnetcore/issues/63759 @@ -1322,18 +1334,18 @@ public async Task Aspire_NoEffect_AutoRestart() App.Start(testAsset, ["-lp", "http", "--non-interactive"], relativeProjectDirectory: "WatchAspire.AppHost"); - await App.WaitForOutputLineContaining(MessageDescriptor.WaitingForChanges); + await App.WaitUntilOutputContains(MessageDescriptor.WaitingForChanges); + + await App.WaitUntilOutputContains("dotnet watch โญ Session started: #1"); + 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 โญ Session started: #3"); - // other services are waiting for completion of MigrationService: - App.AssertOutputContains("dotnet watch โญ Session started: #1"); - App.AssertOutputContains(MessageDescriptor.Exited, $"WatchAspire.MigrationService ({tfm})"); - App.AssertOutputContains("dotnet watch โญ [#1] Sending 'sessionTerminated'"); - - // migration service output should not be printed to dotnet-watch output, it hsould be sent via DCP as a notification: - App.AssertOutputContains("dotnet watch โญ [#1] Sending 'serviceLogs': log_message=' Migration complete', is_std_err=False"); App.AssertOutputDoesNotContain(new Regex("^ +Migration complete")); App.Process.ClearOutput(); @@ -1341,7 +1353,7 @@ public async Task Aspire_NoEffect_AutoRestart() // no-effect edit: UpdateSourceFile(webSourcePath, src => src.Replace("/* top-level placeholder */", "builder.Services.AddRazorComponents();")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); + await App.WaitUntilOutputContains(MessageDescriptor.ManagedCodeChangesApplied); await App.WaitUntilOutputContains("dotnet watch โญ Session started: #3"); await App.WaitUntilOutputContains(MessageDescriptor.ProjectsRestarted.GetMessage(1)); App.AssertOutputDoesNotContain("โš "); @@ -1355,8 +1367,8 @@ public async Task Aspire_NoEffect_AutoRestart() // lambda body edit: UpdateSourceFile(webSourcePath, src => src.Replace("Hello world!", "")); - await App.WaitForOutputLineContaining(MessageDescriptor.ManagedCodeChangesApplied); - App.AssertOutputContains($"dotnet watch ๐Ÿ•ต๏ธ [WatchAspire.Web ({tfm})] Updates applied."); + 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("โš ");