From 04eacc14240df45f1f8e152f55501f2179170928 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Thu, 9 Jan 2025 22:57:18 -0800 Subject: [PATCH 1/8] Bump up to 3.0.2-pre --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 381127c..0c18b9d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.0.1", + "version": "3.0.2-pre.{height}", "nuGetPackageVersion": { "semVer": 2.0 }, From f02b46d4ba4deb35cf04e4c812a576ad95c26f45 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Tue, 14 Jan 2025 10:51:19 -0800 Subject: [PATCH 2/8] Latest dependencies, for xunit/xunit#3130 --- Versions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Versions.props b/Versions.props index 0426340..3134aa4 100644 --- a/Versions.props +++ b/Versions.props @@ -7,12 +7,12 @@ 17.12.0 8.0.0 $(MicrosoftNetTestSdkVersion) - 3.7.112 + 3.7.115 5.3.0 1.0.0-alpha.160 1.19.0 2.9.3 - 1.0.1 + 1.0.2-pre.4 From c157d6fd0f7481256f7db45fba652777209e3417 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Fri, 24 Jan 2025 12:47:24 -0800 Subject: [PATCH 3/8] Remove half-written test --- .../RunSettingsTests.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs index a42e78c..7e12409 100644 --- a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs +++ b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs @@ -323,23 +323,4 @@ public void RunSettingsHelperShouldIgnoreEvenIfAdditionalElementsExist() Assert.Equal("FrameworkCore10", runSettings.TargetFrameworkVersion); Assert.Equal("foo", runSettings.ReporterSwitch); } - - [Fact] - public void RunConfigurationOptionsOverrideXunitOptions() - { - string settingsXml = -@" - - - true - true - - - < - -"; - - var runSettings = RunSettings.Parse(settingsXml); - - } } From c6763603c97cd50b5ca01d99a2cd55c864d4021a Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Fri, 24 Jan 2025 12:47:59 -0800 Subject: [PATCH 4/8] Add support for configuring maximum print lengths in assertion messages --- Versions.props | 4 +- .../Utility/RunSettings.cs | 40 ++++++++ .../RunSettingsTests.cs | 96 +++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/Versions.props b/Versions.props index 3134aa4..9fda936 100644 --- a/Versions.props +++ b/Versions.props @@ -10,9 +10,9 @@ 3.7.115 5.3.0 1.0.0-alpha.160 - 1.19.0 + 1.20.0-pre.4 2.9.3 - 1.0.2-pre.4 + 1.1.0-pre.3 diff --git a/src/xunit.runner.visualstudio/Utility/RunSettings.cs b/src/xunit.runner.visualstudio/Utility/RunSettings.cs index 3edbd91..52e9e0f 100644 --- a/src/xunit.runner.visualstudio/Utility/RunSettings.cs +++ b/src/xunit.runner.visualstudio/Utility/RunSettings.cs @@ -9,6 +9,7 @@ namespace Xunit.Runner.VisualStudio; internal class RunSettings { public AppDomainSupport? AppDomain { get; set; } + public int? AssertEquivalentMaxDepth { get; set; } public string? Culture { get; set; } public bool DesignMode { get; set; } = false; public bool? DiagnosticMessages { get; set; } @@ -25,6 +26,10 @@ internal class RunSettings public bool? ParallelizeAssembly { get; set; } public bool? ParallelizeTestCollections { get; set; } public bool? PreEnumerateTheories { get; set; } + public int? PrintMaxEnumerableLength { get; set; } + public int? PrintMaxObjectDepth { get; set; } + public int? PrintMaxObjectMemberCount { get; set; } + public int? PrintMaxStringLength { get; set; } public string? ReporterSwitch { get; set; } public int? Seed { get; set; } public bool? ShadowCopy { get; set; } @@ -36,6 +41,8 @@ public void CopyTo(TestAssemblyConfiguration configuration) { if (AppDomain.HasValue) configuration.AppDomain = AppDomain; + if (AssertEquivalentMaxDepth.HasValue) + configuration.AssertEquivalentMaxDepth = AssertEquivalentMaxDepth; if (Culture is not null) configuration.Culture = Culture.ToUpperInvariant() switch { @@ -69,6 +76,14 @@ public void CopyTo(TestAssemblyConfiguration configuration) configuration.ParallelizeTestCollections = ParallelizeTestCollections; if (PreEnumerateTheories.HasValue) configuration.PreEnumerateTheories = PreEnumerateTheories; + if (PrintMaxEnumerableLength.HasValue) + configuration.PrintMaxEnumerableLength = PrintMaxEnumerableLength; + if (PrintMaxObjectDepth.HasValue) + configuration.PrintMaxObjectDepth = PrintMaxObjectDepth; + if (PrintMaxObjectMemberCount.HasValue) + configuration.PrintMaxObjectMemberCount = PrintMaxObjectMemberCount; + if (PrintMaxStringLength.HasValue) + configuration.PrintMaxStringLength = PrintMaxStringLength; if (Seed.HasValue) configuration.Seed = Seed; if (ShadowCopy.HasValue) @@ -99,6 +114,10 @@ public static RunSettings Parse(string? settingsXml) if (Enum.TryParse(appDomainString, ignoreCase: true, out var appDomain)) result.AppDomain = appDomain; + var assertEquivalentMaxDepthString = xunitElement.Element(Constants.Xunit.AssertEquivalentMaxDepth)?.Value; + if (int.TryParse(assertEquivalentMaxDepthString, out var assertEquivalentMaxDepth) && assertEquivalentMaxDepth >= 1) + result.AssertEquivalentMaxDepth = assertEquivalentMaxDepth; + result.Culture = xunitElement.Element(Constants.Xunit.Culture)?.Value; var diagnosticMessagesString = xunitElement.Element(Constants.Xunit.DiagnosticMessages)?.Value; @@ -177,6 +196,22 @@ public static RunSettings Parse(string? settingsXml) if (bool.TryParse(preEnumerateTheoriesString, out var preEnumerateTheories)) result.PreEnumerateTheories = preEnumerateTheories; + var printMaxEnumerableLengthString = xunitElement.Element(Constants.Xunit.PrintMaxEnumerableLength)?.Value; + if (int.TryParse(printMaxEnumerableLengthString, out var printMaxEnumerableLength) && printMaxEnumerableLength >= 0) + result.PrintMaxEnumerableLength = printMaxEnumerableLength; + + var printMaxObjectDepthString = xunitElement.Element(Constants.Xunit.PrintMaxObjectDepth)?.Value; + if (int.TryParse(printMaxObjectDepthString, out var printMaxObjectDepth) && printMaxObjectDepth >= 0) + result.PrintMaxObjectDepth = printMaxObjectDepth; + + var printMaxObjectMemberCountString = xunitElement.Element(Constants.Xunit.PrintMaxObjectMemberCount)?.Value; + if (int.TryParse(printMaxObjectMemberCountString, out var printMaxObjectMemberCount) && printMaxObjectMemberCount >= 0) + result.PrintMaxObjectMemberCount = printMaxObjectMemberCount; + + var printMaxStringLengthString = xunitElement.Element(Constants.Xunit.PrintMaxStringLength)?.Value; + if (int.TryParse(printMaxStringLengthString, out var printMaxStringLength) && printMaxStringLength >= 0) + result.PrintMaxStringLength = printMaxStringLength; + var reporterSwitchString = xunitElement.Element(Constants.Xunit.ReporterSwitch)?.Value; if (reporterSwitchString is not null) result.ReporterSwitch = reporterSwitchString; @@ -263,6 +298,7 @@ public static class RunConfiguration public static class Xunit { public const string AppDomain = nameof(AppDomain); + public const string AssertEquivalentMaxDepth = nameof(AssertEquivalentMaxDepth); public const string Culture = nameof(Culture); public const string DiagnosticMessages = nameof(DiagnosticMessages); public const string Explicit = nameof(Explicit); @@ -278,6 +314,10 @@ public static class Xunit public const string ParallelizeAssembly = nameof(ParallelizeAssembly); public const string ParallelizeTestCollections = nameof(ParallelizeTestCollections); public const string PreEnumerateTheories = nameof(PreEnumerateTheories); + public const string PrintMaxEnumerableLength = nameof(PrintMaxEnumerableLength); + public const string PrintMaxObjectDepth = nameof(PrintMaxObjectDepth); + public const string PrintMaxObjectMemberCount = nameof(PrintMaxObjectMemberCount); + public const string PrintMaxStringLength = nameof(PrintMaxStringLength); public const string Seed = nameof(Seed); public const string ReporterSwitch = nameof(ReporterSwitch); public const string ShadowCopy = nameof(ShadowCopy); diff --git a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs index 7e12409..0def5cb 100644 --- a/test/test.xunit.runner.visualstudio/RunSettingsTests.cs +++ b/test/test.xunit.runner.visualstudio/RunSettingsTests.cs @@ -1,6 +1,7 @@ extern alias VSTestAdapter; using System; +using System.Collections.Generic; using Xunit; using Xunit.Runner.Common; using Xunit.Sdk; @@ -11,6 +12,7 @@ public class RunSettingsTests void AssertDefaultValues(RunSettings runSettings) { Assert.Null(runSettings.AppDomain); + Assert.Null(runSettings.AssertEquivalentMaxDepth); Assert.False(runSettings.DesignMode); Assert.Null(runSettings.DiagnosticMessages); Assert.Null(runSettings.FailSkips); @@ -24,6 +26,10 @@ void AssertDefaultValues(RunSettings runSettings) Assert.Null(runSettings.ParallelizeAssembly); Assert.Null(runSettings.ParallelizeTestCollections); Assert.Null(runSettings.PreEnumerateTheories); + Assert.Null(runSettings.PrintMaxEnumerableLength); + Assert.Null(runSettings.PrintMaxObjectDepth); + Assert.Null(runSettings.PrintMaxObjectMemberCount); + Assert.Null(runSettings.PrintMaxStringLength); Assert.Null(runSettings.ReporterSwitch); Assert.Null(runSettings.ShadowCopy); Assert.Null(runSettings.ShowLiveOutput); @@ -169,6 +175,26 @@ public void AppDomain(string value, AppDomainSupport? expected) Assert.Equal(expected, runSettings.AppDomain); } + [Theory] + [InlineData("blarch", null)] + [InlineData("0", null)] + [InlineData("1", 1)] + [InlineData("42", 42)] + public void AssertEquivalentMaxDepth(string value, int? expected) + { + string settingsXml = +$@" + + + {value} + +"; + + var runSettings = RunSettings.Parse(settingsXml); + + Assert.Equal(expected, runSettings.AssertEquivalentMaxDepth); + } + [Theory] [InlineData(0, null)] [InlineData(1, 1)] @@ -260,6 +286,76 @@ public void MethodDisplayOptions(string value, TestMethodDisplayOptions? expecte Assert.Equal(expected, runSettings.MethodDisplayOptions); } + public static IEnumerable> ZeroOrMore = [("blarch", null), ("-1", null), ("0", 0), ("42", 42)]; + + [Theory] + [MemberData(nameof(ZeroOrMore))] + public void PrintMaxEnumerableLength(string value, int? expected) + { + string settingsXml = +$@" + + + {value} + +"; + + var runSettings = RunSettings.Parse(settingsXml); + + Assert.Equal(expected, runSettings.PrintMaxEnumerableLength); + } + + [Theory] + [MemberData(nameof(ZeroOrMore))] + public void PrintMaxObjectDepth(string value, int? expected) + { + string settingsXml = +$@" + + + {value} + +"; + + var runSettings = RunSettings.Parse(settingsXml); + + Assert.Equal(expected, runSettings.PrintMaxObjectDepth); + } + + [Theory] + [MemberData(nameof(ZeroOrMore))] + public void PrintMaxObjectMemberCount(string value, int? expected) + { + string settingsXml = +$@" + + + {value} + +"; + + var runSettings = RunSettings.Parse(settingsXml); + + Assert.Equal(expected, runSettings.PrintMaxObjectMemberCount); + } + + [Theory] + [MemberData(nameof(ZeroOrMore))] + public void PrintMaxStringLength(string value, int? expected) + { + string settingsXml = +$@" + + + {value} + +"; + + var runSettings = RunSettings.Parse(settingsXml); + + Assert.Equal(expected, runSettings.PrintMaxStringLength); + } + [Theory] [InlineData(false)] [InlineData(true)] From 2009e51a713410319b8c4ad0a109224e80627430 Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Fri, 24 Jan 2025 12:56:39 -0800 Subject: [PATCH 5/8] Update to .NET SDK 9 for builds --- .github/workflows/pull-request.yaml | 3 ++- .github/workflows/push-main.yaml | 3 ++- BUILDING.md | 38 +---------------------------- global.json | 2 +- tools/builder/build.csproj | 8 +++--- tools/builder/targets/Packages.cs | 8 +----- 6 files changed, 11 insertions(+), 51 deletions(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 621e2bf..e4209a6 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -8,6 +8,7 @@ jobs: name: "Build" runs-on: windows-2022 env: + DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE: true DOTNET_NOLOGO: true steps: - name: Clone source @@ -24,7 +25,7 @@ jobs: with: dotnet-version: | 6.0.x - 8.0.x + 9.0.x - name: Get .NET information run: dotnet --info diff --git a/.github/workflows/push-main.yaml b/.github/workflows/push-main.yaml index 1789030..a4d49a2 100644 --- a/.github/workflows/push-main.yaml +++ b/.github/workflows/push-main.yaml @@ -10,6 +10,7 @@ jobs: name: "Build" runs-on: windows-2022 env: + DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE: true DOTNET_NOLOGO: true steps: - name: Clone source @@ -26,7 +27,7 @@ jobs: with: dotnet-version: | 6.0.x - 8.0.x + 9.0.x - name: Get .NET information run: dotnet --info diff --git a/BUILDING.md b/BUILDING.md index 9e91b9d..2e2278c 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -8,7 +8,7 @@ as the only supported IDE environment (others like Resharper should work, though You will need the following software installed: * .NET Framework 4.7.2 or later (part of the Windows OS) -* [.NET SDK 8.0](https://dotnet.microsoft.com/download/dotnet/8.0) +* [.NET SDK 9.0](https://dotnet.microsoft.com/download/dotnet/9.0) * [.NET 6.0 Runtime](https://dotnet.microsoft.com/download/dotnet/6.0) * [git](https://git-scm.com/downloads) * PowerShell (or [PowerShell Core](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-6)) @@ -40,39 +40,3 @@ Ensure that you have configured PowerShell to be able to run local unsigned scri You can get a list of options: `./build --help` - -## Debugging - -Debugging the VS Adapter is tricky. There are two ways to do it depending on whether you want to do it under `net472` or `net6.0`. In all cases, you'll currently need to build your own test adapter NuGet package using `./build.ps1 Build` first to ensure you have local symbols. The symbols are not in the public package. It's helpful to add it to a local `\packages` directory and then use an entry like `` in your `NuGet.config` file to point to it. Don't forget to eventually delete it from your global profile `.nuget\packages\xunit...` when you're done. - -### `net472` -Easiest thing to do is add a `launchSettings.json` file that adds the `vstest.console.exe` as a startup project and point it to an xunit dll. Something like the following (use `/listtests` if you just want to debug the discovery portion): - -```json -{ - "profiles": { - "vstest console": { - "executablePath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\IDE\\CommonExtensions\\Microsoft\\TestWindow\\vstest.console.exe", - "commandLineArgs": ".\\bin\\Debug\\net472\\Tests.System.Reactive.dll /TestAdapterPath:.\\bin\\Debug\\net472 /listtests", - "workingDirectory": "C:\\dev\\RxNET\\Rx.NET\\Source\\Tests.System.Reactive\\" - } - } -} -``` - -With that as the startup project, you can set breakpoints and then hit them. You may need to manually load symbols the first time if it's not detected automatically. - -### `net6.0` - -Debugging the .NET Core version of the runner is currently much more difficult. You'll need [Process Explorer](https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx) to help locate the correct process to debug. This limitation should be improved in subsequent .NET Test Platform releases. - -1. Start a PowerShell console and navigate to the directory with your test project -2. Set an environment variable in that console session: `$env:VSTEST_HOST_DEBUG = 1` -3. Build your test project: `dotnet build` -4. Have VS open with the xUnit solution loaded -5. Have Process Explorer open and in Tree View mode. You may want to update the "highlight delay" settings to 2-3 settings. Defaults to 1. Make the list scroll roughly to the "d's" -6. Execute the test: `dotnet vstest .\bin\debug\net6.0\MyTest.dll` (you can use the `-lt` switch to do discovery only) -7. The test adapter will wait for about 30 seconds for you to attach a debugger. You need to look for the "lowest" `dotnet.exe` process in the tree like this: ![img](https://cloud.githubusercontent.com/assets/1427284/21454655/2ca31676-c8e8-11e6-937b-06b16d8b9254.png). In this case, the PID you're looking for is `79404`. -8. In VS, go to Debug -> Attach to Process and look for the PID (easiest to sort the column by PID). Ensure the debugger type is "Automatic" and it'll choose the CoreCLR debugger. -9. Attach and then quickly hit Continue. It should load up the adapter and related code with symbols. - diff --git a/global.json b/global.json index c19a2e0..2bc13e8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.100", "rollForward": "latestMinor" } } diff --git a/tools/builder/build.csproj b/tools/builder/build.csproj index 0f6aaf8..24f72c1 100644 --- a/tools/builder/build.csproj +++ b/tools/builder/build.csproj @@ -1,19 +1,19 @@ - $(NoWarn);CS8002 + enable Exe - net8.0 + net9.0 true - + - + diff --git a/tools/builder/targets/Packages.cs b/tools/builder/targets/Packages.cs index d3c86d0..b74ce38 100644 --- a/tools/builder/targets/Packages.cs +++ b/tools/builder/targets/Packages.cs @@ -23,14 +23,8 @@ public static async Task OnExecute(BuildContext context) .GetFiles(srcFolder, "*.nuspec", SearchOption.AllDirectories) .ToList(); - // You can't see the created package name in .NET 9+ SDK without doing detailed verbosity - var verbosity = - context.DotNetSdkVersion.Major <= 8 - ? context.Verbosity.ToString() - : "detailed"; - // Pack the .nuspec file(s) foreach (var nuspecFile in nuspecFiles.OrderBy(x => x)) - await context.Exec("dotnet", $"pack --nologo --no-build --configuration {context.ConfigurationText} --output {context.PackageOutputFolder} --verbosity {verbosity} \"{Path.GetDirectoryName(nuspecFile)}\" -p:NuspecFile={Path.GetFileName(nuspecFile)}"); + await context.Exec("dotnet", $"pack --nologo --no-build --configuration {context.ConfigurationText} --output {context.PackageOutputFolder} --verbosity {context.Verbosity} \"{Path.GetDirectoryName(nuspecFile)}\" -p:NuspecFile={Path.GetFileName(nuspecFile)} -tl:off"); } } From ecace34afde83968521ef010a8aea752ae5136ed Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Fri, 24 Jan 2025 12:59:46 -0800 Subject: [PATCH 6/8] Remove commented out project element --- tools/builder/build.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/builder/build.csproj b/tools/builder/build.csproj index 24f72c1..db61dd7 100644 --- a/tools/builder/build.csproj +++ b/tools/builder/build.csproj @@ -1,7 +1,6 @@ - enable Exe net9.0 From b67d776b63cff86a5455df86553212fd494329dc Mon Sep 17 00:00:00 2001 From: Matt Richardson Date: Mon, 3 Feb 2025 09:13:58 +1100 Subject: [PATCH 7/8] Defer reporting until TestComplete --- Versions.props | 6 +- .../Sinks/VsExecutionSink.cs | 98 ++++++++++++++++++- test/Directory.Build.props | 2 + 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/Versions.props b/Versions.props index 9fda936..cfdfe70 100644 --- a/Versions.props +++ b/Versions.props @@ -1,7 +1,7 @@ - 2.0.36 + 2.0.37 6.0.32 1.0.3 17.12.0 @@ -10,9 +10,9 @@ 3.7.115 5.3.0 1.0.0-alpha.160 - 1.20.0-pre.4 + 1.20.0-pre.9 2.9.3 - 1.1.0-pre.3 + 1.1.0-pre.11 diff --git a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs index 124f6b4..08318e8 100644 --- a/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs +++ b/src/xunit.runner.visualstudio/Sinks/VsExecutionSink.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; using System.Threading; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Xunit.Runner.Common; using Xunit.Sdk; using VsTestCase = Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase; @@ -16,6 +20,8 @@ namespace Xunit.Runner.VisualStudio; internal sealed class VsExecutionSink : TestMessageSink, IDisposable { + static readonly HashSet InvalidFileNameChars = Path.GetInvalidFileNameChars().ToHashSet(); + readonly Func cancelledThunk; readonly LoggerHelper logger; readonly IMessageSink innerSink; @@ -24,10 +30,12 @@ internal sealed class VsExecutionSink : TestMessageSink, IDisposable readonly ConcurrentDictionary startTimeByTestID = []; readonly ConcurrentDictionary> testCasesByAssemblyID = []; readonly ConcurrentDictionary testCasesByCaseID = []; + readonly ConcurrentDictionary testResultByCaseID = []; readonly ConcurrentDictionary> testCasesByClassID = []; readonly ConcurrentDictionary> testCasesByCollectionID = []; readonly ConcurrentDictionary> testCasesByMethodID = []; readonly Dictionary testCasesMap; + static readonly Uri uri = new(Constants.ExecutorUri); public VsExecutionSink( IMessageSink innerSink, @@ -298,7 +306,7 @@ void HandleTestFailed(MessageHandlerArgs args) result.ErrorMessage = ExceptionUtility.CombineMessages(testFailed); result.ErrorStackTrace = ExceptionUtility.CombineStackTraces(testFailed); - TryAndReport("RecordResult (Fail)", testFailed, () => recorder.RecordResult(result)); + DeferReportUntilTestFinished("RecordResult (Fail)", testFailed, result); } else LogWarning(testFailed, "(Fail) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testFailed), testFailed.TestCaseUniqueID); @@ -306,8 +314,69 @@ void HandleTestFailed(MessageHandlerArgs args) HandleCancellation(args); } - void HandleTestFinished(MessageHandlerArgs args) => + void HandleTestFinished(MessageHandlerArgs args) + { + var testUniqueID = args.Message.TestUniqueID; + + if (testResultByCaseID.TryRemove(testUniqueID, out var testResultEntry)) + { + var (actionDescription, test, testResult) = testResultEntry; + var attachments = args.Message.Attachments; + + if (attachments.Count != 0) + try + { + var basePath = Path.Combine(Path.GetTempPath(), testUniqueID); + Directory.CreateDirectory(basePath); + + var attachmentSet = new AttachmentSet(uri, "xUnit.net"); + + foreach (var kvp in attachments) + { + var localFilePath = Path.Combine(basePath, SanitizeFileName(kvp.Key)); + + try + { + var attachmentType = kvp.Value.AttachmentType; + + if (attachmentType == TestAttachmentType.String) + { + localFilePath += ".txt"; + File.WriteAllText(localFilePath, kvp.Value.AsString()); + } + else if (attachmentType == TestAttachmentType.ByteArray) + { + var (byteArray, mediaType) = kvp.Value.AsByteArray(); + localFilePath += MediaTypeUtility.GetFileExtension(mediaType); + File.WriteAllBytes(localFilePath, byteArray); + } + else + { + LogWarning(test, "Unknown test attachment type '{0}' for attachment '{1}' [test case ID '{2}']", attachmentType, kvp.Key, testUniqueID); + localFilePath = null; + } + + if (localFilePath is not null) + attachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(localFilePath, kvp.Key)); + } + catch (Exception ex) + { + LogWarning(test, "Exception while adding attachment '{0}' in '{1}' [test case ID '{2}']: {3}", kvp.Key, localFilePath, testUniqueID, ex); + } + } + + testResult.Attachments.Add(attachmentSet); + } + catch (Exception ex) + { + LogWarning(test, "Exception while adding attachments [test case ID '{0}']: {1}", testUniqueID, ex); + } + + TryAndReport(actionDescription, test, () => recorder.RecordResult(testResult)); + } + MetadataCache(args.Message)?.TryRemove(args.Message); + } void HandleTestMethodCleanupFailure(MessageHandlerArgs args) { @@ -340,7 +409,7 @@ void HandleTestNotRun(MessageHandlerArgs args) var result = MakeVsTestResult(VsTestOutcome.None, testNotRun, startTime); if (result is not null) - TryAndReport("RecordResult (None)", testNotRun, () => recorder.RecordResult(result)); + DeferReportUntilTestFinished("RecordResult (None)", testNotRun, result); else LogWarning(testNotRun, "(NotRun) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testNotRun), testNotRun.TestCaseUniqueID); @@ -354,7 +423,7 @@ void HandleTestPassed(MessageHandlerArgs args) var result = MakeVsTestResult(VsTestOutcome.Passed, testPassed, startTime); if (result is not null) - TryAndReport("RecordResult (Pass)", testPassed, () => recorder.RecordResult(result)); + DeferReportUntilTestFinished("RecordResult (Pass)", testPassed, result); else LogWarning(testPassed, "(Pass) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testPassed), testPassed.TestCaseUniqueID); @@ -368,7 +437,7 @@ void HandleTestSkipped(MessageHandlerArgs args) var result = MakeVsTestResult(VsTestOutcome.Skipped, testSkipped, startTime); if (result is not null) - TryAndReport("RecordResult (Skip)", testSkipped, () => recorder.RecordResult(result)); + DeferReportUntilTestFinished("RecordResult (Skip)", testSkipped, result); else LogWarning(testSkipped, "(Skip) Could not find VS test case for {0} (ID = {1})", TestDisplayName(testSkipped), testSkipped.TestCaseUniqueID); @@ -494,6 +563,25 @@ string TestDisplayName(ITestMessage msg) => string TestMethodName(ITestMethodMessage msg) => TestClassName(msg) + "." + MetadataCache(msg)?.TryGetMethodMetadata(msg)?.MethodName ?? $""; + void DeferReportUntilTestFinished( + string actionDescription, + ITestMessage test, + VsTestResult testResult) => + testResultByCaseID.TryAdd(test.TestUniqueID, (actionDescription, test, testResult)); + + string SanitizeFileName(string fileName) + { + var result = new StringBuilder(fileName.Length); + + foreach (var c in fileName) + if (InvalidFileNameChars.Contains(c)) + result.Append('_'); + else + result.Append(c); + + return result.ToString(); + } + void TryAndReport( string actionDescription, ITestCaseMessage testCase, diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 09d58b3..abd6111 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -4,6 +4,8 @@ true portable + + true false 12.0 $(MSBuildProjectName) From dd36e86129dcb108d86eb3650eba5fae5fc4c60a Mon Sep 17 00:00:00 2001 From: Brad Wilson Date: Thu, 6 Feb 2025 23:07:22 -0800 Subject: [PATCH 8/8] v3.0.2 --- Versions.props | 4 ++-- version.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Versions.props b/Versions.props index cfdfe70..52fb97d 100644 --- a/Versions.props +++ b/Versions.props @@ -10,9 +10,9 @@ 3.7.115 5.3.0 1.0.0-alpha.160 - 1.20.0-pre.9 + 1.20.0 2.9.3 - 1.1.0-pre.11 + 1.1.0 diff --git a/version.json b/version.json index 0c18b9d..5e22190 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.0.2-pre.{height}", + "version": "3.0.2", "nuGetPackageVersion": { "semVer": 2.0 },