Skip to content
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

[dotnet-watch] Misc test fixes #45575

Open
wants to merge 3 commits into
base: release/9.0.2xx
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,10 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke
};
}

if (!await BuildProjectAsync(rootProjectOptions.ProjectPath, rootProjectOptions.BuildArguments, iterationCancellationToken))
var (buildSucceeded, buildOutput, _) = await BuildProjectAsync(rootProjectOptions.ProjectPath, rootProjectOptions.BuildArguments, iterationCancellationToken);
BuildUtilities.ReportBuildOutput(Context.Reporter, buildOutput, buildSucceeded, projectDisplay: rootProjectOptions.ProjectPath);
if (!buildSucceeded)
{
// error has been reported:
continue;
}

Expand Down Expand Up @@ -334,7 +335,12 @@ void FileChangedCallback(ChangedPath change)
var buildResults = await Task.WhenAll(
projectsToRebuild.Values.Select(projectPath => BuildProjectAsync(projectPath, rootProjectOptions.BuildArguments, iterationCancellationToken)));

if (buildResults.All(success => success))
foreach (var (success, output, projectPath) in buildResults)
{
BuildUtilities.ReportBuildOutput(Context.Reporter, output, success, projectPath);
}

if (buildResults.All(result => result.success))
{
break;
}
Expand Down Expand Up @@ -815,7 +821,8 @@ await FileWatcher.WaitForFileChangeAsync(
}
}

private async Task<bool> BuildProjectAsync(string projectPath, IReadOnlyList<string> buildArguments, CancellationToken cancellationToken)
private async Task<(bool success, ImmutableArray<OutputLine> output, string projectPath)> BuildProjectAsync(
string projectPath, IReadOnlyList<string> buildArguments, CancellationToken cancellationToken)
{
var buildOutput = new List<OutputLine>();

Expand All @@ -834,17 +841,10 @@ private async Task<bool> BuildProjectAsync(string projectPath, IReadOnlyList<str
Arguments = ["build", projectPath, "-consoleLoggerParameters:NoSummary;Verbosity=minimal", .. buildArguments]
};

Context.Reporter.Output($"Building '{projectPath}' ...");
Context.Reporter.Output($"Building {projectPath} ...");

var exitCode = await ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: false, launchResult: null, cancellationToken);
BuildUtilities.ReportBuildOutput(Context.Reporter, buildOutput, verboseOutput: exitCode == 0);

if (exitCode == 0)
{
Context.Reporter.Output("Build succeeded.");
}

return exitCode == 0;
return (exitCode == 0, buildOutput.ToImmutableArray(), projectPath);
}

