From 86d4207cb7d2494a6ba2d06826aadb6e168618c7 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Thu, 11 Jun 2026 19:46:14 +0200 Subject: [PATCH 01/15] Migrate Microsoft.DotNet.HotReload.Watch.Aspire.Tests to MSTest.Sdk on MTP This is a pathfinder PR for migrating the test suite to MSTest on Microsoft.Testing.Platform (MTP). Microsoft.DotNet.HotReload.Watch.Aspire.Tests was chosen because it has no dependency on the shared Microsoft.NET.TestFramework (which is xUnit-coupled and referenced by ~57 of 78 test projects), so it can migrate in isolation without unblocking dependents first. Changes: * global.json: add MSTest.Sdk 4.3.0-preview.26307.5 to msbuild-sdks. * test/Directory.Build.targets: gate the xUnit defaults (TestRunnerName=XUnitV3, Using Include=Xunit, etc.) behind $(UseMSTestSdk) != true, so MSTest.Sdk projects opt out cleanly. * test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests: - csproj now uses Sdk="MSTest.Sdk", sets UseMSTestSdk=true, references AwesomeAssertions and only the Watch.Aspire project. MTP is on by default via MSTest.Sdk (EnableMSTestRunner + TestingPlatformDotnetTestSupport). - All 4 unit-test files converted from xUnit to MSTest attributes/asserts ([Fact]/[Theory] -> [TestMethod]/[DataRow], Assert.* equivalents, Assert.IsInstanceOfType, Assert.HasCount, Assert.IsEmpty). - Local AssertEx.SequenceEqual helper replaces the xUnit-coupled one from HotReload.Test.Utilities. * Move the 2 integration tests (AspireLauncherTests + PipeUtilities) to test/dotnet-watch.Tests/Aspire/ so the Aspire.Tests project stays a pure MSTest unit-test project. They keep xUnit because they depend on WatchSdkTest, WatchableApp, [PlatformSpecificFact], ITestOutputHelper and TestAssets from Microsoft.NET.TestFramework. AspireLauncherTests was renamed to AspireLauncherIntegrationTests to reflect its new role. * src/Dotnet.Watch/Watch.Aspire/Properties/AssemblyInfo.cs: grant InternalsVisibleTo to dotnet-watch.Tests (needed by PipeUtilities, which uses internal WatchStatusEvent). * test/dotnet-watch.Tests/dotnet-watch.Tests.csproj: add ProjectReference to Watch.Aspire (ExcludeAssets=Runtime) so the moved integration tests compile. Verification: * Microsoft.DotNet.HotReload.Watch.Aspire.Tests builds with MSTest.Sdk and all 58 unit tests pass under MTP (705 ms). * test/dotnet-watch.Tests builds successfully with the moved files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Watch.Aspire/Properties/AssemblyInfo.cs | 1 + .../AspireHostLauncherCliTests.cs | 111 ++++++------ .../AspireHostLauncherTests.cs | 57 +++--- .../AspireResourceLauncherCliTests.cs | 163 +++++++++--------- .../AspireServerLauncherCliTests.cs | 87 +++++----- .../AssertEx.cs | 30 ++++ ...DotNet.HotReload.Watch.Aspire.Tests.csproj | 16 +- .../Aspire/AspireLauncherIntegrationTests.cs} | 2 +- .../Aspire}/PipeUtilities.cs | 0 .../dotnet-watch.Tests.csproj | 2 + 10 files changed, 255 insertions(+), 214 deletions(-) create mode 100644 test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs rename test/{Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireLauncherTests.cs => dotnet-watch.Tests/Aspire/AspireLauncherIntegrationTests.cs} (98%) rename test/{Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Utilities => dotnet-watch.Tests/Aspire}/PipeUtilities.cs (100%) diff --git a/src/Dotnet.Watch/Watch.Aspire/Properties/AssemblyInfo.cs b/src/Dotnet.Watch/Watch.Aspire/Properties/AssemblyInfo.cs index 74a1e15ab7a8..3d48d3fc6f5e 100644 --- a/src/Dotnet.Watch/Watch.Aspire/Properties/AssemblyInfo.cs +++ b/src/Dotnet.Watch/Watch.Aspire/Properties/AssemblyInfo.cs @@ -4,4 +4,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.HotReload.Watch.Aspire.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("dotnet-watch.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs index b7d9c7f69059..23edc5e39889 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs @@ -1,145 +1,146 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class AspireHostLauncherCliTests { - [Fact] + [TestMethod] public void RequiredSdkOption() { // --sdk option is missing var args = new[] { "host", "--entrypoint", "proj", "a", "b" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void RequiredEntryPointOption() { // --entrypoint option is missing var args = new[] { "host", "--sdk", "sdk", "--verbose" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void ProjectAndSdkPaths() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "myproject.csproj" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("sdk", launcher.EnvironmentOptions.SdkDirectory); - Assert.True(launcher.EntryPoint.IsProjectFile); - Assert.Equal("myproject.csproj", launcher.EntryPoint.PhysicalPath); - Assert.Empty(launcher.ApplicationArguments); - Assert.Equal(LogLevel.Information, launcher.GlobalOptions.LogLevel); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); + Assert.IsTrue(launcher.EntryPoint.IsProjectFile); + Assert.AreEqual("myproject.csproj", launcher.EntryPoint.PhysicalPath); + Assert.IsEmpty(launcher.ApplicationArguments); + Assert.AreEqual(LogLevel.Information, launcher.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void FilePath() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "file.cs" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("sdk", launcher.EnvironmentOptions.SdkDirectory); - Assert.False(launcher.EntryPoint.IsProjectFile); - Assert.Equal("file.cs", launcher.EntryPoint.EntryPointFilePath); - Assert.Empty(launcher.ApplicationArguments); - Assert.Equal(LogLevel.Information, launcher.GlobalOptions.LogLevel); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); + Assert.IsFalse(launcher.EntryPoint.IsProjectFile); + Assert.AreEqual("file.cs", launcher.EntryPoint.EntryPointFilePath); + Assert.IsEmpty(launcher.ApplicationArguments); + Assert.AreEqual(LogLevel.Information, launcher.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void ApplicationArguments() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--verbose", "a", "b" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); - Assert.Equal(LogLevel.Debug, launcher.GlobalOptions.LogLevel); + Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void VerboseOption() { // With verbose flag var argsVerbose = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--verbose" }; - var launcherVerbose = Assert.IsType(AspireLauncher.TryCreate(argsVerbose)); - Assert.Equal(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); + var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); // Without verbose flag var argsNotVerbose = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherNotVerbose = Assert.IsType(AspireLauncher.TryCreate(argsNotVerbose)); - Assert.Equal(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); + var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void QuietOption() { // With quiet flag var argsQuiet = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--quiet" }; - var launcherQuiet = Assert.IsType(AspireLauncher.TryCreate(argsQuiet)); - Assert.Equal(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); + var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); // Without quiet flag var argsNotQuiet = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherNotQuiet = Assert.IsType(AspireLauncher.TryCreate(argsNotQuiet)); - Assert.Equal(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); + var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void NoLaunchProfileOption() { // With no-launch-profile flag var argsNoProfile = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--no-launch-profile" }; - var launcherNoProfile = Assert.IsType(AspireLauncher.TryCreate(argsNoProfile)); - Assert.False(launcherNoProfile.LaunchProfileName.HasValue); + var launcherNoProfile = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); + Assert.IsFalse(launcherNoProfile.LaunchProfileName.HasValue); // Without no-launch-profile flag var argsDefault = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherDefault = Assert.IsType(AspireLauncher.TryCreate(argsDefault)); - Assert.True(launcherDefault.LaunchProfileName.HasValue); - Assert.Null(launcherDefault.LaunchProfileName.Value); + var launcherDefault = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsDefault)); + Assert.IsTrue(launcherDefault.LaunchProfileName.HasValue); + Assert.IsNull(launcherDefault.LaunchProfileName.Value); } - [Fact] + [TestMethod] public void LaunchProfileOption() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--launch-profile", "MyProfile" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.True(launcher.LaunchProfileName.HasValue); - Assert.Equal("MyProfile", launcher.LaunchProfileName.Value); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.IsTrue(launcher.LaunchProfileName.HasValue); + Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } - [Fact] + [TestMethod] public void ConflictingOptions() { // Cannot specify both --quiet and --verbose var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--quiet", "--verbose" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void EntryPoint_MultipleValues() { // EntryPoint option should only accept one value; extra values become application arguments var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj1", "proj2" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("proj1", launcher.EntryPoint.ProjectOrEntryPointFilePath); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("proj1", launcher.EntryPoint.ProjectOrEntryPointFilePath); AssertEx.SequenceEqual(["proj2"], launcher.ApplicationArguments); } - [Fact] + [TestMethod] public void AllOptionsSet() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "myapp.csproj", "--verbose", "--no-launch-profile", "arg1", "arg2", "arg3" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); - Assert.True(launcher.EntryPoint.IsProjectFile); - Assert.Equal("myapp.csproj", launcher.EntryPoint.PhysicalPath); - Assert.Equal("sdk", launcher.EnvironmentOptions.SdkDirectory); - Assert.Equal(LogLevel.Debug, launcher.GlobalOptions.LogLevel); - Assert.False(launcher.LaunchProfileName.HasValue); + Assert.IsTrue(launcher.EntryPoint.IsProjectFile); + Assert.AreEqual("myapp.csproj", launcher.EntryPoint.PhysicalPath); + Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); + Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); + Assert.IsFalse(launcher.LaunchProfileName.HasValue); AssertEx.SequenceEqual(["arg1", "arg2", "arg3"], launcher.ApplicationArguments); } -} +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs index 94abac0ac2ca..33cf07e5aa9e 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; @@ -6,6 +6,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class AspireHostLauncherTests { private static AspireHostLauncher CreateLauncher( @@ -33,13 +34,13 @@ private static AspireHostLauncher CreateLauncher( private static void AssertCommonProperties(ProjectOptions options, AspireHostLauncher launcher) { - Assert.True(options.IsMainProject); - Assert.Equal("run", options.Command); - Assert.Equal(launcher.EntryPoint, options.Representation); - Assert.Empty(options.LaunchEnvironmentVariables); + Assert.IsTrue(options.IsMainProject); + Assert.AreEqual("run", options.Command); + Assert.AreEqual(launcher.EntryPoint, options.Representation); + Assert.IsEmpty(options.LaunchEnvironmentVariables); } - [Fact] + [TestMethod] public void GetProjectOptions_ProjectFile_UsesProjectFlag() { var launcher = CreateLauncher("myapp.csproj"); @@ -47,11 +48,11 @@ public void GetProjectOptions_ProjectFile_UsesProjectFlag() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.False(options.LaunchProfileName.HasValue); + Assert.IsFalse(options.LaunchProfileName.HasValue); AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_EntryPointFile_UsesFileFlag() { var launcher = CreateLauncher("Program.cs"); @@ -59,11 +60,11 @@ public void GetProjectOptions_EntryPointFile_UsesFileFlag() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.False(options.LaunchProfileName.HasValue); + Assert.IsFalse(options.LaunchProfileName.HasValue); AssertEx.SequenceEqual(["--file", "Program.cs", "--no-launch-profile"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_WithLaunchProfile_AddsLaunchProfileArguments() { var launcher = CreateLauncher("myapp.csproj", launchProfileName: "MyProfile"); @@ -71,12 +72,12 @@ public void GetProjectOptions_WithLaunchProfile_AddsLaunchProfileArguments() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.True(options.LaunchProfileName.HasValue); - Assert.Equal("MyProfile", options.LaunchProfileName.Value); + Assert.IsTrue(options.LaunchProfileName.HasValue); + Assert.AreEqual("MyProfile", options.LaunchProfileName.Value); AssertEx.SequenceEqual(["--project", "myapp.csproj", "--launch-profile", "MyProfile"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_NoLaunchProfile_AddsNoLaunchProfileFlag() { var launcher = CreateLauncher("myapp.csproj", launchProfileName: Optional.NoValue); @@ -84,11 +85,11 @@ public void GetProjectOptions_NoLaunchProfile_AddsNoLaunchProfileFlag() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.False(options.LaunchProfileName.HasValue); + Assert.IsFalse(options.LaunchProfileName.HasValue); AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_NullLaunchProfile_UsesDefault() { // null value (HasValue=true) means use default launch profile - no --launch-profile or --no-launch-profile flag @@ -97,12 +98,12 @@ public void GetProjectOptions_NullLaunchProfile_UsesDefault() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.True(options.LaunchProfileName.HasValue); - Assert.Null(options.LaunchProfileName.Value); + Assert.IsTrue(options.LaunchProfileName.HasValue); + Assert.IsNull(options.LaunchProfileName.Value); AssertEx.SequenceEqual(["--project", "myapp.csproj"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_WithApplicationArguments_AppendsArguments() { var launcher = CreateLauncher("myapp.csproj", launchProfileName: "Profile", applicationArguments: ["arg1", "arg2"]); @@ -110,12 +111,12 @@ public void GetProjectOptions_WithApplicationArguments_AppendsArguments() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.True(options.LaunchProfileName.HasValue); - Assert.Equal("Profile", options.LaunchProfileName.Value); + Assert.IsTrue(options.LaunchProfileName.HasValue); + Assert.AreEqual("Profile", options.LaunchProfileName.Value); AssertEx.SequenceEqual(["--project", "myapp.csproj", "--launch-profile", "Profile", "arg1", "arg2"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_SetsCustomWorkingDirectory() { var launcher = CreateLauncher("myapp.csproj", workingDirectory: "/custom/path"); @@ -123,10 +124,10 @@ public void GetProjectOptions_SetsCustomWorkingDirectory() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.Equal("/custom/path", options.WorkingDirectory); + Assert.AreEqual("/custom/path", options.WorkingDirectory); } - [Fact] + [TestMethod] public void GetProjectOptions_EntryPointFile_WithLaunchProfileAndArguments() { var launcher = CreateLauncher("Program.cs", launchProfileName: "Dev", applicationArguments: ["--port", "8080"]); @@ -134,12 +135,12 @@ public void GetProjectOptions_EntryPointFile_WithLaunchProfileAndArguments() var options = launcher.GetHostProjectOptions()!; AssertCommonProperties(options, launcher); - Assert.True(options.LaunchProfileName.HasValue); - Assert.Equal("Dev", options.LaunchProfileName.Value); + Assert.IsTrue(options.LaunchProfileName.HasValue); + Assert.AreEqual("Dev", options.LaunchProfileName.Value); AssertEx.SequenceEqual(["--file", "Program.cs", "--launch-profile", "Dev", "--port", "8080"], options.CommandArguments); } - [Fact] + [TestMethod] public void GetProjectOptions_NoLaunchProfile_WithApplicationArguments() { var launcher = CreateLauncher("myapp.csproj", launchProfileName: Optional.NoValue, applicationArguments: ["--urls", "http://localhost:5000"]); @@ -147,7 +148,7 @@ public void GetProjectOptions_NoLaunchProfile_WithApplicationArguments() var options = launcher.GetHostProjectOptions(); AssertCommonProperties(options, launcher); - Assert.False(options.LaunchProfileName.HasValue); + Assert.IsFalse(options.LaunchProfileName.HasValue); AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile", "--urls", "http://localhost:5000"], options.CommandArguments); } -} +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs index d0151c2102fb..ef5a3347ddde 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; @@ -6,175 +6,176 @@ namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class AspireResourceLauncherCliTests { - [Fact] + [TestMethod] public void RequiredServerOption() { // --server option is missing var args = new[] { "resource", "--entrypoint", "proj" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void RequiredEntryPointOption() { // --entrypoint option is missing var args = new[] { "resource", "--server", "pipe1" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void MinimalRequiredOptions() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj.csproj" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("pipe1", launcher.ServerPipeName); - Assert.Equal("proj.csproj", launcher.EntryPoint); - Assert.Empty(launcher.ApplicationArguments); - Assert.Empty(launcher.EnvironmentVariables); - Assert.True(launcher.LaunchProfileName.HasValue); - Assert.Null(launcher.LaunchProfileName.Value); - Assert.Equal(LogLevel.Information, launcher.GlobalOptions.LogLevel); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("pipe1", launcher.ServerPipeName); + Assert.AreEqual("proj.csproj", launcher.EntryPoint); + Assert.IsEmpty(launcher.ApplicationArguments); + Assert.IsEmpty(launcher.EnvironmentVariables); + Assert.IsTrue(launcher.LaunchProfileName.HasValue); + Assert.IsNull(launcher.LaunchProfileName.Value); + Assert.AreEqual(LogLevel.Information, launcher.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void ApplicationArguments() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "a", "b" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); } - [Fact] + [TestMethod] public void EnvironmentOption_SingleVariable() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY=value" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Single(launcher.EnvironmentVariables); - Assert.Equal("value", launcher.EnvironmentVariables["KEY"]); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.HasCount(1, launcher.EnvironmentVariables); + Assert.AreEqual("value", launcher.EnvironmentVariables["KEY"]); } - [Fact] + [TestMethod] public void EnvironmentOption_MultipleVariables() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY1=val1", "-e", "KEY2=val2" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal(2, launcher.EnvironmentVariables.Count); - Assert.Equal("val1", launcher.EnvironmentVariables["KEY1"]); - Assert.Equal("val2", launcher.EnvironmentVariables["KEY2"]); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual(2, launcher.EnvironmentVariables.Count); + Assert.AreEqual("val1", launcher.EnvironmentVariables["KEY1"]); + Assert.AreEqual("val2", launcher.EnvironmentVariables["KEY2"]); } - [Fact] + [TestMethod] public void EnvironmentOption_ValueWithEquals() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "CONN=Server=localhost;Port=5432" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("Server=localhost;Port=5432", launcher.EnvironmentVariables["CONN"]); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("Server=localhost;Port=5432", launcher.EnvironmentVariables["CONN"]); } - [Fact] + [TestMethod] public void EnvironmentOption_EmptyValue() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY=" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("", launcher.EnvironmentVariables["KEY"]); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("", launcher.EnvironmentVariables["KEY"]); } - [Fact] + [TestMethod] public void EnvironmentOption_NoEquals() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("", launcher.EnvironmentVariables["KEY"]); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("", launcher.EnvironmentVariables["KEY"]); } - [Fact] + [TestMethod] public void NoLaunchProfileOption() { // With no-launch-profile flag var argsNoProfile = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--no-launch-profile" }; - var launcherNoProfile = Assert.IsType(AspireLauncher.TryCreate(argsNoProfile)); - Assert.False(launcherNoProfile.LaunchProfileName.HasValue); + var launcherNoProfile = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); + Assert.IsFalse(launcherNoProfile.LaunchProfileName.HasValue); // Without no-launch-profile flag var argsDefault = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherDefault = Assert.IsType(AspireLauncher.TryCreate(argsDefault)); - Assert.True(launcherDefault.LaunchProfileName.HasValue); - Assert.Null(launcherDefault.LaunchProfileName.Value); + var launcherDefault = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsDefault)); + Assert.IsTrue(launcherDefault.LaunchProfileName.HasValue); + Assert.IsNull(launcherDefault.LaunchProfileName.Value); } - [Fact] + [TestMethod] public void LaunchProfileOption() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--launch-profile", "MyProfile" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.True(launcher.LaunchProfileName.HasValue); - Assert.Equal("MyProfile", launcher.LaunchProfileName.Value); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.IsTrue(launcher.LaunchProfileName.HasValue); + Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } - [Fact] + [TestMethod] public void LaunchProfileOption_ShortForm() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-lp", "MyProfile" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.True(launcher.LaunchProfileName.HasValue); - Assert.Equal("MyProfile", launcher.LaunchProfileName.Value); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.IsTrue(launcher.LaunchProfileName.HasValue); + Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } - [Fact] + [TestMethod] public void VerboseOption() { var argsVerbose = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--verbose" }; - var launcherVerbose = Assert.IsType(AspireLauncher.TryCreate(argsVerbose)); - Assert.Equal(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); + var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); var argsNotVerbose = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherNotVerbose = Assert.IsType(AspireLauncher.TryCreate(argsNotVerbose)); - Assert.Equal(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); + var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void QuietOption() { var argsQuiet = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--quiet" }; - var launcherQuiet = Assert.IsType(AspireLauncher.TryCreate(argsQuiet)); - Assert.Equal(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); + var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); var argsNotQuiet = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherNotQuiet = Assert.IsType(AspireLauncher.TryCreate(argsNotQuiet)); - Assert.Equal(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); + var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void ConflictingOptions() { // Cannot specify both --quiet and --verbose var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--quiet", "--verbose" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void AllOptionsSet() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "myapp.csproj", "-e", "K1=V1", "-e", "K2=V2", "--launch-profile", "Dev", "--verbose", "arg1", "arg2" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); - Assert.Equal("pipe1", launcher.ServerPipeName); - Assert.Equal("myapp.csproj", launcher.EntryPoint); - Assert.Equal(LogLevel.Debug, launcher.GlobalOptions.LogLevel); - Assert.True(launcher.LaunchProfileName.HasValue); - Assert.Equal("Dev", launcher.LaunchProfileName.Value); + Assert.AreEqual("pipe1", launcher.ServerPipeName); + Assert.AreEqual("myapp.csproj", launcher.EntryPoint); + Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); + Assert.IsTrue(launcher.LaunchProfileName.HasValue); + Assert.AreEqual("Dev", launcher.LaunchProfileName.Value); AssertEx.SequenceEqual(["arg1", "arg2"], launcher.ApplicationArguments); - Assert.Equal(2, launcher.EnvironmentVariables.Count); - Assert.Equal("V1", launcher.EnvironmentVariables["K1"]); - Assert.Equal("V2", launcher.EnvironmentVariables["K2"]); + Assert.AreEqual(2, launcher.EnvironmentVariables.Count); + Assert.AreEqual("V1", launcher.EnvironmentVariables["K1"]); + Assert.AreEqual("V2", launcher.EnvironmentVariables["K2"]); } - [Fact] + [TestMethod] public void EnvironmentOption_Duplicates() { var command = new AspireResourceCommandDefinition(); @@ -187,7 +188,7 @@ public void EnvironmentOption_Duplicates() result.Errors.Should().BeEmpty(); } - [Fact] + [TestMethod] public void EnvironmentOption_Duplicates_CasingDifference() { var command = new AspireResourceCommandDefinition(); @@ -212,7 +213,7 @@ public void EnvironmentOption_Duplicates_CasingDifference() result.Errors.Should().BeEmpty(); } - [Fact] + [TestMethod] public void EnvironmentOption_MultiplePerToken() { var command = new AspireResourceCommandDefinition(); @@ -230,7 +231,7 @@ public void EnvironmentOption_MultiplePerToken() result.Errors.Should().BeEmpty(); } - [Fact] + [TestMethod] public void EnvironmentOption_NoValue() { var command = new AspireResourceCommandDefinition(); @@ -243,7 +244,7 @@ public void EnvironmentOption_NoValue() result.Errors.Should().BeEmpty(); } - [Fact] + [TestMethod] public void EnvironmentOption_WhitespaceTrimming() { var command = new AspireResourceCommandDefinition(); @@ -256,11 +257,11 @@ public void EnvironmentOption_WhitespaceTrimming() result.Errors.Should().BeEmpty(); } - [Theory] - [InlineData("")] - [InlineData("=")] - [InlineData("= X")] - [InlineData(" \u2002 = X")] + [TestMethod] + [DataRow("")] + [DataRow("=")] + [DataRow("= X")] + [DataRow(" \u2002 = X")] public void EnvironmentOption_Errors(string token) { var command = new AspireResourceCommandDefinition(); @@ -271,4 +272,4 @@ public void EnvironmentOption_Errors(string token) $"Incorrectly formatted environment variables '{token}'" ], result.Errors.Select(e => e.Message)); } -} +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs index 39ba1fdff22b..91dcfe91bdf7 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs @@ -1,129 +1,130 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class AspireServerLauncherCliTests { - [Fact] + [TestMethod] public void RequiredServerOption() { // --server option is missing var args = new[] { "server", "--sdk", "sdk" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void RequiredSdkOption() { // --sdk option is missing var args = new[] { "server", "--server", "pipe1" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void MinimalRequiredOptions() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("pipe1", launcher.ServerPipeName); - Assert.Equal(LogLevel.Information, launcher.GlobalOptions.LogLevel); - Assert.Empty(launcher.ResourcePaths); - Assert.Null(launcher.StatusPipeName); - Assert.Null(launcher.ControlPipeName); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("pipe1", launcher.ServerPipeName); + Assert.AreEqual(LogLevel.Information, launcher.GlobalOptions.LogLevel); + Assert.IsEmpty(launcher.ResourcePaths); + Assert.IsNull(launcher.StatusPipeName); + Assert.IsNull(launcher.ControlPipeName); } - [Fact] + [TestMethod] public void ResourceOption_SingleValue() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj"], launcher.ResourcePaths); } - [Fact] + [TestMethod] public void ResourceOption_MultipleValues() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "proj2.csproj", "file.cs" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj", "file.cs"], launcher.ResourcePaths); } - [Fact] + [TestMethod] public void ResourceOption_MultipleFlags() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "--resource", "proj2.csproj" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); } - [Fact] + [TestMethod] public void StatusPipeOption() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--status-pipe", "status1" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("status1", launcher.StatusPipeName); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("status1", launcher.StatusPipeName); } - [Fact] + [TestMethod] public void ControlPipeOption() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--control-pipe", "control1" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); - Assert.Equal("control1", launcher.ControlPipeName); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + Assert.AreEqual("control1", launcher.ControlPipeName); } - [Fact] + [TestMethod] public void VerboseOption() { // With verbose flag var argsVerbose = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--verbose" }; - var launcherVerbose = Assert.IsType(AspireLauncher.TryCreate(argsVerbose)); - Assert.Equal(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); + var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); // Without verbose flag var argsNotVerbose = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcherNotVerbose = Assert.IsType(AspireLauncher.TryCreate(argsNotVerbose)); - Assert.Equal(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); + var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void QuietOption() { // With quiet flag var argsQuiet = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--quiet" }; - var launcherQuiet = Assert.IsType(AspireLauncher.TryCreate(argsQuiet)); - Assert.Equal(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); + var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); // Without quiet flag var argsNotQuiet = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcherNotQuiet = Assert.IsType(AspireLauncher.TryCreate(argsNotQuiet)); - Assert.Equal(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); + var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } - [Fact] + [TestMethod] public void ConflictingOptions() { // Cannot specify both --quiet and --verbose var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--quiet", "--verbose" }; var launcher = AspireLauncher.TryCreate(args); - Assert.Null(launcher); + Assert.IsNull(launcher); } - [Fact] + [TestMethod] public void AllOptionsSet() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "proj2.csproj", "--status-pipe", "status1", "--control-pipe", "control1", "--verbose" }; - var launcher = Assert.IsType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); - Assert.Equal("pipe1", launcher.ServerPipeName); - Assert.Equal(LogLevel.Debug, launcher.GlobalOptions.LogLevel); + Assert.AreEqual("pipe1", launcher.ServerPipeName); + Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); - Assert.Equal("status1", launcher.StatusPipeName); - Assert.Equal("control1", launcher.ControlPipeName); + Assert.AreEqual("status1", launcher.StatusPipeName); + Assert.AreEqual("control1", launcher.ControlPipeName); } -} +} \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs new file mode 100644 index 000000000000..67ae04e4e745 --- /dev/null +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Watch.UnitTests; + +/// +/// Small project-local helper that mirrors the shape of the xUnit-based AssertEx +/// in for the subset of +/// helpers used by the unit tests in this project. Keeping it local lets this project +/// stay independent from the xUnit-coupled shared test utilities. +/// +internal static class AssertEx +{ + public static void SequenceEqual(IEnumerable expected, IEnumerable actual, string? message = null) + { + Assert.IsNotNull(actual); + + if (!expected.SequenceEqual(actual)) + { + var expectedString = string.Join(Environment.NewLine, expected.Select(FormatItem)); + var actualString = string.Join(Environment.NewLine, actual.Select(FormatItem)); + Assert.Fail( + (message is null ? string.Empty : message + Environment.NewLine) + + $"Expected:{Environment.NewLine}{expectedString}{Environment.NewLine}" + + $"Actual:{Environment.NewLine}{actualString}"); + } + + static string FormatItem(T item) => item?.ToString() ?? ""; + } +} diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj index e65ce1a88a56..3aa8c6452145 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj @@ -1,19 +1,23 @@ - + + + true + $(SdkTargetFramework) - Exe Microsoft.DotNet.Watch.Aspire.UnitTests MicrosoftAspNetCore - + + + + - - - + \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireLauncherTests.cs b/test/dotnet-watch.Tests/Aspire/AspireLauncherIntegrationTests.cs similarity index 98% rename from test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireLauncherTests.cs rename to test/dotnet-watch.Tests/Aspire/AspireLauncherIntegrationTests.cs index ceab4de2f838..2255ed3aa721 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireLauncherTests.cs +++ b/test/dotnet-watch.Tests/Aspire/AspireLauncherIntegrationTests.cs @@ -6,7 +6,7 @@ namespace Microsoft.DotNet.Watch.UnitTests; -public class AspireLauncherTests(ITestOutputHelper logger) : WatchSdkTest(logger) +public class AspireLauncherIntegrationTests(ITestOutputHelper logger) : WatchSdkTest(logger) { private WatchableApp CreateHostApp() => new( diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Utilities/PipeUtilities.cs b/test/dotnet-watch.Tests/Aspire/PipeUtilities.cs similarity index 100% rename from test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Utilities/PipeUtilities.cs rename to test/dotnet-watch.Tests/Aspire/PipeUtilities.cs diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 72312ae26fb1..6e31001e68f0 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -18,6 +18,8 @@ + + From 52eade30e7344c1b2a22a9d97883d5b7a8d829d8 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Thu, 11 Jun 2026 22:02:57 +0200 Subject: [PATCH 02/15] Bump MSTest.Sdk to 4.3.0-preview.26311.10 + apply skill review fixes - Bumps MSTest.Sdk to the latest internal preview to pick up the newest 4.3 assertion APIs (Assert.ContainsSingle, Assert.Contains for strings with the more natural (needle, haystack) signature, etc.). - Applies the assertion mapping flagged by the migrate-xunit-to-mstest skill in this repo (.github/skills/migrate-xunit-to-mstest, PR #54727): Assert.HasCount(1, x) -> Assert.ContainsSingle(x) (one occurrence in AspireResourceLauncherCliTests.cs) Verified: 58/58 tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- global.json | 2 +- .../AspireResourceLauncherCliTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/global.json b/global.json index 1dc269111cae..2b0f56d196c0 100644 --- a/global.json +++ b/global.json @@ -26,6 +26,6 @@ "Microsoft.Build.NoTargets": "3.7.134", "Microsoft.Build.Traversal": "4.1.82", "Microsoft.WixToolset.Sdk": "6.0.3-dotnet.4", - "MSTest.Sdk": "4.3.0-preview.26307.5" + "MSTest.Sdk": "4.3.0-preview.26311.10" } } diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs index ef5a3347ddde..5640079c6a7d 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs @@ -54,7 +54,7 @@ public void EnvironmentOption_SingleVariable() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY=value" }; var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); - Assert.HasCount(1, launcher.EnvironmentVariables); + Assert.ContainsSingle(launcher.EnvironmentVariables); Assert.AreEqual("value", launcher.EnvironmentVariables["KEY"]); } From 75a20cf293aa17907166b6ce634372af0d7b56dd Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Thu, 11 Jun 2026 22:16:38 +0200 Subject: [PATCH 03/15] Address review: use Assert.IsExactInstanceOfType for xUnit IsType parity Per reviewer feedback: MSTest 4.1.0+ exposes Assert.IsExactInstanceOfType(value) which returns T and enforces exact-type semantics -- the proper equivalent of xUnit's Assert.IsType(x). Assert.IsInstanceOfType is the equivalent of xUnit's Assert.IsAssignableFrom (assignable, not exact), which would be a silent semantic regression for the IsType originals. All 39 occurrences across AspireHostLauncherCliTests.cs, AspireResourceLauncherCliTests.cs, AspireServerLauncherCliTests.cs, and AspireLauncherIntegrationTests.cs were originally Assert.IsType in xUnit (verified against main), so all 39 are flipped to Assert.IsExactInstanceOfType. Note: the migrate-xunit-to-mstest skill cheatsheet at .github/skills/migrate-xunit-to-mstest/references/mapping-cheatsheet.md recommends `Assert.IsInstanceOfType` plus an extra typeof-check for exact-type semantics; that guidance predates IsExactInstanceOfType being available. Follow-up upstream (dotnet/skills) suggested. Verified: 58/58 Aspire tests still pass; dotnet-watch.Tests builds clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AspireHostLauncherCliTests.cs | 24 +++++++------- .../AspireResourceLauncherCliTests.cs | 32 +++++++++---------- .../AspireServerLauncherCliTests.cs | 22 ++++++------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs index 23edc5e39889..40db79bbb9a4 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs @@ -30,7 +30,7 @@ public void RequiredEntryPointOption() public void ProjectAndSdkPaths() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "myproject.csproj" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); Assert.IsTrue(launcher.EntryPoint.IsProjectFile); Assert.AreEqual("myproject.csproj", launcher.EntryPoint.PhysicalPath); @@ -42,7 +42,7 @@ public void ProjectAndSdkPaths() public void FilePath() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "file.cs" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); Assert.IsFalse(launcher.EntryPoint.IsProjectFile); Assert.AreEqual("file.cs", launcher.EntryPoint.EntryPointFilePath); @@ -54,7 +54,7 @@ public void FilePath() public void ApplicationArguments() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--verbose", "a", "b" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); } @@ -64,12 +64,12 @@ public void VerboseOption() { // With verbose flag var argsVerbose = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--verbose" }; - var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + var launcherVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); // Without verbose flag var argsNotVerbose = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + var launcherNotVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } @@ -78,12 +78,12 @@ public void QuietOption() { // With quiet flag var argsQuiet = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--quiet" }; - var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + var launcherQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); // Without quiet flag var argsNotQuiet = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + var launcherNotQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } @@ -92,12 +92,12 @@ public void NoLaunchProfileOption() { // With no-launch-profile flag var argsNoProfile = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--no-launch-profile" }; - var launcherNoProfile = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); + var launcherNoProfile = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); Assert.IsFalse(launcherNoProfile.LaunchProfileName.HasValue); // Without no-launch-profile flag var argsDefault = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj" }; - var launcherDefault = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsDefault)); + var launcherDefault = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsDefault)); Assert.IsTrue(launcherDefault.LaunchProfileName.HasValue); Assert.IsNull(launcherDefault.LaunchProfileName.Value); } @@ -106,7 +106,7 @@ public void NoLaunchProfileOption() public void LaunchProfileOption() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--launch-profile", "MyProfile" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.IsTrue(launcher.LaunchProfileName.HasValue); Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } @@ -125,7 +125,7 @@ public void EntryPoint_MultipleValues() { // EntryPoint option should only accept one value; extra values become application arguments var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj1", "proj2" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("proj1", launcher.EntryPoint.ProjectOrEntryPointFilePath); AssertEx.SequenceEqual(["proj2"], launcher.ApplicationArguments); } @@ -134,7 +134,7 @@ public void EntryPoint_MultipleValues() public void AllOptionsSet() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "myapp.csproj", "--verbose", "--no-launch-profile", "arg1", "arg2", "arg3" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.IsTrue(launcher.EntryPoint.IsProjectFile); Assert.AreEqual("myapp.csproj", launcher.EntryPoint.PhysicalPath); diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs index 5640079c6a7d..06efb2e94417 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs @@ -31,7 +31,7 @@ public void RequiredEntryPointOption() public void MinimalRequiredOptions() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj.csproj" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("pipe1", launcher.ServerPipeName); Assert.AreEqual("proj.csproj", launcher.EntryPoint); Assert.IsEmpty(launcher.ApplicationArguments); @@ -45,7 +45,7 @@ public void MinimalRequiredOptions() public void ApplicationArguments() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "a", "b" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); } @@ -53,7 +53,7 @@ public void ApplicationArguments() public void EnvironmentOption_SingleVariable() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY=value" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.ContainsSingle(launcher.EnvironmentVariables); Assert.AreEqual("value", launcher.EnvironmentVariables["KEY"]); } @@ -62,7 +62,7 @@ public void EnvironmentOption_SingleVariable() public void EnvironmentOption_MultipleVariables() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY1=val1", "-e", "KEY2=val2" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual(2, launcher.EnvironmentVariables.Count); Assert.AreEqual("val1", launcher.EnvironmentVariables["KEY1"]); Assert.AreEqual("val2", launcher.EnvironmentVariables["KEY2"]); @@ -72,7 +72,7 @@ public void EnvironmentOption_MultipleVariables() public void EnvironmentOption_ValueWithEquals() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "CONN=Server=localhost;Port=5432" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("Server=localhost;Port=5432", launcher.EnvironmentVariables["CONN"]); } @@ -80,7 +80,7 @@ public void EnvironmentOption_ValueWithEquals() public void EnvironmentOption_EmptyValue() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY=" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("", launcher.EnvironmentVariables["KEY"]); } @@ -88,7 +88,7 @@ public void EnvironmentOption_EmptyValue() public void EnvironmentOption_NoEquals() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-e", "KEY" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("", launcher.EnvironmentVariables["KEY"]); } @@ -97,12 +97,12 @@ public void NoLaunchProfileOption() { // With no-launch-profile flag var argsNoProfile = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--no-launch-profile" }; - var launcherNoProfile = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); + var launcherNoProfile = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNoProfile)); Assert.IsFalse(launcherNoProfile.LaunchProfileName.HasValue); // Without no-launch-profile flag var argsDefault = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherDefault = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsDefault)); + var launcherDefault = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsDefault)); Assert.IsTrue(launcherDefault.LaunchProfileName.HasValue); Assert.IsNull(launcherDefault.LaunchProfileName.Value); } @@ -111,7 +111,7 @@ public void NoLaunchProfileOption() public void LaunchProfileOption() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--launch-profile", "MyProfile" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.IsTrue(launcher.LaunchProfileName.HasValue); Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } @@ -120,7 +120,7 @@ public void LaunchProfileOption() public void LaunchProfileOption_ShortForm() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "-lp", "MyProfile" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.IsTrue(launcher.LaunchProfileName.HasValue); Assert.AreEqual("MyProfile", launcher.LaunchProfileName.Value); } @@ -129,11 +129,11 @@ public void LaunchProfileOption_ShortForm() public void VerboseOption() { var argsVerbose = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--verbose" }; - var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + var launcherVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); var argsNotVerbose = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + var launcherNotVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } @@ -141,11 +141,11 @@ public void VerboseOption() public void QuietOption() { var argsQuiet = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "--quiet" }; - var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + var launcherQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); var argsNotQuiet = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj" }; - var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + var launcherNotQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } @@ -162,7 +162,7 @@ public void ConflictingOptions() public void AllOptionsSet() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "myapp.csproj", "-e", "K1=V1", "-e", "K2=V2", "--launch-profile", "Dev", "--verbose", "arg1", "arg2" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("pipe1", launcher.ServerPipeName); Assert.AreEqual("myapp.csproj", launcher.EntryPoint); diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs index 91dcfe91bdf7..b1bf32c9b82f 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs @@ -30,7 +30,7 @@ public void RequiredSdkOption() public void MinimalRequiredOptions() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("pipe1", launcher.ServerPipeName); Assert.AreEqual(LogLevel.Information, launcher.GlobalOptions.LogLevel); Assert.IsEmpty(launcher.ResourcePaths); @@ -42,7 +42,7 @@ public void MinimalRequiredOptions() public void ResourceOption_SingleValue() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj"], launcher.ResourcePaths); } @@ -50,7 +50,7 @@ public void ResourceOption_SingleValue() public void ResourceOption_MultipleValues() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "proj2.csproj", "file.cs" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj", "file.cs"], launcher.ResourcePaths); } @@ -58,7 +58,7 @@ public void ResourceOption_MultipleValues() public void ResourceOption_MultipleFlags() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "--resource", "proj2.csproj" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); } @@ -66,7 +66,7 @@ public void ResourceOption_MultipleFlags() public void StatusPipeOption() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--status-pipe", "status1" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("status1", launcher.StatusPipeName); } @@ -74,7 +74,7 @@ public void StatusPipeOption() public void ControlPipeOption() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--control-pipe", "control1" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("control1", launcher.ControlPipeName); } @@ -83,12 +83,12 @@ public void VerboseOption() { // With verbose flag var argsVerbose = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--verbose" }; - var launcherVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); + var launcherVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsVerbose)); Assert.AreEqual(LogLevel.Debug, launcherVerbose.GlobalOptions.LogLevel); // Without verbose flag var argsNotVerbose = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcherNotVerbose = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); + var launcherNotVerbose = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotVerbose)); Assert.AreEqual(LogLevel.Information, launcherNotVerbose.GlobalOptions.LogLevel); } @@ -97,12 +97,12 @@ public void QuietOption() { // With quiet flag var argsQuiet = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--quiet" }; - var launcherQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); + var launcherQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsQuiet)); Assert.AreEqual(LogLevel.Warning, launcherQuiet.GlobalOptions.LogLevel); // Without quiet flag var argsNotQuiet = new[] { "server", "--server", "pipe1", "--sdk", "sdk" }; - var launcherNotQuiet = Assert.IsInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); + var launcherNotQuiet = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(argsNotQuiet)); Assert.AreEqual(LogLevel.Information, launcherNotQuiet.GlobalOptions.LogLevel); } @@ -119,7 +119,7 @@ public void ConflictingOptions() public void AllOptionsSet() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "proj2.csproj", "--status-pipe", "status1", "--control-pipe", "control1", "--verbose" }; - var launcher = Assert.IsInstanceOfType(AspireLauncher.TryCreate(args)); + var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("pipe1", launcher.ServerPipeName); Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); From 27da8c4aa0d5b7ff5aa66d59d52f5631d6e7552d Mon Sep 17 00:00:00 2001 From: Evangelink Date: Thu, 11 Jun 2026 22:32:03 +0200 Subject: [PATCH 04/15] Remove redundant Using of Microsoft.VisualStudio.TestTools.UnitTesting MSTest.Sdk already adds this as an implicit global using. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj index 3aa8c6452145..a7be15d830b3 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj @@ -13,7 +13,6 @@ - From 7a551ebccb67adbfb3ad1b17db1e8fce28e6961d Mon Sep 17 00:00:00 2001 From: Evangelink Date: Thu, 11 Jun 2026 23:03:36 +0200 Subject: [PATCH 05/15] Replace AssertEx.SequenceEqual with Assert.AreSequenceEqual MSTest 4.3+ provides Assert.AreSequenceEqual for element-wise IEnumerable compare with a nice diff message, so the project-local AssertEx helper is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AspireHostLauncherCliTests.cs | 6 ++-- .../AspireHostLauncherTests.cs | 16 +++++----- .../AspireResourceLauncherCliTests.cs | 6 ++-- .../AspireServerLauncherCliTests.cs | 8 ++--- .../AssertEx.cs | 30 ------------------- 5 files changed, 18 insertions(+), 48 deletions(-) delete mode 100644 test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs index 40db79bbb9a4..c5b3e40cf567 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherCliTests.cs @@ -55,7 +55,7 @@ public void ApplicationArguments() { var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj", "--verbose", "a", "b" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); - AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); + Assert.AreSequenceEqual(["a", "b"], launcher.ApplicationArguments); Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); } @@ -127,7 +127,7 @@ public void EntryPoint_MultipleValues() var args = new[] { "host", "--sdk", "sdk", "--entrypoint", "proj1", "proj2" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); Assert.AreEqual("proj1", launcher.EntryPoint.ProjectOrEntryPointFilePath); - AssertEx.SequenceEqual(["proj2"], launcher.ApplicationArguments); + Assert.AreSequenceEqual(["proj2"], launcher.ApplicationArguments); } [TestMethod] @@ -141,6 +141,6 @@ public void AllOptionsSet() Assert.AreEqual("sdk", launcher.EnvironmentOptions.SdkDirectory); Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); Assert.IsFalse(launcher.LaunchProfileName.HasValue); - AssertEx.SequenceEqual(["arg1", "arg2", "arg3"], launcher.ApplicationArguments); + Assert.AreSequenceEqual(["arg1", "arg2", "arg3"], launcher.ApplicationArguments); } } \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs index 33cf07e5aa9e..23ca92043e70 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireHostLauncherTests.cs @@ -49,7 +49,7 @@ public void GetProjectOptions_ProjectFile_UsesProjectFlag() AssertCommonProperties(options, launcher); Assert.IsFalse(options.LaunchProfileName.HasValue); - AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); } [TestMethod] @@ -61,7 +61,7 @@ public void GetProjectOptions_EntryPointFile_UsesFileFlag() AssertCommonProperties(options, launcher); Assert.IsFalse(options.LaunchProfileName.HasValue); - AssertEx.SequenceEqual(["--file", "Program.cs", "--no-launch-profile"], options.CommandArguments); + Assert.AreSequenceEqual(["--file", "Program.cs", "--no-launch-profile"], options.CommandArguments); } [TestMethod] @@ -74,7 +74,7 @@ public void GetProjectOptions_WithLaunchProfile_AddsLaunchProfileArguments() AssertCommonProperties(options, launcher); Assert.IsTrue(options.LaunchProfileName.HasValue); Assert.AreEqual("MyProfile", options.LaunchProfileName.Value); - AssertEx.SequenceEqual(["--project", "myapp.csproj", "--launch-profile", "MyProfile"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj", "--launch-profile", "MyProfile"], options.CommandArguments); } [TestMethod] @@ -86,7 +86,7 @@ public void GetProjectOptions_NoLaunchProfile_AddsNoLaunchProfileFlag() AssertCommonProperties(options, launcher); Assert.IsFalse(options.LaunchProfileName.HasValue); - AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj", "--no-launch-profile"], options.CommandArguments); } [TestMethod] @@ -100,7 +100,7 @@ public void GetProjectOptions_NullLaunchProfile_UsesDefault() AssertCommonProperties(options, launcher); Assert.IsTrue(options.LaunchProfileName.HasValue); Assert.IsNull(options.LaunchProfileName.Value); - AssertEx.SequenceEqual(["--project", "myapp.csproj"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj"], options.CommandArguments); } [TestMethod] @@ -113,7 +113,7 @@ public void GetProjectOptions_WithApplicationArguments_AppendsArguments() AssertCommonProperties(options, launcher); Assert.IsTrue(options.LaunchProfileName.HasValue); Assert.AreEqual("Profile", options.LaunchProfileName.Value); - AssertEx.SequenceEqual(["--project", "myapp.csproj", "--launch-profile", "Profile", "arg1", "arg2"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj", "--launch-profile", "Profile", "arg1", "arg2"], options.CommandArguments); } [TestMethod] @@ -137,7 +137,7 @@ public void GetProjectOptions_EntryPointFile_WithLaunchProfileAndArguments() AssertCommonProperties(options, launcher); Assert.IsTrue(options.LaunchProfileName.HasValue); Assert.AreEqual("Dev", options.LaunchProfileName.Value); - AssertEx.SequenceEqual(["--file", "Program.cs", "--launch-profile", "Dev", "--port", "8080"], options.CommandArguments); + Assert.AreSequenceEqual(["--file", "Program.cs", "--launch-profile", "Dev", "--port", "8080"], options.CommandArguments); } [TestMethod] @@ -149,6 +149,6 @@ public void GetProjectOptions_NoLaunchProfile_WithApplicationArguments() AssertCommonProperties(options, launcher); Assert.IsFalse(options.LaunchProfileName.HasValue); - AssertEx.SequenceEqual(["--project", "myapp.csproj", "--no-launch-profile", "--urls", "http://localhost:5000"], options.CommandArguments); + Assert.AreSequenceEqual(["--project", "myapp.csproj", "--no-launch-profile", "--urls", "http://localhost:5000"], options.CommandArguments); } } \ No newline at end of file diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs index 06efb2e94417..a4cb31a69526 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireResourceLauncherCliTests.cs @@ -46,7 +46,7 @@ public void ApplicationArguments() { var args = new[] { "resource", "--server", "pipe1", "--entrypoint", "proj", "a", "b" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); - AssertEx.SequenceEqual(["a", "b"], launcher.ApplicationArguments); + Assert.AreSequenceEqual(["a", "b"], launcher.ApplicationArguments); } [TestMethod] @@ -169,7 +169,7 @@ public void AllOptionsSet() Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); Assert.IsTrue(launcher.LaunchProfileName.HasValue); Assert.AreEqual("Dev", launcher.LaunchProfileName.Value); - AssertEx.SequenceEqual(["arg1", "arg2"], launcher.ApplicationArguments); + Assert.AreSequenceEqual(["arg1", "arg2"], launcher.ApplicationArguments); Assert.AreEqual(2, launcher.EnvironmentVariables.Count); Assert.AreEqual("V1", launcher.EnvironmentVariables["K1"]); Assert.AreEqual("V2", launcher.EnvironmentVariables["K2"]); @@ -267,7 +267,7 @@ public void EnvironmentOption_Errors(string token) var command = new AspireResourceCommandDefinition(); var result = command.Parse(["--server", "S", "--entrypoint", "E", "-e", token]); - AssertEx.SequenceEqual( + Assert.AreSequenceEqual( [ $"Incorrectly formatted environment variables '{token}'" ], result.Errors.Select(e => e.Message)); diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs index b1bf32c9b82f..127b20e9ca0f 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AspireServerLauncherCliTests.cs @@ -43,7 +43,7 @@ public void ResourceOption_SingleValue() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); - AssertEx.SequenceEqual(["proj1.csproj"], launcher.ResourcePaths); + Assert.AreSequenceEqual(["proj1.csproj"], launcher.ResourcePaths); } [TestMethod] @@ -51,7 +51,7 @@ public void ResourceOption_MultipleValues() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "proj2.csproj", "file.cs" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); - AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj", "file.cs"], launcher.ResourcePaths); + Assert.AreSequenceEqual(["proj1.csproj", "proj2.csproj", "file.cs"], launcher.ResourcePaths); } [TestMethod] @@ -59,7 +59,7 @@ public void ResourceOption_MultipleFlags() { var args = new[] { "server", "--server", "pipe1", "--sdk", "sdk", "--resource", "proj1.csproj", "--resource", "proj2.csproj" }; var launcher = Assert.IsExactInstanceOfType(AspireLauncher.TryCreate(args)); - AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); + Assert.AreSequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); } [TestMethod] @@ -123,7 +123,7 @@ public void AllOptionsSet() Assert.AreEqual("pipe1", launcher.ServerPipeName); Assert.AreEqual(LogLevel.Debug, launcher.GlobalOptions.LogLevel); - AssertEx.SequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); + Assert.AreSequenceEqual(["proj1.csproj", "proj2.csproj"], launcher.ResourcePaths); Assert.AreEqual("status1", launcher.StatusPipeName); Assert.AreEqual("control1", launcher.ControlPipeName); } diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs deleted file mode 100644 index 67ae04e4e745..000000000000 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/AssertEx.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Watch.UnitTests; - -/// -/// Small project-local helper that mirrors the shape of the xUnit-based AssertEx -/// in for the subset of -/// helpers used by the unit tests in this project. Keeping it local lets this project -/// stay independent from the xUnit-coupled shared test utilities. -/// -internal static class AssertEx -{ - public static void SequenceEqual(IEnumerable expected, IEnumerable actual, string? message = null) - { - Assert.IsNotNull(actual); - - if (!expected.SequenceEqual(actual)) - { - var expectedString = string.Join(Environment.NewLine, expected.Select(FormatItem)); - var actualString = string.Join(Environment.NewLine, actual.Select(FormatItem)); - Assert.Fail( - (message is null ? string.Empty : message + Environment.NewLine) + - $"Expected:{Environment.NewLine}{expectedString}{Environment.NewLine}" + - $"Actual:{Environment.NewLine}{actualString}"); - } - - static string FormatItem(T item) => item?.ToString() ?? ""; - } -} From cb5098995948cabacda6d93f175533cddedfde04 Mon Sep 17 00:00:00 2001 From: Evangelink <32149626+Evangelink@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:19:48 +0200 Subject: [PATCH 06/15] Move FluentAssertions Using + AwesomeAssertions PackageReference to test/Directory.Build.targets Per @Evangelink: keep per-csproj boilerplate minimal. FluentAssertions is now a global using for any test project (gated on IsTestProject OR UsingMSTestSdk), and AwesomeAssertions is added as a PackageReference for MSTest.Sdk projects. xUnit projects continue to pick it up transitively via Microsoft.NET.TestFramework. The Microsoft.NET.TestFramework.* and Xunit usings remain gated on the xUnit branch (UsingMSTestSdk != true) because MSTest projects in this repo do not reference Microsoft.NET.TestFramework; making those usings global would fail with CS0246. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj index a7be15d830b3..2ae0f2fe0244 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj @@ -10,11 +10,6 @@ MicrosoftAspNetCore - - - - - From ead0658e82a0946b58fa2decf327f229603696ee Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 12 Jun 2026 16:07:51 +0200 Subject: [PATCH 07/15] Restore Watch.Aspire ProjectReference in dotnet-watch.Tests.csproj The recent `Helix dispatcher: gate --report-trx on TrxReport extension being loaded` commit accidentally removed the ProjectReference to Microsoft.DotNet.HotReload.Watch.Aspire from dotnet-watch.Tests.csproj (introduced in the `Migrate Microsoft.DotNet.HotReload.Watch.Aspire.Tests to MSTest.Sdk on MTP` commit to allow the moved AspireLauncherIntegrationTests and PipeUtilities to compile). Without that reference, the build fails with: error CS0246: The type or namespace name 'WatchStatusEvent' could not be found (are you missing a using directive or an assembly reference?) [test/dotnet-watch.Tests/Aspire/PipeUtilities.cs] Re-add the ProjectReference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/dotnet-watch.Tests/dotnet-watch.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 6e31001e68f0..3b45b8eb3031 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -1,4 +1,4 @@ - + Exe $(SdkTargetFramework) From fe718beb6b20d0e227ad7410054bd7a949f05b4f Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 12 Jun 2026 18:29:09 +0200 Subject: [PATCH 08/15] Add shared Microsoft.DotNet.Test.MSTest.Utilities project Introduces an MSTest-flavored counterpart to the xUnit-based Microsoft.DotNet.HotReload.Test.Utilities so that test helpers that need MSTest's TestContext (e.g. TestLogger, TestLoggerFactory) can be shared across MSTest.Sdk test projects instead of being copy-pasted per project. This commit also migrates the inline TestLogger from Microsoft.DotNet.HotReload.Client.Tests to consume the shared project, which serves as the first reference consumer. Subsequent migration PRs (DeltaApplier.Tests, Containers.UnitTests) will adopt the same project reference instead of adding their own TestLogger/TestLoggerFactory copies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk.slnx | 1 + src/Dotnet.Watch/dotnet-watch.slnf | 1 + ...osoft.DotNet.HotReload.Client.Tests.csproj | 4 + .../StaticWebAssetsManifestTests.cs | 2 +- .../InMemoryLoggerProvider.cs | 39 ++++++++ ...rosoft.DotNet.Test.MSTest.Utilities.csproj | 42 ++++++++ .../TestLogger.cs | 14 ++- .../TestLoggerFactory.cs | 97 +++++++++++++++++++ 8 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 test/Microsoft.DotNet.Test.MSTest.Utilities/InMemoryLoggerProvider.cs create mode 100644 test/Microsoft.DotNet.Test.MSTest.Utilities/Microsoft.DotNet.Test.MSTest.Utilities.csproj rename test/{Microsoft.DotNet.HotReload.Client.Tests/Utilities => Microsoft.DotNet.Test.MSTest.Utilities}/TestLogger.cs (70%) create mode 100644 test/Microsoft.DotNet.Test.MSTest.Utilities/TestLoggerFactory.cs diff --git a/sdk.slnx b/sdk.slnx index df9722223799..07d879bec7b9 100644 --- a/sdk.slnx +++ b/sdk.slnx @@ -367,6 +367,7 @@ + diff --git a/src/Dotnet.Watch/dotnet-watch.slnf b/src/Dotnet.Watch/dotnet-watch.slnf index 48e156d1458e..b5600195a12e 100644 --- a/src/Dotnet.Watch/dotnet-watch.slnf +++ b/src/Dotnet.Watch/dotnet-watch.slnf @@ -33,6 +33,7 @@ "test\\dotnet-watch-test-browser\\dotnet-watch-test-browser.csproj", "test\\Microsoft.DotNet.HotReload.Test.Utilities\\Microsoft.DotNet.HotReload.Test.Utilities.csproj", "test\\Microsoft.DotNet.HotReload.Watch.Aspire.Tests\\Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj", + "test\\Microsoft.DotNet.Test.MSTest.Utilities\\Microsoft.DotNet.Test.MSTest.Utilities.csproj", "test\\Microsoft.Extensions.DotNetDeltaApplier.Tests\\Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj", "test\\dotnet-watch.Tests\\dotnet-watch.Tests.csproj" ] diff --git a/test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj b/test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj index 7500dad99340..ef519bdfcde4 100644 --- a/test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj +++ b/test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj @@ -17,6 +17,10 @@ + + + + diff --git a/test/Microsoft.DotNet.HotReload.Client.Tests/StaticWebAssetsManifestTests.cs b/test/Microsoft.DotNet.HotReload.Client.Tests/StaticWebAssetsManifestTests.cs index 87d3eadc3176..e82b59ba8884 100644 --- a/test/Microsoft.DotNet.HotReload.Client.Tests/StaticWebAssetsManifestTests.cs +++ b/test/Microsoft.DotNet.HotReload.Client.Tests/StaticWebAssetsManifestTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.DotNet.Watch.UnitTests; +using Microsoft.DotNet.Test.MSTest.Utilities; using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.DotNet.HotReload.UnitTests; diff --git a/test/Microsoft.DotNet.Test.MSTest.Utilities/InMemoryLoggerProvider.cs b/test/Microsoft.DotNet.Test.MSTest.Utilities/InMemoryLoggerProvider.cs new file mode 100644 index 000000000000..3715dac3febe --- /dev/null +++ b/test/Microsoft.DotNet.Test.MSTest.Utilities/InMemoryLoggerProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.DotNet.Test.MSTest.Utilities; + +/// +/// An that appends every log entry to a caller-supplied list. +/// Useful for tests that need to assert on the exact sequence of log entries produced by a +/// component under test (without dragging in MSTest's TestContext sink). +/// +public sealed class InMemoryLoggerProvider(List<(LogLevel, string)> messagesCollection) : ILoggerProvider +{ + public ILogger CreateLogger(string categoryName) => new InMemoryLogger(messagesCollection); + + public void Dispose() + { + } + + private sealed class InMemoryLogger(List<(LogLevel, string)> messagesCollection) : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull => NullScope.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + => messagesCollection.Add((logLevel, formatter(state, exception))); + } + + private sealed class NullScope : IDisposable + { + public static readonly NullScope Instance = new(); + + public void Dispose() + { + } + } +} diff --git a/test/Microsoft.DotNet.Test.MSTest.Utilities/Microsoft.DotNet.Test.MSTest.Utilities.csproj b/test/Microsoft.DotNet.Test.MSTest.Utilities/Microsoft.DotNet.Test.MSTest.Utilities.csproj new file mode 100644 index 000000000000..73adf7653621 --- /dev/null +++ b/test/Microsoft.DotNet.Test.MSTest.Utilities/Microsoft.DotNet.Test.MSTest.Utilities.csproj @@ -0,0 +1,42 @@ + + + + + + $(SdkTargetFramework);$(NetFrameworkToolCurrent) + Library + Microsoft.DotNet.Test.MSTest.Utilities + MicrosoftAspNetCore + false + + + + + + + + + + + + + + + + diff --git a/test/Microsoft.DotNet.HotReload.Client.Tests/Utilities/TestLogger.cs b/test/Microsoft.DotNet.Test.MSTest.Utilities/TestLogger.cs similarity index 70% rename from test/Microsoft.DotNet.HotReload.Client.Tests/Utilities/TestLogger.cs rename to test/Microsoft.DotNet.Test.MSTest.Utilities/TestLogger.cs index 6bb15080c57d..87c8c1248f62 100644 --- a/test/Microsoft.DotNet.HotReload.Client.Tests/Utilities/TestLogger.cs +++ b/test/Microsoft.DotNet.Test.MSTest.Utilities/TestLogger.cs @@ -1,12 +1,18 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Microsoft.DotNet.Watch.UnitTests; +namespace Microsoft.DotNet.Test.MSTest.Utilities; -internal class TestLogger(TestContext? output = null) : ILogger +/// +/// An that captures messages in memory and optionally echoes them to an +/// MSTest . Designed to be shared across MSTest.Sdk test projects so +/// the same pattern doesn't have to be duplicated per project. +/// +public class TestLogger(TestContext? testContext = null) : ILogger { public readonly object Guard = new(); private readonly List _messages = []; @@ -31,7 +37,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except HasWarning |= logLevel is LogLevel.Warning; _messages.Add(message); - output?.WriteLine(message); + testContext?.WriteLine(message); } } diff --git a/test/Microsoft.DotNet.Test.MSTest.Utilities/TestLoggerFactory.cs b/test/Microsoft.DotNet.Test.MSTest.Utilities/TestLoggerFactory.cs new file mode 100644 index 000000000000..15485a93f137 --- /dev/null +++ b/test/Microsoft.DotNet.Test.MSTest.Utilities/TestLoggerFactory.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.DotNet.Test.MSTest.Utilities; + +/// +/// An that writes log messages to an MSTest +/// (when provided) and a simple console sink. Useful for tests that +/// need a real (e.g. for components that take one in their ctor). +/// +public sealed class TestLoggerFactory : ILoggerFactory +{ + private readonly List _loggerProviders = new(); + private readonly List _factories = new(); + + public TestLoggerFactory(TestContext? testContext = null) + { + if (testContext is not null) + { + _loggerProviders.Add(new TestContextLoggerProvider(testContext)); + } + } + + public void Dispose() + { + while (_factories.Count > 0) + { + ILoggerFactory factory = _factories[0]; + _factories.RemoveAt(0); + factory.Dispose(); + } + } + + public ILogger CreateLogger(string categoryName) + { + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + { + builder.SetMinimumLevel(LogLevel.Trace); + + foreach (ILoggerProvider loggerProvider in _loggerProviders) + { + builder.AddProvider(loggerProvider); + } + + builder.AddSimpleConsole(options => + { + options.SingleLine = true; + options.TimestampFormat = "[yyyy-MM-dd HH:mm:ss.fff] "; + options.IncludeScopes = true; + }); + }); + + _factories.Add(loggerFactory); + return loggerFactory.CreateLogger(categoryName); + } + + public ILogger CreateLogger() => CreateLogger("Test Host"); + + public void AddProvider(ILoggerProvider provider) => _loggerProviders.Add(provider); + + private sealed class TestContextLoggerProvider(TestContext testContext) : ILoggerProvider + { + public ILogger CreateLogger(string categoryName) => new TestContextLogger(testContext, categoryName); + + public void Dispose() + { + } + } + + private sealed class TestContextLogger(TestContext testContext, string categoryName) : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull => NullScope.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + testContext.WriteLine($"{logLevel}: {categoryName}: {formatter(state, exception)}"); + if (exception is not null) + { + testContext.WriteLine(exception.ToString()); + } + } + } + + private sealed class NullScope : IDisposable + { + public static readonly NullScope Instance = new(); + + public void Dispose() + { + } + } +} From 4305afa5119181884846882ffc96705c8ca70b64 Mon Sep 17 00:00:00 2001 From: Evangelink Date: Thu, 11 Jun 2026 23:33:52 +0200 Subject: [PATCH 09/15] Migrate Microsoft.Extensions.DotNetDeltaApplier.Tests to MSTest.Sdk - Switch SDK to MSTest.Sdk; set UseMSTestSdk=true to opt out of test/Directory.Build.targets xUnit defaults. - Replace [Fact]/[Theory] + [InlineData] with [TestMethod] + [DataRow]. - Replace Xunit.Combinatorial with Combinatorial.MSTest 2.0.0 (added to eng/dependabot/Packages.props). - Replace ITestOutputHelper with TestContext via a project-local TestLogger. - Replace xUnit assertions with MSTest assertions and remove the Microsoft.NET.TestFramework reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/dependabot/Packages.props | 2 +- .../HotReloadAgentTest.cs | 88 ++++++++++--------- .../HotReloadClientTests.cs | 22 ++--- .../ManagedCodeUpdateRequestTests.cs | 17 ++-- ...Extensions.DotNetDeltaApplier.Tests.csproj | 16 ++-- .../Mocks/TestLogger.cs | 53 +++++++++++ .../StaticAssetUpdateRequestTests.cs | 11 +-- .../StreamExtensionsTests.cs | 30 ++++--- 8 files changed, 149 insertions(+), 90 deletions(-) create mode 100644 test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props index e5a597a4eea3..fbe14886f59d 100644 --- a/eng/dependabot/Packages.props +++ b/eng/dependabot/Packages.props @@ -1,4 +1,4 @@ - + diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadAgentTest.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadAgentTest.cs index 7125d426fcdc..e4f9e6ab1b63 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadAgentTest.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadAgentTest.cs @@ -7,9 +7,10 @@ namespace Microsoft.DotNet.Watch.UnitTests { + [TestClass] public class HotReloadAgentTest { - [Fact] + [TestMethod] public void ClearHotReloadEnvironmentVariables_DoesNotThrow_WhenStartupHooksNotSet() { // Ensure DOTNET_STARTUP_HOOKS is not set @@ -27,30 +28,31 @@ public void ClearHotReloadEnvironmentVariables_DoesNotThrow_WhenStartupHooksNotS } } - [Fact] + [TestMethod] public void ClearHotReloadEnvironmentVariables_ClearsStartupHook() { - Assert.Equal("", + Assert.AreEqual("", HotReloadAgent.RemoveCurrentAssembly(typeof(StartupHook), typeof(StartupHook).Assembly.Location)); } - [Fact] + [TestMethod] public void ClearHotReloadEnvironmentVariables_PreservedOtherStartupHooks() { var customStartupHook = "/path/mycoolstartup.dll"; - Assert.Equal(customStartupHook, + Assert.AreEqual(customStartupHook, HotReloadAgent.RemoveCurrentAssembly(typeof(StartupHook), typeof(StartupHook).Assembly.Location + Path.PathSeparator + customStartupHook)); } - [PlatformSpecificFact(TestPlatforms.Windows)] + [TestMethod] + [OSCondition(OperatingSystems.Windows)] public void ClearHotReloadEnvironmentVariables_RemovesHotReloadStartup_InCaseInvariantManner() { var customStartupHook = "/path/mycoolstartup.dll"; - Assert.Equal(customStartupHook, + Assert.AreEqual(customStartupHook, HotReloadAgent.RemoveCurrentAssembly(typeof(StartupHook), customStartupHook + Path.PathSeparator + typeof(StartupHook).Assembly.Location.ToUpperInvariant())); } - [Fact] + [TestMethod] public void TopologicalSort_Works() { // Arrange @@ -62,10 +64,10 @@ public void TopologicalSort_Works() var sortedList = MetadataUpdateHandlerInvoker.TopologicalSort(new[] { assembly2, assembly4, assembly1, assembly3 }); // Assert - Assert.Equal(new[] { assembly1, assembly2, assembly3, assembly4 }, sortedList); + Assert.AreSequenceEqual(new[] { assembly1, assembly2, assembly3, assembly4 }, sortedList, ReferenceEqualityComparer.Instance); } - [Fact] + [TestMethod] public void TopologicalSort_IgnoresUnknownReferencedAssemblies() { // Arrange @@ -77,10 +79,10 @@ public void TopologicalSort_IgnoresUnknownReferencedAssemblies() var sortedList = MetadataUpdateHandlerInvoker.TopologicalSort(new[] { assembly2, assembly4, assembly1, assembly3 }); // Assert - Assert.Equal(new[] { assembly1, assembly2, assembly3, assembly4 }, sortedList); + Assert.AreSequenceEqual(new[] { assembly1, assembly2, assembly3, assembly4 }, sortedList, ReferenceEqualityComparer.Instance); } - [Fact] + [TestMethod] public void TopologicalSort_WithCycles() { // Arrange @@ -93,55 +95,55 @@ public void TopologicalSort_WithCycles() var sortedList = MetadataUpdateHandlerInvoker.TopologicalSort(new[] { assembly2, assembly4, assembly1, assembly3, assembly5 }); // Assert - Assert.Equal(new[] { assembly1, assembly3, assembly2, assembly4, assembly5 }, sortedList); + Assert.AreSequenceEqual(new[] { assembly1, assembly3, assembly2, assembly4, assembly5 }, sortedList, ReferenceEqualityComparer.Instance); } - [Theory] - [InlineData(typeof(HandlerWithClearCache))] - [InlineData(typeof(HandlerWithUpdateApplication))] - [InlineData(typeof(HandlerWithUpdateContent))] + [TestMethod] + [DataRow(typeof(HandlerWithClearCache))] + [DataRow(typeof(HandlerWithUpdateApplication))] + [DataRow(typeof(HandlerWithUpdateContent))] public void GetHandlerActions_SingleAction(Type handlerType) { var reporter = new AgentReporter(); var invoker = new MetadataUpdateHandlerInvoker(reporter); var actions = invoker.GetUpdateHandlerActions([handlerType]); - Assert.Empty(reporter.GetAndClearLogEntries(ResponseLoggingLevel.Verbose)); + Assert.IsEmpty(reporter.GetAndClearLogEntries(ResponseLoggingLevel.Verbose)); if (handlerType == typeof(HandlerWithUpdateContent)) { - Assert.Single(actions.UpdateContentHandlers); - Assert.Empty(actions.ClearCacheHandlers); - Assert.Empty(actions.UpdateApplicationHandlers); + Assert.ContainsSingle(actions.UpdateContentHandlers); + Assert.IsEmpty(actions.ClearCacheHandlers); + Assert.IsEmpty(actions.UpdateApplicationHandlers); } else if (handlerType == typeof(HandlerWithUpdateApplication)) { - Assert.Single(actions.UpdateApplicationHandlers); - Assert.Empty(actions.ClearCacheHandlers); - Assert.Empty(actions.UpdateContentHandlers); + Assert.ContainsSingle(actions.UpdateApplicationHandlers); + Assert.IsEmpty(actions.ClearCacheHandlers); + Assert.IsEmpty(actions.UpdateContentHandlers); } else if (handlerType == typeof(HandlerWithClearCache)) { - Assert.Single(actions.ClearCacheHandlers); - Assert.Empty(actions.UpdateContentHandlers); - Assert.Empty(actions.UpdateApplicationHandlers); + Assert.ContainsSingle(actions.ClearCacheHandlers); + Assert.IsEmpty(actions.UpdateContentHandlers); + Assert.IsEmpty(actions.UpdateApplicationHandlers); } } - [Fact] + [TestMethod] public void GetHandlerActions_DiscoversActionsOnTypeWithAllActions() { var reporter = new AgentReporter(); var invoker = new MetadataUpdateHandlerInvoker(reporter); var actions = invoker.GetUpdateHandlerActions([typeof(HandlerWithAllActions)]); - AssertEx.Empty(reporter.GetAndClearLogEntries(ResponseLoggingLevel.Verbose)); - Assert.Equal(typeof(HandlerWithAllActions).GetMethod("ClearCache", BindingFlags.Static | BindingFlags.NonPublic), actions.ClearCacheHandlers.Single().Method); - Assert.Equal(typeof(HandlerWithAllActions).GetMethod("UpdateApplication", BindingFlags.Static | BindingFlags.NonPublic), actions.UpdateApplicationHandlers.Single().Method); - Assert.Equal(typeof(HandlerWithAllActions).GetMethod("UpdateContent", BindingFlags.Static | BindingFlags.NonPublic), actions.UpdateContentHandlers.Single().Method); + Assert.IsEmpty(reporter.GetAndClearLogEntries(ResponseLoggingLevel.Verbose)); + Assert.AreEqual(typeof(HandlerWithAllActions).GetMethod("ClearCache", BindingFlags.Static | BindingFlags.NonPublic), actions.ClearCacheHandlers.Single().Method); + Assert.AreEqual(typeof(HandlerWithAllActions).GetMethod("UpdateApplication", BindingFlags.Static | BindingFlags.NonPublic), actions.UpdateApplicationHandlers.Single().Method); + Assert.AreEqual(typeof(HandlerWithAllActions).GetMethod("UpdateContent", BindingFlags.Static | BindingFlags.NonPublic), actions.UpdateContentHandlers.Single().Method); } - [Fact] + [TestMethod] public void GetHandlerActions_LogsMessageIfMethodHasIncorrectSignature() { var reporter = new AgentReporter(); @@ -151,19 +153,19 @@ public void GetHandlerActions_LogsMessageIfMethodHasIncorrectSignature() var actions = invoker.GetUpdateHandlerActions([handlerType]); var log = reporter.GetAndClearLogEntries(ResponseLoggingLevel.WarningsAndErrors); - AssertEx.SequenceEqual( + Assert.AreSequenceEqual( [ $"Warning: Type '{handlerType}' has method 'Void ClearCache()' that does not match the required signature.", $"Warning: Type '{handlerType}' has method 'Void UpdateContent()' that does not match the required signature." ], log.Select(e => $"{e.severity}: {e.message}")); - Assert.Empty(actions.ClearCacheHandlers); - Assert.Empty(actions.UpdateContentHandlers); - Assert.Single(actions.UpdateApplicationHandlers); + Assert.IsEmpty(actions.ClearCacheHandlers); + Assert.IsEmpty(actions.UpdateContentHandlers); + Assert.ContainsSingle(actions.UpdateApplicationHandlers); } - [Fact] + [TestMethod] public void GetHandlerActions_LogsMessageIfNoActionsAreDiscovered() { var reporter = new AgentReporter(); @@ -173,13 +175,13 @@ public void GetHandlerActions_LogsMessageIfNoActionsAreDiscovered() var actions = invoker.GetUpdateHandlerActions([handlerType]); var log = reporter.GetAndClearLogEntries(ResponseLoggingLevel.WarningsAndErrors); - var logEntry = Assert.Single(log); - Assert.Equal( + var logEntry = Assert.ContainsSingle(log); + Assert.AreEqual( $"Expected to find a static method 'ClearCache', 'UpdateApplication' or 'UpdateContent' on type '{handlerType.AssemblyQualifiedName}' but neither exists.", logEntry.message); - Assert.Equal(AgentMessageSeverity.Warning, logEntry.severity); - Assert.Empty(actions.ClearCacheHandlers); - Assert.Empty(actions.UpdateApplicationHandlers); + Assert.AreEqual(AgentMessageSeverity.Warning, logEntry.severity); + Assert.IsEmpty(actions.ClearCacheHandlers); + Assert.IsEmpty(actions.UpdateApplicationHandlers); } private static Assembly GetAssembly(string fullName, AssemblyName[] dependencies) diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs index 506a00791e02..2ecacc1198d3 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs @@ -5,8 +5,10 @@ namespace Microsoft.DotNet.Watch.UnitTests; -public class HotReloadClientTests(ITestOutputHelper output) +[TestClass] +public class HotReloadClientTests { + public TestContext TestContext { get; set; } = null!; private sealed class Test : IAsyncDisposable { public readonly TestLogger Logger; @@ -15,10 +17,10 @@ private sealed class Test : IAsyncDisposable private readonly CancellationTokenSource _cancellationSource; private readonly Task _listenerTaskFactory; - public Test(ITestOutputHelper output, TestHotReloadAgent agent) + public Test(TestContext testContext, TestHotReloadAgent agent) { - Logger = new TestLogger(output); - AgentLogger = new TestLogger(output); + Logger = new TestLogger(testContext); + AgentLogger = new TestLogger(testContext); var clientTransport = new NamedPipeClientTransport(Logger); Client = new DefaultHotReloadClient(Logger, AgentLogger, startupHookPath: "", handlesStaticAssetUpdates: true, clientTransport); @@ -46,7 +48,7 @@ public async ValueTask DisposeAsync() } } - [Fact] + [TestMethod] public async Task ApplyManagedCodeUpdates() { var moduleId = Guid.NewGuid(); @@ -56,10 +58,10 @@ public async Task ApplyManagedCodeUpdates() Capabilities = "Baseline AddMethodToExistingType AddStaticFieldToExistingType", }; - await using var test = new Test(output, agent); + await using var test = new Test(TestContext, agent); var actualCapabilities = await test.Client.GetUpdateCapabilitiesAsync(CancellationToken.None); - AssertEx.SequenceEqual(["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "AddExplicitInterfaceImplementation"], actualCapabilities); + Assert.AreSequenceEqual(["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "AddExplicitInterfaceImplementation"], actualCapabilities); var update = new HotReloadManagedCodeUpdate( moduleId: moduleId, @@ -81,7 +83,7 @@ public async Task ApplyManagedCodeUpdates() Assert.Contains(agentMessage, agentMessages); } - [Fact] + [TestMethod] public async Task ApplyManagedCodeUpdates_Failure() { var agent = new TestHotReloadAgent() @@ -90,10 +92,10 @@ public async Task ApplyManagedCodeUpdates_Failure() ApplyManagedCodeUpdatesImpl = updates => throw new Exception("Bug!") }; - await using var test = new Test(output, agent); + await using var test = new Test(TestContext, agent); var actualCapabilities = await test.Client.GetUpdateCapabilitiesAsync(CancellationToken.None); - AssertEx.SequenceEqual(["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "AddExplicitInterfaceImplementation"], actualCapabilities); + Assert.AreSequenceEqual(["Baseline", "AddMethodToExistingType", "AddStaticFieldToExistingType", "AddExplicitInterfaceImplementation"], actualCapabilities); var update = new HotReloadManagedCodeUpdate( moduleId: Guid.NewGuid(), diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs index 4da8c8f74569..01ec4023454d 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs @@ -5,9 +5,10 @@ namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class ManagedCodeUpdateRequestTests { - [Fact] + [TestMethod] public async Task Roundtrip() { var initial = new ManagedCodeUpdateRequest( @@ -36,7 +37,7 @@ public async Task Roundtrip() AssertEqual(initial, read); } - [Fact] + [TestMethod] public async Task WithLargeDeltas() { var initial = new ManagedCodeUpdateRequest( @@ -61,19 +62,19 @@ public async Task WithLargeDeltas() private static void AssertEqual(ManagedCodeUpdateRequest initial, ManagedCodeUpdateRequest read) { - Assert.Equal(initial.Updates.Count, read.Updates.Count); + Assert.AreEqual(initial.Updates.Count, read.Updates.Count); for (var i = 0; i < initial.Updates.Count; i++) { var e = initial.Updates[i]; var a = read.Updates[i]; - Assert.Equal(e.ModuleId, a.ModuleId); - Assert.Equal(e.ILDelta, a.ILDelta); - Assert.Equal(e.MetadataDelta, a.MetadataDelta); - Assert.Equal(e.UpdatedTypes, a.UpdatedTypes); + Assert.AreEqual(e.ModuleId, a.ModuleId); + Assert.AreSequenceEqual(e.ILDelta, a.ILDelta); + Assert.AreSequenceEqual(e.MetadataDelta, a.MetadataDelta); + Assert.AreSequenceEqual(e.UpdatedTypes, a.UpdatedTypes); } - Assert.Equal(initial.ResponseLoggingLevel, read.ResponseLoggingLevel); + Assert.AreEqual(initial.ResponseLoggingLevel, read.ResponseLoggingLevel); } } diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj index d6b9a4167851..a062bca5eedb 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj @@ -1,22 +1,20 @@ - + + + true + $(RestoreAdditionalProjectSources);https://api.nuget.org/v3/index.json + $(SdkTargetFramework) Microsoft.Extensions.DotNetDeltaApplier MicrosoftAspNetCore - Exe - - - - - - - + diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs new file mode 100644 index 000000000000..554f4fb7d275 --- /dev/null +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; +using Microsoft.Extensions.Logging; + +namespace Microsoft.DotNet.Watch.UnitTests; + +internal class TestLogger(TestContext? testContext = null) : ILogger +{ + public readonly object Guard = new(); + private readonly List _messages = []; + + public Func IsEnabledImpl = _ => true; + + public bool HasError { get; private set; } + public bool HasWarning { get; private set; } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (logLevel == LogLevel.None) + { + return; + } + + var message = $"[{logLevel}] {formatter(state, exception)}"; + + lock (Guard) + { + HasError |= logLevel is LogLevel.Error or LogLevel.Critical; + HasWarning |= logLevel is LogLevel.Warning; + + _messages.Add(message); + testContext?.WriteLine(message); + } + } + + public ImmutableArray GetAndClearMessages() + { + lock (Guard) + { + var result = _messages.ToImmutableArray(); + _messages.Clear(); + return result; + } + } + + public IDisposable? BeginScope(TState state) + where TState : notnull => throw new NotImplementedException(); + + public bool IsEnabled(LogLevel logLevel) + => IsEnabledImpl(logLevel); +} diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StaticAssetUpdateRequestTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StaticAssetUpdateRequestTests.cs index f1dc9f445ec6..71109a969adb 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StaticAssetUpdateRequestTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StaticAssetUpdateRequestTests.cs @@ -5,9 +5,10 @@ namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class StaticAssetUpdateRequestTests { - [Fact] + [TestMethod] public async Task Roundtrip() { var initial = new StaticAssetUpdateRequest( @@ -29,9 +30,9 @@ public async Task Roundtrip() private static void AssertEqual(StaticAssetUpdateRequest initial, StaticAssetUpdateRequest read) { - Assert.Equal(initial.Update.AssemblyName, read.Update.AssemblyName); - Assert.Equal(initial.Update.RelativePath, read.Update.RelativePath); - Assert.Equal(initial.Update.IsApplicationProject, read.Update.IsApplicationProject); - AssertEx.SequenceEqual(initial.Update.Contents, read.Update.Contents); + Assert.AreEqual(initial.Update.AssemblyName, read.Update.AssemblyName); + Assert.AreEqual(initial.Update.RelativePath, read.Update.RelativePath); + Assert.AreEqual(initial.Update.IsApplicationProject, read.Update.IsApplicationProject); + Assert.AreSequenceEqual(initial.Update.Contents, read.Update.Contents); } } diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StreamExtensionsTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StreamExtensionsTests.cs index 73996d5019f0..6edd6b2fcb31 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StreamExtensionsTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/StreamExtensionsTests.cs @@ -1,10 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Combinatorial.MSTest; using Microsoft.DotNet.HotReload; namespace Microsoft.DotNet.Watch.UnitTests; +[TestClass] public class StreamExtensionsTests { private static async Task TestAsync( @@ -42,11 +44,11 @@ private static async Task TestAsync( actual = await asyncRead(stream, CancellationToken.None); } - Assert.Equal(expected, actual); - Assert.Equal(bytesWritten, stream.Position); + Assert.AreEqual(expected, actual); + Assert.AreEqual(bytesWritten, stream.Position); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_String( [CombinatorialValues("", "\u1234", "hello")] string expected, @@ -63,7 +65,7 @@ await TestAsync( useBinaryReader); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_7BitEncodedInt( [CombinatorialValues(-1, -127, -128, -255, -256, int.MinValue, 0, 1, 10, 127, 128, 255, 256, int.MaxValue)] int expected, @@ -80,10 +82,10 @@ await TestAsync( useBinaryReader); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_Byte( - [CombinatorialValues(0, 255)] byte expected, + [CombinatorialValues((byte)0, (byte)255)] byte expected, bool useBinaryWriter, bool useBinaryReader) { @@ -97,7 +99,7 @@ await TestAsync( useBinaryReader); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_Int32( [CombinatorialValues(int.MinValue, 0, int.MaxValue)] int expected, @@ -114,7 +116,7 @@ await TestAsync( useBinaryReader); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_Bool( bool expected, @@ -131,7 +133,7 @@ await TestAsync( useBinaryReader); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_Int32Array( [CombinatorialValues(0, 1, 1234)] int length) @@ -144,10 +146,10 @@ public async Task ReadWrite_Int32Array( stream.Position = 0; var actual = await stream.ReadIntArrayAsync(CancellationToken.None); - Assert.Equal(expected, actual); + Assert.AreSequenceEqual(expected, actual); } - [Theory] + [TestMethod] [CombinatorialData] public async Task ReadWrite_ByteArray( [CombinatorialValues(0, 1, 1234)] int length) @@ -160,10 +162,10 @@ public async Task ReadWrite_ByteArray( stream.Position = 0; var actual = await stream.ReadByteArrayAsync(CancellationToken.None); - Assert.Equal(expected, actual); + Assert.AreSequenceEqual(expected, actual); } - [Fact] + [TestMethod] public async Task ReadWrite_Guid() { var expected = Guid.NewGuid(); @@ -174,6 +176,6 @@ public async Task ReadWrite_Guid() stream.Position = 0; var actual = await stream.ReadGuidAsync(CancellationToken.None); - Assert.Equal(expected, actual); + Assert.AreEqual(expected, actual); } } From 7cf67916eabb860d6328fef3c8a716c99cb9218a Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 12 Jun 2026 13:37:12 +0200 Subject: [PATCH 10/15] Use built-in $(UsingMSTestSdk) property instead of custom $(UseMSTestSdk) MSTest.Sdk already sets $(UsingMSTestSdk)=true in its Sdk.props before Directory.Build.props is evaluated, so the custom true opt-in property is redundant. This change: - Removes true from MSTest.Sdk csproj(s). - Renames $(UseMSTestSdk) -> $(UsingMSTestSdk) in test/Directory.Build.targets (the xUnit-defaults gating condition) and in xunit-runner/{XUnitPublish,XUnitRunner}.targets (Helix MTP dispatcher detection). --- .../Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj | 3 --- .../Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj index 2ae0f2fe0244..0aec8ad4a1ee 100644 --- a/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj +++ b/test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj @@ -1,9 +1,6 @@ - - true $(SdkTargetFramework) Microsoft.DotNet.Watch.Aspire.UnitTests diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj index a062bca5eedb..8c83b9c1c898 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj @@ -1,9 +1,6 @@ - + - - true $(RestoreAdditionalProjectSources);https://api.nuget.org/v3/index.json $(SdkTargetFramework) From e17f13a084d5aadc0213864f48f05c96be37bf60 Mon Sep 17 00:00:00 2001 From: Evangelink <32149626+Evangelink@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:20:24 +0200 Subject: [PATCH 11/15] Helix dispatcher: gate --report-trx on TrxReport extension being loaded --report-trx is an MTP CLI argument provided only when the Microsoft.Testing.Extensions.TrxReport extension is loaded on the test host. MSTest.Sdk's Default/AllMicrosoft profiles enable it by default, but other MTP runners (e.g. xUnit v3 MTP) do not bundle the extension, so passing --report-trx to those hosts fails with 'unknown argument'. XUnitPublish.targets now exposes the GetTrxReportEnabled target which returns the value of EnableMicrosoftTestingExtensionsTrxReport. XUnitRunner.targets calls that target and propagates the value as the EnableTrxReport metadata of SDKCustomXUnitProject items. SDKCustomCreateXUnitWorkItemsWithTestExclusion reads that metadata and only appends --report-trx to the MTP command line when it is true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/dotnet-watch.Tests/dotnet-watch.Tests.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 3b45b8eb3031..24484402b0e1 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -18,8 +18,6 @@ - - From 262113b804887079e79a285c2a4607f3d3349a5d Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 12 Jun 2026 16:07:12 +0200 Subject: [PATCH 12/15] Restore Watch.Aspire ProjectReference in dotnet-watch.Tests.csproj The recent `Helix dispatcher: gate --report-trx on TrxReport extension being loaded` commit accidentally removed the ProjectReference to Microsoft.DotNet.HotReload.Watch.Aspire from dotnet-watch.Tests.csproj (introduced in the `Migrate Microsoft.DotNet.HotReload.Watch.Aspire.Tests to MSTest.Sdk on MTP` commit to allow the moved AspireLauncherIntegrationTests and PipeUtilities to compile). Without that reference, the build fails with: error CS0246: The type or namespace name 'WatchStatusEvent' could not be found (are you missing a using directive or an assembly reference?) [test/dotnet-watch.Tests/Aspire/PipeUtilities.cs] Re-add the ProjectReference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/dotnet-watch.Tests/dotnet-watch.Tests.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 24484402b0e1..3b45b8eb3031 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -18,6 +18,8 @@ + + From 2049acb645668a568fe3fac6ef73538c0e94bea5 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Fri, 12 Jun 2026 18:31:48 +0200 Subject: [PATCH 13/15] Use shared Microsoft.DotNet.Test.MSTest.Utilities for TestLogger Replace the local Mocks/TestLogger.cs copy with a project reference to the shared MSTest utilities project introduced in the Aspire migration PR (#54722), keeping a single source of truth for the TestLogger implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../HotReloadClientTests.cs | 1 + ...Extensions.DotNetDeltaApplier.Tests.csproj | 1 + .../Mocks/TestLogger.cs | 53 ------------------- 3 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs index 2ecacc1198d3..87a8ed8f333d 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.DotNet.HotReload; +using Microsoft.DotNet.Test.MSTest.Utilities; namespace Microsoft.DotNet.Watch.UnitTests; diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj index 8c83b9c1c898..cdfa9eee623a 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Microsoft.Extensions.DotNetDeltaApplier.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs deleted file mode 100644 index 554f4fb7d275..000000000000 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/Mocks/TestLogger.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using Microsoft.Extensions.Logging; - -namespace Microsoft.DotNet.Watch.UnitTests; - -internal class TestLogger(TestContext? testContext = null) : ILogger -{ - public readonly object Guard = new(); - private readonly List _messages = []; - - public Func IsEnabledImpl = _ => true; - - public bool HasError { get; private set; } - public bool HasWarning { get; private set; } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - if (logLevel == LogLevel.None) - { - return; - } - - var message = $"[{logLevel}] {formatter(state, exception)}"; - - lock (Guard) - { - HasError |= logLevel is LogLevel.Error or LogLevel.Critical; - HasWarning |= logLevel is LogLevel.Warning; - - _messages.Add(message); - testContext?.WriteLine(message); - } - } - - public ImmutableArray GetAndClearMessages() - { - lock (Guard) - { - var result = _messages.ToImmutableArray(); - _messages.Clear(); - return result; - } - } - - public IDisposable? BeginScope(TState state) - where TState : notnull => throw new NotImplementedException(); - - public bool IsEnabled(LogLevel logLevel) - => IsEnabledImpl(logLevel); -} From 00ba7b49fb37db3ed869cd20320d1e96e323aa23 Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Tue, 16 Jun 2026 05:58:29 +0200 Subject: [PATCH 14/15] Deploy Aspire launcher runtime closure for integration tests AspireLauncherIntegrationTests exec the Aspire launcher (Microsoft.DotNet.HotReload.Watch.Aspire) as a child process. ExcludeAssets=Runtime omitted the launcher's transitive runtime dependencies (Microsoft.CodeAnalysis*) from the test output, so the launched process crashed at startup with FileNotFoundException and the tests failed with Assert.NotNull. Removing ExcludeAssets=Runtime deploys the full closure next to the launcher. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/dotnet-watch.Tests/dotnet-watch.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj index 3b45b8eb3031..b31410e85e75 100644 --- a/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj +++ b/test/dotnet-watch.Tests/dotnet-watch.Tests.csproj @@ -19,7 +19,7 @@ - + From 5cdce270abf2eada5cdd3e0f1c3c0b970ba6345e Mon Sep 17 00:00:00 2001 From: Amaury Leveugle Date: Tue, 16 Jun 2026 22:32:33 +0200 Subject: [PATCH 15/15] Fix MSTest analyzer violations (MSTEST0037/0049/0023) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../HotReloadClientTests.cs | 2 +- .../ManagedCodeUpdateRequestTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs index 87a8ed8f333d..dfc3029279ce 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/HotReloadClientTests.cs @@ -30,7 +30,7 @@ public Test(TestContext testContext, TestHotReloadAgent agent) Client.InitiateConnection(CancellationToken.None); var agentTransport = new NamedPipeTransport(clientTransport.NamedPipeName, log: _ => { }, timeoutMS: Timeout.Infinite); var listener = new Listener(agentTransport, agent, log: _ => { }); - _listenerTaskFactory = Task.Run(() => listener.Listen(_cancellationSource.Token)); + _listenerTaskFactory = Task.Run(() => listener.Listen(_cancellationSource.Token), testContext.CancellationToken); } public async ValueTask DisposeAsync() diff --git a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs index 01ec4023454d..e64f62ed65b0 100644 --- a/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs +++ b/test/Microsoft.Extensions.DotNetDeltaApplier.Tests/ManagedCodeUpdateRequestTests.cs @@ -62,7 +62,7 @@ public async Task WithLargeDeltas() private static void AssertEqual(ManagedCodeUpdateRequest initial, ManagedCodeUpdateRequest read) { - Assert.AreEqual(initial.Updates.Count, read.Updates.Count); + Assert.HasCount(initial.Updates.Count, read.Updates); for (var i = 0; i < initial.Updates.Count; i++) {