From 02d33a7c642426fca913b7dde558458ae905be80 Mon Sep 17 00:00:00 2001 From: tmat Date: Thu, 12 Jun 2025 12:08:06 -0700 Subject: [PATCH 1/2] Implement posix signal registration --- ...osoft.Extensions.DotNetDeltaApplier.csproj | 2 +- .../DotNetDeltaApplier/StartupHook.cs | 46 ++++++++++++- .../dotnet-watch/DotNetWatcher.cs | 2 +- .../HotReload/BlazorWebAssemblyAppModel.cs | 35 ++++------ .../BlazorWebAssemblyHostedAppModel.cs | 32 ++++----- .../dotnet-watch/HotReload/DefaultAppModel.cs | 8 +-- .../dotnet-watch/HotReload/DeltaApplier.cs | 2 - .../HotReload/HotReloadAppModel.cs | 39 ++++++++--- .../dotnet-watch/HotReload/ProjectLauncher.cs | 4 +- .../dotnet-watch/Internal/ProcessRunner.cs | 58 ++++++++++++---- .../dotnet-watch/dotnet-watch.csproj | 23 ++++++- .../Aspire/AspireServiceFactoryTests.cs | 16 ++--- .../Browser/BrowserConnectorTests.cs | 2 +- .../CommandLineOptionsTests.cs | 68 +++++++++---------- .../ConsoleReporterTests.cs | 4 +- .../FileSetSerializerTests.cs | 4 +- test/dotnet-watch.Tests/FileWatcherTests.cs | 2 +- .../HotReload/ApplyDeltaTests.cs | 34 +++++----- .../HotReload/CompilationHandlerTests.cs | 2 +- .../HotReload/HotReloadDotNetWatcherTests.cs | 2 +- .../HotReload/RuntimeProcessLauncherTests.cs | 10 +-- .../EnvironmentVariablesBuilderTests.cs | 4 +- .../LaunchSettingsProfileTest.cs | 4 +- .../MSBuildEvaluationFilterTest.cs | 8 +-- .../MsBuildFileSetFactoryTest.cs | 20 +++--- test/dotnet-watch.Tests/NoRestoreTests.cs | 20 +++--- .../Utilities/TestOptions.cs | 8 ++- .../Watch/BrowserLaunchTests.cs | 4 +- .../Watch/DotNetWatcherTests.cs | 14 ++-- .../Watch/GlobbingAppTests.cs | 2 +- test/dotnet-watch.Tests/Watch/ProgramTests.cs | 30 ++++---- .../Watch/Utilities/WatchableApp.cs | 3 + 32 files changed, 307 insertions(+), 205 deletions(-) diff --git a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj index 9dd0d32e7fdb..22db61517698 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj +++ b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj @@ -7,7 +7,7 @@ dotnet-watch may inject this assembly to .NET 6.0+ app, so we can't target a newer version. At the same time source build requires us to not target 6.0, so we fall back to netstandard. --> - netstandard2.1 + netstandard2.1;net10.0 MicrosoftAspNetCore diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs index 59787ed4f47b..6cd3d9edca1e 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs +++ b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs @@ -13,7 +13,15 @@ internal sealed class StartupHook private const int ConnectionTimeoutMS = 5000; private static readonly bool s_logToStandardOutput = Environment.GetEnvironmentVariable(AgentEnvironmentVariables.HotReloadDeltaClientLogMessages) == "1"; - private static readonly string s_namedPipeName = Environment.GetEnvironmentVariable(AgentEnvironmentVariables.DotNetWatchHotReloadNamedPipeName); + private static readonly string? s_namedPipeName = Environment.GetEnvironmentVariable(AgentEnvironmentVariables.DotNetWatchHotReloadNamedPipeName); + +#if NET10_0_OR_GREATER + private const string TargetFramework = "net10.0"; + + private static PosixSignalRegistration? s_signalRegistration; +#else + private const string TargetFramework = "netstandard2.1"; +#endif /// /// Invoked by the runtime when the containing assembly is listed in DOTNET_STARTUP_HOOKS. @@ -22,12 +30,18 @@ public static void Initialize() { var processPath = Environment.GetCommandLineArgs().FirstOrDefault(); - Log($"Loaded into process: {processPath}"); + Log($"Loaded into process: {processPath} ({TargetFramework})"); HotReloadAgent.ClearHotReloadEnvironmentVariables(typeof(StartupHook)); Log($"Connecting to hot-reload server"); + if (s_namedPipeName == null) + { + Log($"Environment variable {AgentEnvironmentVariables.DotNetWatchHotReloadNamedPipeName} has no value"); + return; + } + // Connect to the pipe synchronously. // // If a debugger is attached and there is a breakpoint in the startup code connecting asynchronously would @@ -48,22 +62,50 @@ public static void Initialize() return; } + RegisterPosixSignalHandlers(); + var agent = new HotReloadAgent(); try { // block until initialization completes: InitializeAsync(pipeClient, agent, CancellationToken.None).GetAwaiter().GetResult(); +#pragma warning disable CA2025 // Ensure tasks using 'IDisposable' instances complete before the instances are disposed // fire and forget: _ = ReceiveAndApplyUpdatesAsync(pipeClient, agent, initialUpdates: false, CancellationToken.None); +#pragma warning restore } catch (Exception ex) { Log(ex.Message); pipeClient.Dispose(); + agent.Dispose(); } } + private static void RegisterPosixSignalHandlers() + { +#if NET10_0_OR_GREATER + // Register a handler for SIGTERM to allow graceful shutdown of the application on Unix. + // See https://github.com/dotnet/docs/issues/46226. + + // Note: registered handlers are executed in reverse order of their registration. + // Since the startup hook is executed before any code of the application, it is the first handler registered and thus the last to run. + + s_signalRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context => + { + Log($"SIGTERM received. Cancel={context.Cancel}"); + + if (!context.Cancel) + { + Environment.Exit(0); + } + }); + + Log("Posix signal handlers registered."); +#endif + } + private static async ValueTask InitializeAsync(NamedPipeClientStream pipeClient, HotReloadAgent agent, CancellationToken cancellationToken) { agent.Reporter.Report("Writing capabilities: " + agent.Capabilities, AgentMessageSeverity.Verbose); diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs index 36714a4c200f..b4b3a67b87b1 100644 --- a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs @@ -62,7 +62,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke }; var browserRefreshServer = (projectRootNode != null) - ? await browserConnector.GetOrCreateBrowserRefreshServerAsync(projectRootNode, processSpec, environmentBuilder, Context.RootProjectOptions, DefaultAppModel.Instance, shutdownCancellationToken) + ? await browserConnector.GetOrCreateBrowserRefreshServerAsync(projectRootNode, processSpec, environmentBuilder, Context.RootProjectOptions, new DefaultAppModel(projectRootNode), shutdownCancellationToken) : null; environmentBuilder.SetProcessEnvironmentVariables(processSpec); diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs index b666406f2a91..dd2da0750c4f 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyAppModel.cs @@ -5,30 +5,23 @@ namespace Microsoft.DotNet.Watch; -internal abstract partial class HotReloadAppModel +/// +/// Blazor client-only WebAssembly app. +/// +internal sealed class BlazorWebAssemblyAppModel(ProjectGraphNode clientProject) + // Blazor WASM does not need agent injected as all changes are applied in the browser, the process being launched is a dev server. + : HotReloadAppModel(agentInjectionProject: null) { - /// - /// Blazor client-only WebAssembly app. - /// - internal sealed class BlazorWebAssemblyAppModel(ProjectGraphNode clientProject) : HotReloadAppModel - { - public override bool RequiresBrowserRefresh => true; - - /// - /// Blazor WASM does not need dotnet applier as all changes are applied in the browser, - /// the process being launched is a dev server. - /// - public override bool InjectDeltaApplier => false; + public override bool RequiresBrowserRefresh => true; - public override DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter) + public override DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter) + { + if (browserRefreshServer == null) { - if (browserRefreshServer == null) - { - // error has been reported earlier - return null; - } - - return new BlazorWebAssemblyDeltaApplier(processReporter, browserRefreshServer, clientProject); + // error has been reported earlier + return null; } + + return new BlazorWebAssemblyDeltaApplier(processReporter, browserRefreshServer, clientProject); } } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs index 94f8a467b408..c361deb0d88c 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyHostedAppModel.cs @@ -5,26 +5,24 @@ namespace Microsoft.DotNet.Watch; -internal abstract partial class HotReloadAppModel +/// +/// Blazor WebAssembly app hosted by an ASP.NET Core app. +/// App has a client and server projects and deltas are applied to both processes. +/// Agent is injected into the server process. The client process is updated via WebSocketScriptInjection.js injected into the browser. +/// +internal sealed class BlazorWebAssemblyHostedAppModel(ProjectGraphNode clientProject, ProjectGraphNode serverProject) + : HotReloadAppModel(agentInjectionProject: serverProject) { - /// - /// Blazor WebAssembly app hosted by an ASP.NET Core app. - /// App has a client and server projects and deltas are applied to both processes. - /// - internal sealed class BlazorWebAssemblyHostedAppModel(ProjectGraphNode clientProject) : HotReloadAppModel - { - public override bool RequiresBrowserRefresh => true; - public override bool InjectDeltaApplier => true; + public override bool RequiresBrowserRefresh => true; - public override DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter) + public override DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter) + { + if (browserRefreshServer == null) { - if (browserRefreshServer == null) - { - // error has been reported earlier - return null; - } - - return new BlazorWebAssemblyHostedDeltaApplier(processReporter, browserRefreshServer, clientProject); + // error has been reported earlier + return null; } + + return new BlazorWebAssemblyHostedDeltaApplier(processReporter, browserRefreshServer, clientProject); } } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs index b104de3c263a..8cd7d5eb92c9 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/DefaultAppModel.cs @@ -1,17 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Build.Graph; + namespace Microsoft.DotNet.Watch; /// /// Default model. /// -internal sealed class DefaultAppModel : HotReloadAppModel +internal sealed class DefaultAppModel(ProjectGraphNode project) + : HotReloadAppModel(agentInjectionProject: project) { - public static readonly DefaultAppModel Instance = new(); - public override bool RequiresBrowserRefresh => false; - public override bool InjectDeltaApplier => true; public override DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter) => new DefaultDeltaApplier(processReporter); diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs index 852c9295e949..4fb5e4b9d6fe 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/DeltaApplier.cs @@ -12,8 +12,6 @@ internal abstract class DeltaApplier(IReporter reporter) : IDisposable { public readonly IReporter Reporter = reporter; - public static readonly string StartupHookPath = Path.Combine(AppContext.BaseDirectory, "hotreload", "Microsoft.Extensions.DotNetDeltaApplier.dll"); - public abstract void CreateConnection(string namedPipeName, CancellationToken cancellationToken); /// diff --git a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs index 595c0a52b4a2..2d1788425e70 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadAppModel.cs @@ -1,21 +1,38 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Execution; +using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Graph; namespace Microsoft.DotNet.Watch; -internal abstract partial class HotReloadAppModel +internal abstract partial class HotReloadAppModel(ProjectGraphNode? agentInjectionProject) { public abstract bool RequiresBrowserRefresh { get; } + public abstract DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter); + /// - /// True to inject delta applier to the process. + /// Returns true and the path to the client agent implementation binary if the application needs the agent to be injected. /// - public abstract bool InjectDeltaApplier { get; } + public bool TryGetStartupHookPath([NotNullWhen(true)] out string? path) + { + if (agentInjectionProject == null) + { + path = null; + return false; + } - public abstract DeltaApplier? CreateDeltaApplier(BrowserRefreshServer? browserRefreshServer, IReporter processReporter); + var hookTargetFramework = agentInjectionProject.GetTargetFramework() switch + { + // Note: Hot Reload is only supported on net6.0+ + "net6.0" or "net7.0" or "net8.0" or "net9.0" => "netstandard2.1", + _ => "net10.0", + }; + + path = Path.Combine(AppContext.BaseDirectory, "hotreload", hookTargetFramework, "Microsoft.Extensions.DotNetDeltaApplier.dll"); + return true; + } public static HotReloadAppModel InferFromProject(ProjectGraphNode projectNode, IReporter reporter) { @@ -24,7 +41,7 @@ public static HotReloadAppModel InferFromProject(ProjectGraphNode projectNode, I var queue = new Queue(); queue.Enqueue(projectNode); - ProjectInstance? aspnetCoreProject = null; + ProjectGraphNode? aspnetCoreProject = null; var visited = new HashSet(); @@ -37,17 +54,17 @@ public static HotReloadAppModel InferFromProject(ProjectGraphNode projectNode, I { if (item.EvaluatedInclude == "AspNetCore") { - aspnetCoreProject = currentNode.ProjectInstance; + aspnetCoreProject = currentNode; break; } if (item.EvaluatedInclude == "WebAssembly") { // We saw a previous project that was AspNetCore. This must be a blazor hosted app. - if (aspnetCoreProject is not null && aspnetCoreProject != currentNode.ProjectInstance) + if (aspnetCoreProject is not null && aspnetCoreProject.ProjectInstance != currentNode.ProjectInstance) { - reporter.Verbose($"HotReloadProfile: BlazorHosted. {aspnetCoreProject.FullPath} references BlazorWebAssembly project {currentNode.ProjectInstance.FullPath}.", emoji: "🔥"); - return new BlazorWebAssemblyHostedAppModel(clientProject: currentNode); + reporter.Verbose($"HotReloadProfile: BlazorHosted. {aspnetCoreProject.ProjectInstance.FullPath} references BlazorWebAssembly project {currentNode.ProjectInstance.FullPath}.", emoji: "🔥"); + return new BlazorWebAssemblyHostedAppModel(clientProject: currentNode, serverProject: aspnetCoreProject); } reporter.Verbose("HotReloadProfile: BlazorWebAssembly.", emoji: "🔥"); @@ -66,6 +83,6 @@ public static HotReloadAppModel InferFromProject(ProjectGraphNode projectNode, I } reporter.Verbose("HotReloadProfile: Default.", emoji: "🔥"); - return DefaultAppModel.Instance; + return new DefaultAppModel(projectNode); } } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs b/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs index 288bf04b4d5b..f91ae9cc3447 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/ProjectLauncher.cs @@ -77,10 +77,10 @@ public EnvironmentOptions EnvironmentOptions // https://github.com/dotnet/runtime/blob/342936c5a88653f0f622e9d6cb727a0e59279b31/src/mono/browser/runtime/loader/config.ts#L330 environmentBuilder.SetVariable(EnvironmentVariables.Names.DotNetModifiableAssemblies, "debug"); - if (appModel.InjectDeltaApplier) + if (appModel.TryGetStartupHookPath(out var startupHookPath)) { // HotReload startup hook should be loaded before any other startup hooks: - environmentBuilder.DotNetStartupHooks.Insert(0, DeltaApplier.StartupHookPath); + environmentBuilder.DotNetStartupHooks.Insert(0, startupHookPath); environmentBuilder.SetVariable(EnvironmentVariables.Names.DotNetWatchHotReloadNamedPipeName, namedPipeName); diff --git a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs b/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs index 1ebebc89b789..55a7359d5e1a 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs @@ -84,7 +84,7 @@ public async Task RunAsync(ProcessSpec processSpec, IReporter reporter, boo { try { - _ = await WaitForExitAsync(process, timeout: null, processTerminationToken); + await process.WaitForExitAsync(processTerminationToken); } catch (OperationCanceledException) { @@ -221,7 +221,7 @@ private async ValueTask TerminateProcessAsync(Process process, ProcessState stat // Ctrl+C hasn't been sent, force termination. // We don't have means to terminate gracefully on Windows (https://github.com/dotnet/runtime/issues/109432) TerminateProcess(process, state, reporter, force: true); - _ = await WaitForExitAsync(process, timeout: null, cancellationToken); + _ = await WaitForExitAsync(process, state, timeout: null, reporter, cancellationToken); return; } @@ -233,42 +233,70 @@ private async ValueTask TerminateProcessAsync(Process process, ProcessState stat } // Ctlr+C/SIGTERM has been sent, wait for the process to exit gracefully. - if (!await WaitForExitAsync(process, processCleanupTimeout, cancellationToken)) + if (processCleanupTimeout.Milliseconds == 0 || + !await WaitForExitAsync(process, state, processCleanupTimeout, reporter, cancellationToken)) { // Force termination if the process is still running after the timeout. TerminateProcess(process, state, reporter, force: true); - _ = await WaitForExitAsync(process, timeout: null, cancellationToken); + _ = await WaitForExitAsync(process, state, timeout: null, reporter, cancellationToken); } } - private static async ValueTask WaitForExitAsync(Process process, TimeSpan? timeout, CancellationToken cancellationToken) + private static async ValueTask WaitForExitAsync(Process process, ProcessState state, TimeSpan? timeout, IReporter reporter, CancellationToken cancellationToken) { + // On Linux simple call WaitForExitAsync does not work reliably (it may hang). + // As a workaround we poll for HasExited. + // See also https://github.com/dotnet/runtime/issues/109434. + var task = process.WaitForExitAsync(cancellationToken); if (timeout.HasValue) { try { + reporter.Verbose($"Waiting for process {state.ProcessId} to exit within {timeout.Value.TotalSeconds}s."); await task.WaitAsync(timeout.Value, cancellationToken); } catch (TimeoutException) { - return false; + try + { + return process.HasExited; + } + catch + { + return false; + } } } else { - await task; - } + int i = 1; + while (true) + { + try + { + if (process.HasExited) + { + return true; + } + } + catch + { + } - // ensures that all process output has been reported: - try - { - process.WaitForExit(); - } - catch - { + reporter.Verbose($"Waiting for process {state.ProcessId} to exit ({i++})."); + + try + { + await task.WaitAsync(TimeSpan.FromSeconds(1), cancellationToken); + break; + } + catch (TimeoutException) + { + } + } } return true; diff --git a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj index a9d3d3781a64..31e8e4549dfb 100644 --- a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj +++ b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj @@ -1,4 +1,4 @@ - + @@ -50,8 +50,27 @@ - + + + all + Content + true + false + TargetFramework=net10.0 + hotreload\net10.0\Microsoft.Extensions.DotNetDeltaApplier.dll + PreserveNewest + + + + all + Content + true + false + TargetFramework=netstandard2.1 + hotreload\netstandard2.1\Microsoft.Extensions.DotNetDeltaApplier.dll + PreserveNewest + diff --git a/test/dotnet-watch.Tests/Aspire/AspireServiceFactoryTests.cs b/test/dotnet-watch.Tests/Aspire/AspireServiceFactoryTests.cs index 18387ff5af9b..5e5c721e9f0d 100644 --- a/test/dotnet-watch.Tests/Aspire/AspireServiceFactoryTests.cs +++ b/test/dotnet-watch.Tests/Aspire/AspireServiceFactoryTests.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; public class AspireServiceFactoryTests { - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void GetRunCommandArguments_Empty() { var request = new ProjectLaunchRequest() @@ -24,7 +24,7 @@ public void GetRunCommandArguments_Empty() AssertEx.SequenceEqual(["--project", "a.csproj"], args); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void GetRunCommandArguments_DisableLaunchProfile() { var request = new ProjectLaunchRequest() @@ -41,7 +41,7 @@ public void GetRunCommandArguments_DisableLaunchProfile() AssertEx.SequenceEqual(["--project", "a.csproj", "--no-launch-profile" ], args); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("")] [InlineData(null)] public void GetRunCommandArguments_NoLaunchProfile_HostProfile(string? launchProfile) @@ -60,7 +60,7 @@ public void GetRunCommandArguments_NoLaunchProfile_HostProfile(string? launchPro AssertEx.SequenceEqual(["--project", "a.csproj", "--launch-profile", "H"], args); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("")] [InlineData(null)] public void GetRunCommandArguments_DisableLaunchProfile_HostProfile(string? launchProfile) @@ -79,7 +79,7 @@ public void GetRunCommandArguments_DisableLaunchProfile_HostProfile(string? laun AssertEx.SequenceEqual(["--project", "a.csproj", "--no-launch-profile"], args); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("")] [InlineData(null)] public void GetRunCommandArguments_NoLaunchProfile_NoHostProfile(string? launchProfile) @@ -97,7 +97,7 @@ public void GetRunCommandArguments_NoLaunchProfile_NoHostProfile(string? launchP AssertEx.SequenceEqual(["--project", "a.csproj"], args); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void GetRunCommandArguments_LaunchProfile_NoArgs() { var request = new ProjectLaunchRequest() @@ -114,7 +114,7 @@ public void GetRunCommandArguments_LaunchProfile_NoArgs() AssertEx.SequenceEqual(["--project", "a.csproj", "--launch-profile", "P"], args); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void GetRunCommandArguments_LaunchProfile_EmptyArgs() { var request = new ProjectLaunchRequest() @@ -131,7 +131,7 @@ public void GetRunCommandArguments_LaunchProfile_EmptyArgs() AssertEx.SequenceEqual(["--project", "a.csproj", "--launch-profile", "P", "--no-launch-profile-arguments"], args); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void GetRunCommandArguments_LaunchProfile_NonEmptyArgs() { var request = new ProjectLaunchRequest() diff --git a/test/dotnet-watch.Tests/Browser/BrowserConnectorTests.cs b/test/dotnet-watch.Tests/Browser/BrowserConnectorTests.cs index e6a395ae434c..6f1a08391dfc 100644 --- a/test/dotnet-watch.Tests/Browser/BrowserConnectorTests.cs +++ b/test/dotnet-watch.Tests/Browser/BrowserConnectorTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; public class BrowserConnectorTests { - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(null, "https://localhost:1234", "https://localhost:1234")] [InlineData(null, "https://localhost:1234/", "https://localhost:1234/")] [InlineData("", "https://localhost:1234", "https://localhost:1234")] diff --git a/test/dotnet-watch.Tests/CommandLineOptionsTests.cs b/test/dotnet-watch.Tests/CommandLineOptionsTests.cs index 00c3223a8792..30d70643855a 100644 --- a/test/dotnet-watch.Tests/CommandLineOptionsTests.cs +++ b/test/dotnet-watch.Tests/CommandLineOptionsTests.cs @@ -39,7 +39,7 @@ private void VerifyErrors(string[] args, params string[] expectedErrors) Assert.NotEqual(0, errorCode); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData([new[] { "-h" }])] [InlineData([new[] { "-?" }])] [InlineData([new[] { "--help" }])] @@ -55,7 +55,7 @@ public void HelpArgs(string[] args) Assert.Contains("Usage:", output.ToString()); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("-p:P=V", "P", "V")] [InlineData("-p:P==", "P", "=")] [InlineData("-p:P=A=B", "P", "A=B")] @@ -67,7 +67,7 @@ public void BuildProperties_Valid(string argValue, string name, string value) AssertEx.SequenceEqual([(name, value)], properties); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("P")] [InlineData("=P3")] [InlineData("=")] @@ -78,7 +78,7 @@ public void BuildProperties_Invalid(string argValue) AssertEx.SequenceEqual([], properties); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ImplicitCommand() { var options = VerifyOptions([]); @@ -86,7 +86,7 @@ public void ImplicitCommand() AssertEx.SequenceEqual([InteractiveOption], options.CommandArguments); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("add")] [InlineData("build")] [InlineData("build-server")] @@ -119,7 +119,7 @@ public void ExplicitCommand(string command) Assert.Empty(args); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public void WatchOptions_NotPassedThrough_BeforeCommand( [CombinatorialValues("--quiet", "--verbose", "--no-hot-reload", "--non-interactive")] string option, @@ -130,7 +130,7 @@ public void WatchOptions_NotPassedThrough_BeforeCommand( AssertEx.SequenceEqual(option == "--non-interactive" ? [] : [InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_LaunchProfile_Watch() { var options = VerifyOptions(["-lp", "P", "run"]); @@ -139,7 +139,7 @@ public void RunOptions_LaunchProfile_Watch() AssertEx.SequenceEqual(["-lp", "P", InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_LaunchProfile_Run() { var options = VerifyOptions(["run", "-lp", "P"]); @@ -148,14 +148,14 @@ public void RunOptions_LaunchProfile_Run() AssertEx.SequenceEqual(["-lp", "P", InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_LaunchProfile_Both() { VerifyErrors(["-lp", "P1", "run", "-lp", "P2"], "error ❌ Option '-lp' expects a single argument but 2 were provided."); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_NoProfile_Watch() { var options = VerifyOptions(["--no-launch-profile", "run"]); @@ -165,7 +165,7 @@ public void RunOptions_NoProfile_Watch() AssertEx.SequenceEqual(["--no-launch-profile", InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_NoProfile_Run() { var options = VerifyOptions(["run", "--no-launch-profile"]); @@ -175,7 +175,7 @@ public void RunOptions_NoProfile_Run() AssertEx.SequenceEqual(["--no-launch-profile", InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RunOptions_NoProfile_Both() { var options = VerifyOptions(["--no-launch-profile", "run", "--no-launch-profile"]); @@ -185,7 +185,7 @@ public void RunOptions_NoProfile_Both() AssertEx.SequenceEqual(["--no-launch-profile", InteractiveOption], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RemainingOptions() { var options = VerifyOptions(["-watchArg", "--verbose", "run", "-runArg"]); @@ -195,7 +195,7 @@ public void RemainingOptions() AssertEx.SequenceEqual([InteractiveOption, "-watchArg", "-runArg"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void UnknownOption() { var options = VerifyOptions(["--verbose", "--unknown", "x", "y", "run", "--project", "p"]); @@ -205,7 +205,7 @@ public void UnknownOption() AssertEx.SequenceEqual(["--project", "p", InteractiveOption, "--unknown", "x", "y"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RemainingOptionsDashDash() { var options = VerifyOptions(["-watchArg", "--", "--verbose", "run", "-runArg"]); @@ -215,7 +215,7 @@ public void RemainingOptionsDashDash() AssertEx.SequenceEqual([InteractiveOption, "-watchArg", "--", "--verbose", "run", "-runArg",], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void RemainingOptionsDashDashRun() { var options = VerifyOptions(["--", "run"]); @@ -225,7 +225,7 @@ public void RemainingOptionsDashDashRun() AssertEx.SequenceEqual([InteractiveOption, "--", "run"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void NoOptionsAfterDashDash() { var options = VerifyOptions(["--"]); @@ -240,7 +240,7 @@ public void NoOptionsAfterDashDash() /// Therfore, it has to also be ignored by `dotnet run`, /// otherwise the TFMs would be inconsistent between `dotnet watch` and `dotnet run`. /// - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ParsedNonWatchOptionsAfterDashDash_Framework() { var options = VerifyOptions(["--", "-f", "TFM"]); @@ -249,7 +249,7 @@ public void ParsedNonWatchOptionsAfterDashDash_Framework() AssertEx.SequenceEqual([InteractiveOption, "--", "-f", "TFM"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ParsedNonWatchOptionsAfterDashDash_Project() { var options = VerifyOptions(["--", "--project", "proj"]); @@ -258,7 +258,7 @@ public void ParsedNonWatchOptionsAfterDashDash_Project() AssertEx.SequenceEqual([InteractiveOption, "--", "--project", "proj"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ParsedNonWatchOptionsAfterDashDash_NoLaunchProfile() { var options = VerifyOptions(["--", "--no-launch-profile"]); @@ -267,7 +267,7 @@ public void ParsedNonWatchOptionsAfterDashDash_NoLaunchProfile() AssertEx.SequenceEqual([InteractiveOption, "--", "--no-launch-profile"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ParsedNonWatchOptionsAfterDashDash_LaunchProfile() { var options = VerifyOptions(["--", "--launch-profile", "p"]); @@ -276,7 +276,7 @@ public void ParsedNonWatchOptionsAfterDashDash_LaunchProfile() AssertEx.SequenceEqual([InteractiveOption, "--", "--launch-profile", "p"], options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ParsedNonWatchOptionsAfterDashDash_Property() { var options = VerifyOptions(["--", "--property", "x=1"]); @@ -285,7 +285,7 @@ public void ParsedNonWatchOptionsAfterDashDash_Property() AssertEx.SequenceEqual([InteractiveOption, "--", "--property", "x=1"], options.CommandArguments); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public void OptionsSpecifiedBeforeOrAfterRun(bool afterRun) { @@ -307,7 +307,7 @@ public enum ArgPosition Both } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public void OptionDuplicates_Allowed_Bool( ArgPosition position, @@ -342,7 +342,7 @@ public void OptionDuplicates_Allowed_Bool( }); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void MultiplePropertyValues() { var options = VerifyOptions(["--property", "P1=V1", "run", "--property", "P2=V2"]); @@ -352,7 +352,7 @@ public void MultiplePropertyValues() AssertEx.SequenceEqual(["--property:P1=V1", "--property:P2=V2", InteractiveOption], options.CommandArguments); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("--project")] [InlineData("--framework")] public void OptionDuplicates_NotAllowed(string option) @@ -361,7 +361,7 @@ public void OptionDuplicates_NotAllowed(string option) $"error ❌ Option '{option}' expects a single argument but 2 were provided."); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(new[] { "--unrecognized-arg" }, new[] { InteractiveOption, "--unrecognized-arg" })] [InlineData(new[] { "run" }, new string[] { InteractiveOption })] [InlineData(new[] { "run", "--", "runarg" }, new[] { InteractiveOption, "--", "runarg" })] @@ -376,14 +376,14 @@ public void ParsesRemainingArgs(string[] args, string[] expected) Assert.Equal(expected, options.CommandArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void CannotHaveQuietAndVerbose() { VerifyErrors(["--quiet", "--verbose"], $"error ❌ {Resources.Error_QuietAndVerboseSpecified}"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ShortFormForProjectArgumentPrintsWarning() { var options = VerifyOptions(["-p", "MyProject.csproj"], @@ -392,14 +392,14 @@ public void ShortFormForProjectArgumentPrintsWarning() Assert.Equal("MyProject.csproj", options.ProjectPath); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LongFormForProjectArgumentWorks() { var options = VerifyOptions(["--project", "MyProject.csproj"]); Assert.Equal("MyProject.csproj", options.ProjectPath); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LongFormForLaunchProfileArgumentWorks() { var options = VerifyOptions(["--launch-profile", "CustomLaunchProfile"]); @@ -407,7 +407,7 @@ public void LongFormForLaunchProfileArgumentWorks() Assert.Equal("CustomLaunchProfile", options.LaunchProfileName); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ShortFormForLaunchProfileArgumentWorks() { var options = VerifyOptions(["-lp", "CustomLaunchProfile"]); @@ -419,7 +419,7 @@ public void ShortFormForLaunchProfileArgumentWorks() /// /// Validates that options that the "run" command forwards to "build" command are forwarded by dotnet-watch. /// - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(new[] { "--configuration", "release" }, new[] { "-property:Configuration=release", NugetInteractiveProperty })] [InlineData(new[] { "--framework", "net9.0" }, new[] { "-property:TargetFramework=net9.0", NugetInteractiveProperty })] [InlineData(new[] { "--runtime", "arm64" }, new[] { "-property:RuntimeIdentifier=arm64", "-property:_CommandLineDefinedRuntimeIdentifier=true", NugetInteractiveProperty })] @@ -438,7 +438,7 @@ public void ForwardedBuildOptions(string[] args, string[] buildArgs) AssertEx.SequenceEqual(buildArgs, options.BuildArguments); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void ForwardedBuildOptions_ArtifactsPath() { var path = TestContext.Current.TestAssetsDirectory; diff --git a/test/dotnet-watch.Tests/ConsoleReporterTests.cs b/test/dotnet-watch.Tests/ConsoleReporterTests.cs index c9008f820a5a..f87855e87799 100644 --- a/test/dotnet-watch.Tests/ConsoleReporterTests.cs +++ b/test/dotnet-watch.Tests/ConsoleReporterTests.cs @@ -9,7 +9,7 @@ public class ReporterTests { private static readonly string EOL = Environment.NewLine; - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(true)] [InlineData(false)] public void WritesToStandardStreams(bool suppressEmojis) @@ -35,7 +35,7 @@ public void WritesToStandardStreams(bool suppressEmojis) testConsole.Clear(); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(true)] [InlineData(false)] public void WritesToStandardStreamsWithCustomEmojis(bool suppressEmojis) diff --git a/test/dotnet-watch.Tests/FileSetSerializerTests.cs b/test/dotnet-watch.Tests/FileSetSerializerTests.cs index 5b93ab499e36..77bd1b8abe68 100644 --- a/test/dotnet-watch.Tests/FileSetSerializerTests.cs +++ b/test/dotnet-watch.Tests/FileSetSerializerTests.cs @@ -36,7 +36,7 @@ private static string Serialize(MSBuildFileSetResult fileSetResult, Stream strea return reader.ReadToEnd(); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task Roundtrip() { var result1 = new MSBuildFileSetResult() @@ -101,7 +101,7 @@ public async Task Roundtrip() """.Replace("\r\n", "\n"), serialized1.Replace("\r\n", "\n")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task Task() { var dir = _testAssetManager.CreateTestDirectory().Path; diff --git a/test/dotnet-watch.Tests/FileWatcherTests.cs b/test/dotnet-watch.Tests/FileWatcherTests.cs index b3ae38eb31c9..980a19373dcb 100644 --- a/test/dotnet-watch.Tests/FileWatcherTests.cs +++ b/test/dotnet-watch.Tests/FileWatcherTests.cs @@ -228,7 +228,7 @@ await TestOperation( () => File.WriteAllText(file, string.Empty)); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task MoveFile(bool usePolling) { diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index 7a8451716636..d366a921bc6e 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) { - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task AddSourceFile() { Log("AddSourceFile started"); @@ -46,7 +46,7 @@ public static void Print() await App.AssertOutputLineStartsWith("Changed!"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ChangeFileInDependency() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") @@ -71,7 +71,7 @@ public static void Print() await App.AssertOutputLineStartsWith("Changed!"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Fact] public async Task ProjectChange_UpdateDirectoryBuildPropsThenUpdateSource() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") @@ -109,7 +109,7 @@ public static unsafe void Print() await App.WaitUntilOutputContains($"dotnet watch 🔥 [App.WithDeps ({ToolsetInfo.CurrentTargetFramework})] Hot reload succeeded."); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task ProjectChange_Update(bool isDirectoryProps) { @@ -236,7 +236,7 @@ public static void Print() await App.AssertOutputLineStartsWith("BUILD_CONST_IN_PROPS not set"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Fact] public async Task DefaultItemExcludes_DefaultItemsEnabled() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") @@ -268,7 +268,7 @@ class X; await App.WaitUntilOutputContains($"dotnet watch ⌚ Ignoring change in excluded file '{appDataFilePath}': Add. Path matches DefaultItemExcludes glob 'AppData/**/*.*' set in '{testAsset.Path}'."); } - [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Fact] public async Task DefaultItemExcludes_DefaultItemsDisabled() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") @@ -310,7 +310,7 @@ public async Task DefaultItemExcludes_DefaultItemsDisabled() await App.WaitUntilOutputContains($"dotnet watch ⌚ Ignoring change in output directory: Add '{objDirFilePath}'"); } - [PlatformSpecificFact(TestPlatforms.Windows, Skip = "https://github.com/dotnet/sdk/issues/49542")] // "https://github.com/dotnet/sdk/issues/49307") + [Fact(Skip = "https://github.com/dotnet/sdk/issues/49542")] public async Task ProjectChange_GlobalUsings() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") @@ -347,7 +347,7 @@ public async Task ProjectChange_GlobalUsings() App.AssertOutputContains(MessageDescriptor.ReEvaluationCompleted); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task AutoRestartOnRudeEdit(bool nonInteractive) { @@ -391,7 +391,7 @@ public async Task AutoRestartOnRudeEdit(bool nonInteractive) await App.AssertOutputLineStartsWith($"dotnet watch 🔥 [WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Hot reload succeeded."); } - [PlatformSpecificFact(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Fact] public async Task AutoRestartOnRudeEditAfterRestartPrompt() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") @@ -432,7 +432,7 @@ public async Task AutoRestartOnRudeEditAfterRestartPrompt() App.AssertOutputContains($"[WatchHotReloadApp ({ToolsetInfo.CurrentTargetFramework})] Launched"); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task AutoRestartOnNoEffectEdit(bool nonInteractive) { @@ -480,7 +480,7 @@ public async Task AutoRestartOnNoEffectEdit(bool nonInteractive) /// /// Unchanged project doesn't build. Wait for source change and rebuild. /// - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task BaselineCompilationError() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -503,7 +503,7 @@ public async Task BaselineCompilationError() await App.AssertOutputLineStartsWith("", failure: _ => false); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ChangeFileInFSharpProject() { var testAsset = TestAssets.CopyTestAsset("FSharpTestAppSimple") @@ -518,7 +518,7 @@ public async Task ChangeFileInFSharpProject() await App.AssertOutputLineStartsWith(""); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ChangeFileInFSharpProjectWithLoop() { var testAsset = TestAssets.CopyTestAsset("FSharpTestAppSimple") @@ -588,7 +588,7 @@ public static void Print() await App.AssertOutputLineStartsWith("Updated types: Printer"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task MetadataUpdateHandler_NoActions() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadApp") @@ -620,7 +620,7 @@ 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."); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task MetadataUpdateHandler_Exception(bool verbose) { @@ -923,7 +923,7 @@ public static void Print() await App.AssertOutputLineStartsWith("Updated types: Printer"); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")] [InlineData(false)] public async Task RenameSourceFile(bool useMove) @@ -975,7 +975,7 @@ public static void PrintFileName([CallerFilePathAttribute] string filePath = nul await App.AssertOutputLineStartsWith("> Renamed.cs"); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")] [InlineData(false)] public async Task RenameDirectory(bool useMove) diff --git a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs index f0ff0f72d9cb..615d62766238 100644 --- a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs +++ b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; public class CompilationHandlerTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) { - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ReferenceOutputAssembly_False() { var testAsset = TestAssets.CopyTestAsset("WatchAppMultiProc") diff --git a/test/dotnet-watch.Tests/HotReload/HotReloadDotNetWatcherTests.cs b/test/dotnet-watch.Tests/HotReload/HotReloadDotNetWatcherTests.cs index fa9680cb86f5..ac47a4ef9943 100644 --- a/test/dotnet-watch.Tests/HotReload/HotReloadDotNetWatcherTests.cs +++ b/test/dotnet-watch.Tests/HotReload/HotReloadDotNetWatcherTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; public class HotReloadDotNetWatcherTests { - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(new[] { ChangeKind.Update }, new[] { ChangeKind.Update })] [InlineData(new[] { ChangeKind.Add }, new[] { ChangeKind.Add })] [InlineData(new[] { ChangeKind.Delete }, new[] { ChangeKind.Delete })] diff --git a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs index 9a51ac843e74..722c43483d8c 100644 --- a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs +++ b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs @@ -133,7 +133,7 @@ private RunningWatcher StartWatcher(TestAsset testAsset, string[] args, string w return new RunningWatcher(this, watcher, watchTask, reporter, console, serviceHolder, shutdownSource); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task UpdateAndRudeEdit(TriggerEvent trigger) { @@ -294,7 +294,7 @@ async Task MakeRudeEditChange() } } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task UpdateAppliedToNewProcesses(bool sharedOutput) { @@ -393,7 +393,7 @@ public enum UpdateLocation TopFunction, } - [Theory(Skip = "https://github.com/dotnet/sdk/issues/49307")] + [Theory] [CombinatorialData] public async Task HostRestart(UpdateLocation updateLocation) { @@ -484,7 +484,7 @@ public static void Print() await hasUpdate.WaitAsync(w.ShutdownSource.Token); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task RudeEditInProjectWithoutRunningProcess() { var testAsset = CopyTestAsset("WatchAppMultiProc"); @@ -539,7 +539,7 @@ public enum DirectoryKind Obj, } - [PlatformSpecificTheory(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Theory] [CombinatorialData] public async Task IgnoredChange(bool isExisting, bool isIncluded, DirectoryKind directoryKind) { diff --git a/test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs b/test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs index d9f8876e9133..509cadb17854 100644 --- a/test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs +++ b/test/dotnet-watch.Tests/Internal/EnvironmentVariablesBuilderTests.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watch.UnitTests { public class EnvironmentVariablesBuilderTests { - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void Value() { var builder = new EnvironmentVariablesBuilder(); @@ -20,7 +20,7 @@ public void Value() ], env); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void MultipleValues() { var builder = new EnvironmentVariablesBuilder(); diff --git a/test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs b/test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs index b0067ebb8124..44b57449f1c3 100644 --- a/test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs +++ b/test/dotnet-watch.Tests/LaunchSettingsProfileTest.cs @@ -16,7 +16,7 @@ public LaunchSettingsProfileTest(ITestOutputHelper output) _testAssets = new TestAssetsManager(output); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LoadsLaunchProfiles() { var project = _testAssets.CreateTestProject(new TestProject("Project1") @@ -60,7 +60,7 @@ public void LoadsLaunchProfiles() Assert.NotNull(expected); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void DefaultLaunchProfileWithoutProjectCommand() { var project = _testAssets.CreateTestProject(new TestProject("Project1") diff --git a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs index 436ece5d4397..3ff1b569ec2c 100644 --- a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs +++ b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs @@ -24,7 +24,7 @@ private static DotNetWatchContext CreateContext(bool suppressMSBuildIncrementali }; } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges() { var context = CreateContext(); @@ -41,7 +41,7 @@ public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges() Assert.True(evaluator.RequiresRevaluation); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges() { var context = CreateContext(); @@ -60,7 +60,7 @@ public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges() Assert.Equal(1, counter); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppressed() { var context = CreateContext(suppressMSBuildIncrementalism: true); @@ -80,7 +80,7 @@ public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppr Assert.Equal(2, counter); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIsNotChangedFile() { // There's a chance that the watcher does not correctly report edits to msbuild files on diff --git a/test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs b/test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs index edbf77069f63..0088f35d5db2 100644 --- a/test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs +++ b/test/dotnet-watch.Tests/MsBuildFileSetFactoryTest.cs @@ -19,7 +19,7 @@ private static IEnumerable Inspect(string rootDir, IReadOnlyDictionary entry.Key) .Select(entry => $"{InspectPath(entry.Key, rootDir)}: [{string.Join(", ", entry.Value.ContainingProjectPaths.Select(p => InspectPath(p, rootDir)))}]"); - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task FindsCustomWatchItems() { var project = _testAssets.CreateTestProject(new TestProject("Project1") @@ -51,7 +51,7 @@ public async Task FindsCustomWatchItems() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ExcludesDefaultItemsWithWatchFalseMetadata() { var project = _testAssets.CreateTestProject(new TestProject("Project1") @@ -85,7 +85,7 @@ public async Task ExcludesDefaultItemsWithWatchFalseMetadata() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task SingleTfm() { var project = _testAssets.CreateTestProject(new TestProject("Project1") @@ -118,7 +118,7 @@ public async Task SingleTfm() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task MultiTfm() { var project = _testAssets.CreateTestProject(new TestProject("Project1") @@ -154,7 +154,7 @@ public async Task MultiTfm() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task IncludesContentFiles() { var testDir = _testAssets.CreateTestDirectory(); @@ -187,7 +187,7 @@ public async Task IncludesContentFiles() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task IncludesContentFilesFromRCL() { var testDir = _testAssets.CreateTestDirectory(); @@ -239,7 +239,7 @@ public async Task IncludesContentFilesFromRCL() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProjectReferences_OneLevel() { var project2 = _testAssets.CreateTestProject(new TestProject("Project2") @@ -268,7 +268,7 @@ public async Task ProjectReferences_OneLevel() ); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task TransitiveProjectReferences_TwoLevels() { var project3 = _testAssets.CreateTestProject(new TestProject("Project3") @@ -307,7 +307,7 @@ public async Task TransitiveProjectReferences_TwoLevels() Assert.All(result.Files.Values, f => Assert.False(f.IsStaticFile, $"File {f.FilePath} should not be a static file.")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProjectReferences_Graph() { // A->B,F,W(Watch=False) @@ -364,7 +364,7 @@ public async Task ProjectReferences_Graph() _reporter.Messages.Where(l => l.text.Contains("Collecting watch items from")).Select(l => l.text.Trim()).Order()); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task MsbuildOutput() { var project2 = _testAssets.CreateTestProject(new TestProject("Project2") diff --git a/test/dotnet-watch.Tests/NoRestoreTests.cs b/test/dotnet-watch.Tests/NoRestoreTests.cs index b5ee7134e0c6..70b463955044 100644 --- a/test/dotnet-watch.Tests/NoRestoreTests.cs +++ b/test/dotnet-watch.Tests/NoRestoreTests.cs @@ -23,7 +23,7 @@ private static DotNetWatchContext CreateContext(string[] args = null, Environmen }; } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedOnFirstRun() { var context = CreateContext(); @@ -32,7 +32,7 @@ public void LeavesArgumentsUnchangedOnFirstRun() AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 0)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedIfMsBuildRevaluationIsRequired() { var context = CreateContext(); @@ -45,7 +45,7 @@ public void LeavesArgumentsUnchangedIfMsBuildRevaluationIsRequired() AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedIfOptimizationIsSuppressed() { var context = CreateContext([], TestOptions.GetEnvironmentOptions() with { SuppressMSBuildIncrementalism = true }); @@ -55,7 +55,7 @@ public void LeavesArgumentsUnchangedIfOptimizationIsSuppressed() AssertEx.SequenceEqual(["run", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent() { var context = CreateContext(["--no-restore"], TestOptions.GetEnvironmentOptions() with { SuppressMSBuildIncrementalism = true }); @@ -65,7 +65,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent() AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDash1() { var context = CreateContext(["--", "--no-restore"]); @@ -75,7 +75,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDas AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag, "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDash2() { var context = CreateContext(["--", "--", "--no-restore"]); @@ -85,7 +85,7 @@ public void LeavesArgumentsUnchangedIfNoRestoreAlreadyPresent_UnlessAfterDashDas AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag, "--", "--", "--no-restore"], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void AddsNoRestoreSwitch() { var context = CreateContext(); @@ -95,7 +95,7 @@ public void AddsNoRestoreSwitch() AssertEx.SequenceEqual(["run", "--no-restore", InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void AddsNoRestoreSwitch_WithAdditionalArguments() { var context = CreateContext(["run", "-f", ToolsetInfo.CurrentTargetFramework]); @@ -105,7 +105,7 @@ public void AddsNoRestoreSwitch_WithAdditionalArguments() AssertEx.SequenceEqual(["run", "--no-restore", "-f", ToolsetInfo.CurrentTargetFramework, InteractiveFlag], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void AddsNoRestoreSwitch_ForTestCommand() { var context = CreateContext(["test", "--filter SomeFilter"]); @@ -115,7 +115,7 @@ public void AddsNoRestoreSwitch_ForTestCommand() AssertEx.SequenceEqual(["test", "--no-restore", InteractiveFlag, "--filter SomeFilter"], evaluator.GetProcessArguments(iteration: 1)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public void DoesNotModifyArgumentsForUnknownCommands() { var context = CreateContext(["pack"]); diff --git a/test/dotnet-watch.Tests/Utilities/TestOptions.cs b/test/dotnet-watch.Tests/Utilities/TestOptions.cs index e18914e56173..020a7597ec2a 100644 --- a/test/dotnet-watch.Tests/Utilities/TestOptions.cs +++ b/test/dotnet-watch.Tests/Utilities/TestOptions.cs @@ -13,8 +13,12 @@ public static int GetTestPort() public static readonly ProjectOptions ProjectOptions = GetProjectOptions([]); public static EnvironmentOptions GetEnvironmentOptions(string workingDirectory = "", string muxerPath = "", TestAsset? asset = null) - // 0 timeout for process cleanup in tests. We can't send Ctrl+C, so process termination must be forced. - => new(workingDirectory, muxerPath, ProcessCleanupTimeout: TimeSpan.FromSeconds(0), TestFlags: TestFlags.RunningAsTest, TestOutput: asset != null ? GetWatchTestOutputPath(asset) : ""); + { + // 0 timeout for process cleanup in tests. We can't send Ctrl+C on Windows, so process termination must be forced. + var processCleanupTimeout = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(1); + + return new(workingDirectory, muxerPath, processCleanupTimeout, TestFlags: TestFlags.RunningAsTest, TestOutput: asset != null ? GetWatchTestOutputPath(asset) : ""); + } public static CommandLineOptions GetCommandLineOptions(string[] args) => CommandLineOptions.Parse(args, NullReporter.Singleton, TextWriter.Null, out _) ?? throw new InvalidOperationException(); diff --git a/test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs b/test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs index 489e45520b2c..569c2b8d07ac 100644 --- a/test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs +++ b/test/dotnet-watch.Tests/Watch/BrowserLaunchTests.cs @@ -12,7 +12,7 @@ public BrowserLaunchTests(ITestOutputHelper logger) { } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task LaunchesBrowserOnStart() { var testAsset = TestAssets.CopyTestAsset(AppName) @@ -30,7 +30,7 @@ public async Task LaunchesBrowserOnStart() Assert.Contains(App.Process.Output, line => line.Contains("dotnet watch ⌚ Launching browser: https://localhost:5001")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task UsesBrowserSpecifiedInEnvironment() { var testAsset = TestAssets.CopyTestAsset(AppName) diff --git a/test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs b/test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs index 85366609b5a1..22d227944919 100644 --- a/test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs +++ b/test/dotnet-watch.Tests/Watch/DotNetWatcherTests.cs @@ -16,7 +16,7 @@ public DotNetWatcherTests(ITestOutputHelper logger) { } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task RunsWithDotnetWatchEnvVariable() { Assert.True(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DOTNET_WATCH")), "DOTNET_WATCH cannot be set already when this test is running"); @@ -28,7 +28,7 @@ public async Task RunsWithDotnetWatchEnvVariable() Assert.Equal("1", await App.AssertOutputLineStartsWith("DOTNET_WATCH = ")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task RunsWithDotnetLaunchProfileEnvVariableWhenNotExplicitlySpecified(bool hotReload) { @@ -46,7 +46,7 @@ public async Task RunsWithDotnetLaunchProfileEnvVariableWhenNotExplicitlySpecifi Assert.Equal("<<>>", await App.AssertOutputLineStartsWith("DOTNET_LAUNCH_PROFILE = ")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecified(bool hotReload) { @@ -66,7 +66,7 @@ public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecified( Assert.Equal("<<>>", await App.AssertOutputLineStartsWith("DOTNET_LAUNCH_PROFILE = ")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [CombinatorialData] public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecifiedButNotPresentIsEmpty(bool hotReload) { @@ -84,7 +84,7 @@ public async Task RunsWithDotnetLaunchProfileEnvVariableWhenExplicitlySpecifiedB Assert.Equal("<<>>", await App.AssertOutputLineStartsWith("DOTNET_LAUNCH_PROFILE = ")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task RunsWithIterationEnvVariable() { var testAsset = TestAssets.CopyTestAsset(AppName) @@ -110,7 +110,7 @@ public async Task RunsWithIterationEnvVariable() Assert.Equal(2, int.Parse(value, CultureInfo.InvariantCulture)); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task Run_WithHotReloadEnabled_ReadsLaunchSettings() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithLaunchSettings") @@ -121,7 +121,7 @@ public async Task Run_WithHotReloadEnabled_ReadsLaunchSettings() await App.AssertOutputLineEquals("Environment: Development"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task Run_WithHotReloadEnabled_ReadsLaunchSettings_WhenUsingProjectOption() { var testAsset = TestAssets.CopyTestAsset("WatchAppWithLaunchSettings") diff --git a/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs b/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs index c42007d31818..48ff617568cc 100644 --- a/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs +++ b/test/dotnet-watch.Tests/Watch/GlobbingAppTests.cs @@ -103,7 +103,7 @@ public async Task ChangeExcludedFile() Assert.NotSame(fileChanged, finished); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ListsFiles() { var testAsset = TestAssets.CopyTestAsset(AppName) diff --git a/test/dotnet-watch.Tests/Watch/ProgramTests.cs b/test/dotnet-watch.Tests/Watch/ProgramTests.cs index f6004bb53143..23660c757461 100644 --- a/test/dotnet-watch.Tests/Watch/ProgramTests.cs +++ b/test/dotnet-watch.Tests/Watch/ProgramTests.cs @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Watch.UnitTests { public class ProgramTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) { - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ConsoleCancelKey() { var testAsset = TestAssets.CopyTestAsset("WatchKitchenSink") @@ -41,7 +41,7 @@ public async Task ConsoleCancelKey() await shutdownRequested.WaitAsync(); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(new[] { "--no-hot-reload", "run" }, "")] [InlineData(new[] { "--no-hot-reload", "run", "args" }, "args")] [InlineData(new[] { "--no-hot-reload", "--", "run", "args" }, "run,args")] @@ -63,7 +63,7 @@ public async Task Arguments(string[] arguments, string expectedApplicationArgs) Assert.Equal(expectedApplicationArgs, await App.AssertOutputLineStartsWith("Arguments = ")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData(new[] { "--no-hot-reload", "--", "run", "args" }, "Argument Specified in Props,run,args")] [InlineData(new[] { "--", "run", "args" }, "Argument Specified in Props,run,args")] // if arguments specified on command line the ones from launch profile are ignored @@ -80,7 +80,7 @@ public async Task Arguments_HostArguments(string[] arguments, string expectedApp AssertEx.Equal(expectedApplicationArgs, await App.AssertOutputLineStartsWith("Arguments = ")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task RunArguments_NoHotReload() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadAppMultiTfm") @@ -114,7 +114,7 @@ public async Task RunArguments_NoHotReload() Assert.DoesNotContain(App.Process.Output, l => l.Contains("Working directory:")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task RunArguments_HotReload() { var testAsset = TestAssets.CopyTestAsset("WatchHotReloadAppMultiTfm") @@ -145,7 +145,7 @@ public async Task RunArguments_HotReload() Assert.Contains(App.Process.Output, l => l.Contains("Hot reload enabled.")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("P1", "argP1")] [InlineData("P and Q and \"R\"", "argPQR")] public async Task ArgumentsFromLaunchSettings_Watch(string profileName, string expectedArgs) @@ -167,7 +167,7 @@ public async Task ArgumentsFromLaunchSettings_Watch(string profileName, string e Assert.Contains(App.Process.Output, l => l.Contains("Hot Reload disabled by command line switch.")); } - [PlatformSpecificTheory(TestPlatforms.Windows)] // https://github.com/dotnet/sdk/issues/49307 + [Theory] [InlineData("P1", "argP1")] [InlineData("P and Q and \"R\"", "argPQR")] public async Task ArgumentsFromLaunchSettings_HotReload(string profileName, string expectedArgs) @@ -187,7 +187,7 @@ public async Task ArgumentsFromLaunchSettings_HotReload(string profileName, stri Assert.Contains(App.Process.Output, l => l.Contains($"Found named launch profile '{profileName}'.")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task TestCommand() { var testAsset = TestAssets.CopyTestAsset("XunitCore") @@ -212,7 +212,7 @@ public async Task TestCommand() App.AssertOutputContains(" TestNamespace.VSTestXunitTests.VSTestXunitPassTest2"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task TestCommand_MultiTargeting() { var testAsset = TestAssets.CopyTestAsset("XunitMulti") @@ -224,7 +224,7 @@ public async Task TestCommand_MultiTargeting() await App.AssertOutputLineEquals(" TestNamespace.VSTestXunitTests.VSTestXunitFailTestNetCoreApp"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task BuildCommand() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -242,7 +242,7 @@ public async Task BuildCommand() App.AssertOutputContains("warning : The value of property is '123'"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task MSBuildCommand() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -260,7 +260,7 @@ public async Task MSBuildCommand() App.AssertOutputContains("warning : The value of property is '123'"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task PackCommand() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -280,7 +280,7 @@ public async Task PackCommand() App.AssertOutputContains($"Successfully created package '{packagePath}'"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task PublishCommand() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -299,7 +299,7 @@ public async Task PublishCommand() App.AssertOutputContains(Path.Combine("Release", ToolsetInfo.CurrentTargetFramework, "publish")); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task FormatCommand() { var testAsset = TestAssets.CopyTestAsset("WatchNoDepsApp") @@ -317,7 +317,7 @@ public async Task FormatCommand() App.AssertOutputContains("Format complete in"); } - [PlatformSpecificFact(TestPlatforms.Windows)] // "https://github.com/dotnet/sdk/issues/49307") + [Fact] public async Task ProjectGraphLoadFailure() { var testAsset = TestAssets diff --git a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs index a73e765a7923..dcbbc27b6073 100644 --- a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs +++ b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs @@ -124,7 +124,10 @@ public void Start(TestAsset asset, IEnumerable arguments, string relativ var testOutputPath = asset.GetWatchTestOutputPath(); Directory.CreateDirectory(testOutputPath); + + // FileSystemWatcher is unreliable. Use polling for testing to avoid flakiness. commandSpec.WithEnvironmentVariable("DOTNET_USE_POLLING_FILE_WATCHER", "true"); + commandSpec.WithEnvironmentVariable("__DOTNET_WATCH_TEST_FLAGS", testFlags.ToString()); commandSpec.WithEnvironmentVariable("__DOTNET_WATCH_TEST_OUTPUT_DIR", testOutputPath); commandSpec.WithEnvironmentVariable("Microsoft_CodeAnalysis_EditAndContinue_LogDir", testOutputPath); From e06e2ac1f6c606731f5074090d60ed865f2b3ca1 Mon Sep 17 00:00:00 2001 From: tmat Date: Wed, 25 Jun 2025 11:24:08 -0700 Subject: [PATCH 2/2] Feedback --- .../Microsoft.Extensions.DotNetDeltaApplier.csproj | 2 ++ src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs | 6 +----- src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj index 22db61517698..47b120b6375e 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj +++ b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj @@ -6,6 +6,8 @@ netstandard2.1;net10.0 MicrosoftAspNetCore diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs index 6cd3d9edca1e..bff71aeaee23 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs +++ b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs @@ -16,11 +16,7 @@ internal sealed class StartupHook private static readonly string? s_namedPipeName = Environment.GetEnvironmentVariable(AgentEnvironmentVariables.DotNetWatchHotReloadNamedPipeName); #if NET10_0_OR_GREATER - private const string TargetFramework = "net10.0"; - private static PosixSignalRegistration? s_signalRegistration; -#else - private const string TargetFramework = "netstandard2.1"; #endif /// @@ -30,7 +26,7 @@ public static void Initialize() { var processPath = Environment.GetCommandLineArgs().FirstOrDefault(); - Log($"Loaded into process: {processPath} ({TargetFramework})"); + Log($"Loaded into process: {processPath} ({typeof(StartupHook).Assembly.Location})"); HotReloadAgent.ClearHotReloadEnvironmentVariables(typeof(StartupHook)); diff --git a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs b/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs index 55a7359d5e1a..caf811e5a903 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/ProcessRunner.cs @@ -251,12 +251,12 @@ private static async ValueTask WaitForExitAsync(Process process, ProcessSt var task = process.WaitForExitAsync(cancellationToken); - if (timeout.HasValue) + if (timeout is { } timeoutValue) { try { - reporter.Verbose($"Waiting for process {state.ProcessId} to exit within {timeout.Value.TotalSeconds}s."); - await task.WaitAsync(timeout.Value, cancellationToken); + reporter.Verbose($"Waiting for process {state.ProcessId} to exit within {timeoutValue.TotalSeconds}s."); + await task.WaitAsync(timeoutValue, cancellationToken); } catch (TimeoutException) {