private string GetRelativeFilePath(string path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal class MSBuildFileSetFactory(
reporter.Output($"MSBuild output from target '{TargetName}':");
}

BuildUtilities.ReportBuildOutput(reporter, capturedOutput, verboseOutput: success);
BuildUtilities.ReportBuildOutput(reporter, capturedOutput, success, projectDisplay: null);
if (!success)
{
return null;
Expand Down
9 changes: 9 additions & 0 deletions src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ public static async Task<int> RunAsync(ProcessSpec processSpec, IReporter report
try
{
await process.WaitForExitAsync(processTerminationToken);

// ensures that all process output has been reported:
try
{
process.WaitForExit();
}
catch
{
}
}
catch (OperationCanceledException)
{
Expand Down
17 changes: 14 additions & 3 deletions src/BuiltInTools/dotnet-watch/Utilities/BuildUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ namespace Microsoft.DotNet.Watch;

internal static partial class BuildUtilities
{
private const string BuildEmoji = "🔨";
private static readonly Regex s_buildDiagnosticRegex = GetBuildDiagnosticRegex();

[GeneratedRegex(@"[^:]+: (error|warning) [A-Za-z]+[0-9]+: .+")]
private static partial Regex GetBuildDiagnosticRegex();

public static void ReportBuildOutput(IReporter reporter, IEnumerable<OutputLine> buildOutput, bool verboseOutput)
public static void ReportBuildOutput(IReporter reporter, IEnumerable<OutputLine> buildOutput, bool success, string? projectDisplay)
{
const string BuildEmoji = "🔨";
if (projectDisplay != null)
{
if (success)
{
reporter.Output($"Build succeeded: {projectDisplay}", BuildEmoji);
}
else
{
reporter.Output($"Build failed: {projectDisplay}", BuildEmoji);
}
}

foreach (var (line, isError) in buildOutput)
{
Expand All @@ -33,7 +44,7 @@ public static void ReportBuildOutput(IReporter reporter, IEnumerable<OutputLine>
reporter.Warn(line);
}
}
else if (verboseOutput)
else if (success)
{
reporter.Verbose(line, BuildEmoji);
}
Expand Down
41 changes: 21 additions & 20 deletions test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class AppUpdateHandler
await App.AssertOutputLineStartsWith("Updated");

await App.WaitUntilOutputContains(
"dotnet watch ⚠ [WatchHotReloadApp (net9.0)] Expected to find a static method 'ClearCache' or 'UpdateApplication' on type 'AppUpdateHandler, WatchHotReloadApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' but neither exists.");
$"dotnet watch ⚠ [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Expected to find a static method 'ClearCache' or 'UpdateApplication' on type 'AppUpdateHandler, WatchHotReloadApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' but neither exists.");
}

[Theory]
Expand Down Expand Up @@ -287,11 +287,11 @@ class AppUpdateHandler

await App.AssertOutputLineStartsWith("Updated");

await App.WaitUntilOutputContains("dotnet watch ⚠ [WatchHotReloadApp (net9.0)] Exception from 'System.Action`1[System.Type[]]': System.InvalidOperationException: Bug!");
await App.WaitUntilOutputContains($"dotnet watch ⚠ [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Exception from 'System.Action`1[System.Type[]]': System.InvalidOperationException: Bug!");

if (verbose)
{
await App.WaitUntilOutputContains("dotnet watch 🕵️ [WatchHotReloadApp (net9.0)] Deltas applied.");
await App.WaitUntilOutputContains($"dotnet watch 🕵️ [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Deltas applied.");
}
else
{
Expand Down Expand Up @@ -347,7 +347,7 @@ public async Task BlazorWasm(bool projectSpecifiesCapabilities)
""";

UpdateSourceFile(Path.Combine(testAsset.Path, "Pages", "Index.razor"), newSource);
await App.AssertOutputLineStartsWith(MessageDescriptor.HotReloadSucceeded, "blazorwasm (net9.0)");
await App.AssertOutputLineStartsWith(MessageDescriptor.HotReloadSucceeded, $"blazorwasm ({ToolsetInfo.CurrentTargetFramework})");

// check project specified capapabilities:
if (projectSpecifiesCapabilities)
Expand Down Expand Up @@ -410,8 +410,8 @@ public async Task Razor_Component_ScopedCssAndStaticAssets()
await App.AssertOutputLineStartsWith("dotnet watch 🔥 Hot reload change handled");

App.AssertOutputContains($"dotnet watch ⌚ Handling file change event for scoped css file {scopedCssPath}.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorClassLibrary (net9.0)] No refresh server.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorApp (net9.0)] Refreshing browser.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorClassLibrary ({ToolsetInfo.CurrentTargetFramework})] No refresh server.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorApp ({ToolsetInfo.CurrentTargetFramework})] Refreshing browser.");
App.AssertOutputContains($"dotnet watch 🔥 Hot reload of scoped css succeeded.");
App.AssertOutputContains($"dotnet watch ⌚ No C# changes to apply.");
App.Process.ClearOutput();
Expand All @@ -422,7 +422,7 @@ public async Task Razor_Component_ScopedCssAndStaticAssets()
await App.AssertOutputLineStartsWith("dotnet watch 🔥 Hot reload change handled");

App.AssertOutputContains($"dotnet watch ⌚ Sending static file update request for asset 'app.css'.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorApp (net9.0)] Refreshing browser.");
App.AssertOutputContains($"dotnet watch ⌚ [RazorApp ({ToolsetInfo.CurrentTargetFramework})] Refreshing browser.");
App.AssertOutputContains($"dotnet watch 🔥 Hot Reload of static files succeeded.");
App.AssertOutputContains($"dotnet watch ⌚ No C# changes to apply.");
App.Process.ClearOutput();
Expand Down Expand Up @@ -620,6 +620,7 @@ public static void PrintDirectoryName([CallerFilePathAttribute] string filePath
[Fact]
public async Task Aspire()
{
var tfm = ToolsetInfo.CurrentTargetFramework;
var testAsset = TestAssets.CopyTestAsset("WatchAspire")
.WithSource();

Expand All @@ -645,8 +646,8 @@ public async Task Aspire()
await App.AssertOutputLineStartsWith("dotnet watch 🔥 Hot reload change handled");

App.AssertOutputContains("Using Aspire process launcher.");
App.AssertOutputContains(MessageDescriptor.HotReloadSucceeded, "WatchAspire.AppHost (net9.0)");
App.AssertOutputContains(MessageDescriptor.HotReloadSucceeded, "WatchAspire.ApiService (net9.0)");
App.AssertOutputContains(MessageDescriptor.HotReloadSucceeded, $"WatchAspire.AppHost ({tfm})");
App.AssertOutputContains(MessageDescriptor.HotReloadSucceeded, $"WatchAspire.ApiService ({tfm})");

// 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: ")));
Expand All @@ -672,16 +673,16 @@ public async Task Aspire()
// We don't have means to gracefully terminate process on Windows, see https://github.com/dotnet/runtime/issues/109432
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
App.AssertOutputContains("dotnet watch ❌ [WatchAspire.ApiService (net9.0)] Exited with error code -1");
App.AssertOutputContains($"dotnet watch ❌ [WatchAspire.ApiService ({tfm})] Exited with error code -1");
}
else
{
// Unix process may return exit code = 128 + SIGTERM
// dotnet watch ❌ [WatchAspire.ApiService (net9.0)] Exited with error code 143
App.AssertOutputContains("[WatchAspire.ApiService (net9.0)] Exited");
// Exited with error code 143
App.AssertOutputContains($"[WatchAspire.ApiService ({tfm})] Exited");
}

App.AssertOutputContains($"dotnet watch ⌚ Building '{serviceProjectPath}' ...");
App.AssertOutputContains($"dotnet watch ⌚ Building {serviceProjectPath} ...");
App.AssertOutputContains("error CS0246: The type or namespace name 'WeatherForecast' could not be found");
App.Process.ClearOutput();

Expand All @@ -690,9 +691,9 @@ public async Task Aspire()
serviceSourcePath,
originalSource.Replace("WeatherForecast", "WeatherForecast2"));

await App.AssertOutputLineStartsWith("dotnet watch ⌚ [WatchAspire.ApiService (net9.0)] Capabilities");
await App.AssertOutputLineStartsWith($"dotnet watch ⌚ [WatchAspire.ApiService ({tfm})] Capabilities");

App.AssertOutputContains("dotnet watch Build succeeded.");
App.AssertOutputContains($"dotnet watch 🔨 Build succeeded: {serviceProjectPath}");
App.AssertOutputContains("dotnet watch 🔥 Project baselines updated.");
App.AssertOutputContains($"dotnet watch ⭐ Starting project: {serviceProjectPath}");

Expand All @@ -703,15 +704,15 @@ public async Task Aspire()
// We don't have means to gracefully terminate process on Windows, see https://github.com/dotnet/runtime/issues/109432
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
await App.AssertOutputLineStartsWith("dotnet watch ❌ [WatchAspire.ApiService (net9.0)] Exited with error code -1");
await App.AssertOutputLineStartsWith("dotnet watch ❌ [WatchAspire.AppHost (net9.0)] Exited with error code -1");
await App.AssertOutputLineStartsWith($"dotnet watch ❌ [WatchAspire.ApiService ({tfm})] Exited with error code -1");
await App.AssertOutputLineStartsWith($"dotnet watch ❌ [WatchAspire.AppHost ({tfm})] Exited with error code -1");
}
else
{
// Unix process may return exit code = 128 + SIGTERM
// dotnet watch ❌ [WatchAspire.ApiService (net9.0)] Exited with error code 143
await App.AssertOutputLine(line => line.Contains("[WatchAspire.ApiService (net9.0)] Exited"), failure: _ => false);
await App.AssertOutputLine(line => line.Contains("[WatchAspire.AppHost (net9.0)] Exited"), failure: _ => false);
// Exited with error code 143
await App.AssertOutputLine(line => line.Contains($"[WatchAspire.ApiService ({tfm})] Exited"), failure: _ => false);
await App.AssertOutputLine(line => line.Contains($"[WatchAspire.AppHost ({tfm})] Exited"), failure: _ => false);
}

await App.AssertOutputLineStartsWith("dotnet watch ⭐ Waiting for server to shutdown ...");
Expand Down
2 changes: 1 addition & 1 deletion test/dotnet-watch.Tests/Utilities/AwaitableProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private void OnData(object sender, DataReceivedEventArgs args)
line = line.StripTerminalLoggerProgressIndicators();
}

WriteTestOutput($"{DateTime.Now}: post: '{line}'");
WriteTestOutput(line);
_source.Post(line);
}

Expand Down
1 change: 1 addition & 0 deletions test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public async ValueTask WaitUntilOutputContains(string message)
{
if (!Process.Output.Any(line => line.Contains(message)))
{
Logger.WriteLine($"[TEST] Test waiting for output: '{message}'");
_ = await AssertOutputLine(line => line.Contains(message));
}
}
Expand Down
Loading