From d784aac9ea3dcd0ff385d7daad166058921bf824 Mon Sep 17 00:00:00 2001 From: Rolling2405 <89894749+Rolling2405@users.noreply.github.com> Date: Thu, 30 Apr 2026 22:39:13 -0700 Subject: [PATCH 1/2] Add net10.0 to SupportedNetFrameworks, drop net9.0 (EOL May 2026) net9.0 STS reaches end-of-life on May 12, 2026. The repository has already moved in this direction: TargetFrameworks.cs in the test infrastructure lists net10.0 as primary with net8.0 as secondary, and the test utilities no longer reference net9.0. Update SupportedNetFrameworks from 'net8.0;net9.0' to 'net8.0;net10.0' to align the shipped package TFMs with this trajectory. All source and test projects using the \ property will automatically pick up the net10.0 TFM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 2 +- eng/Version.Details.xml | 20 ++++++++++---------- eng/Versions.props | 6 +++--- eng/common/AGENTS.md | 5 +++++ global.json | 2 +- 5 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 eng/common/AGENTS.md diff --git a/Directory.Build.props b/Directory.Build.props index fe3901dfe7..612bacd835 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,7 +29,7 @@ net9.0-windows10.0.17763.0 net8.0-windows10.0.18362.0 - net8.0;net9.0 + net8.0;net10.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6a717699fb..68fcd69b60 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,29 +1,29 @@ - + https://github.com/dotnet/arcade - 3ed6c23fee67b840de0fb5473ea6a95fc667b0a9 + 8a7b2d3d39078db20f33067c7929b88fdff4ee63 - + https://github.com/dotnet/arcade - 3ed6c23fee67b840de0fb5473ea6a95fc667b0a9 + 8a7b2d3d39078db20f33067c7929b88fdff4ee63 - + https://github.com/dotnet/arcade - 3ed6c23fee67b840de0fb5473ea6a95fc667b0a9 + 8a7b2d3d39078db20f33067c7929b88fdff4ee63 https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage 6b29c14c934bae1e26c21961b7e5ad1a25c4d3c5 - + https://github.com/microsoft/testfx - 0da10c24091f1ad35610ac05dd5aa7bd3ea79dd5 + e6af9df44f1c17bcae2798b15388641609add3a3 - + https://github.com/microsoft/testfx - 0da10c24091f1ad35610ac05dd5aa7bd3ea79dd5 + e6af9df44f1c17bcae2798b15388641609add3a3 diff --git a/eng/Versions.props b/eng/Versions.props index 61ec584c48..0aea2be2c8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -7,10 +7,10 @@ preview - 11.0.0-beta.26229.1 + 11.0.0-beta.26230.2 18.8.0-preview.26229.1 - 4.3.0-preview.26229.1 - 2.3.0-preview.26229.1 + 4.3.0-preview.26230.4 + 2.3.0-preview.26230.4 diff --git a/eng/common/AGENTS.md b/eng/common/AGENTS.md new file mode 100644 index 0000000000..a5ed8f7292 --- /dev/null +++ b/eng/common/AGENTS.md @@ -0,0 +1,5 @@ +# `eng/common` + +Files under `eng/common` come from [Arcade](https://github.com/dotnet/arcade). +Edits in `eng/common` will be overwritten by automation unless the changes are made directly in the Arcade repository. +For more information, see the [Arcade documentation](https://github.com/dotnet/arcade/tree/main/Documentation). diff --git a/global.json b/global.json index bb3dafdc76..4ca072a911 100644 --- a/global.json +++ b/global.json @@ -37,7 +37,7 @@ "runner": "Microsoft.Testing.Platform" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26229.1", + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26230.2", "MSBuild.Sdk.Extras": "3.0.44" } } From a7c9078aeed0e95b34f8b8fdc1f27fc631eb7858 Mon Sep 17 00:00:00 2001 From: Rolling2405 <89894749+Rolling2405@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:08:29 -0700 Subject: [PATCH 2/2] perf: use Lock, FrozenDictionary, Span, and throw helpers on NET8+/NET9+ - RandomId.cs: fix CA2002 (lock on interned string) + System.Threading.Lock - ClassCleanupManager.cs: dedicated lock field, CollectionsMarshal.GetValueRefOrNullRef - TypeNameHelper.cs: FrozenDictionary for BuiltInTypeNames - SerializerUtilities.cs: FrozenDictionary for Serializers/Deserializers - Assert.That.cs: AsSpan+SequenceEqual instead of Substring+Equals - MSTestDiscoverer.cs: ArgumentNullException.ThrowIfNull - MSTestExecutor.cs: ArgumentNullException.ThrowIfNull - AssertScope.cs: ObjectDisposedException.ThrowIf (resolves existing CA1513 pragma) - Assert.AreEqual.String.cs: range operator instead of Substring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../VSTestAdapter/MSTestDiscoverer.cs | 6 ++ .../VSTestAdapter/MSTestExecutor.cs | 10 ++ .../Execution/ClassCleanupManager.cs | 48 ++++++++-- .../RandomId.cs | 7 +- .../Logging/TypeNameHelper.cs | 26 +++++ .../ServerMode/JsonRpc/SerializerUtilities.cs | 94 +++++++++++-------- .../Assertions/Assert.AreEqual.String.cs | 4 +- .../TestFramework/Assertions/Assert.That.cs | 20 ++++ .../TestFramework/Assertions/AssertScope.cs | 10 +- 9 files changed, 171 insertions(+), 54 deletions(-) diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs index 5b46c15750..9f410c1740 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestDiscoverer.cs @@ -43,6 +43,11 @@ public void DiscoverTests(IEnumerable sources, IDiscoveryContext discove internal void DiscoverTests(IEnumerable sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink, IConfiguration? configuration, bool isMTP) { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(sources); + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(discoverySink); +#else if (sources is null) { throw new ArgumentNullException(nameof(sources)); @@ -57,6 +62,7 @@ internal void DiscoverTests(IEnumerable sources, IDiscoveryContext disco { throw new ArgumentNullException(nameof(discoverySink)); } +#endif if (MSTestDiscovererHelpers.InitializeDiscovery(sources, discoveryContext, logger, configuration, _testSourceHandler)) { diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs index 098ac21a30..995df40e0b 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs @@ -102,6 +102,10 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run PlatformServiceProvider.Instance.AdapterTraceLogger.Info("MSTestExecutor.RunTests: Running tests from testcases."); } +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(frameworkHandle); + ArgumentNullException.ThrowIfNull(tests); +#else if (frameworkHandle is null) { throw new ArgumentNullException(nameof(frameworkHandle)); @@ -112,6 +116,7 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run { throw new ArgumentNullException(nameof(tests)); } +#endif Ensure.NotEmpty(tests); @@ -130,6 +135,10 @@ internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? run PlatformServiceProvider.Instance.AdapterTraceLogger.Info("MSTestExecutor.RunTests: Running tests from sources."); } +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(frameworkHandle); + ArgumentNullException.ThrowIfNull(sources); +#else if (frameworkHandle is null) { throw new ArgumentNullException(nameof(frameworkHandle)); @@ -140,6 +149,7 @@ internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? run { throw new ArgumentNullException(nameof(sources)); } +#endif Ensure.NotEmpty(sources); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs index b64888c573..648d6e8bc9 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/ClassCleanupManager.cs @@ -5,28 +5,57 @@ using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; +#if NET8_0_OR_GREATER +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#endif namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution; internal sealed class ClassCleanupManager { - private readonly ConcurrentDictionary _remainingTestCountsByClass; +#if NET9_0_OR_GREATER + private readonly Lock _lock = new(); +#else + private readonly object _lock = new(); +#endif + private readonly Dictionary _remainingTestCountsByClass; public ClassCleanupManager(IEnumerable testsToRun) { _remainingTestCountsByClass = - new(testsToRun.GroupBy(t => t.TestMethod.FullClassName) + testsToRun.GroupBy(t => t.TestMethod.FullClassName) .ToDictionary( g => g.Key, - g => g.Count())); + g => g.Count()); } - public bool ShouldRunEndOfAssemblyCleanup => _remainingTestCountsByClass.IsEmpty; + public bool ShouldRunEndOfAssemblyCleanup + { + get + { + lock (_lock) + { + return _remainingTestCountsByClass.Count == 0; + } + } + } public void MarkTestComplete(TestMethod testMethod, out bool isLastTestInClass) { - lock (_remainingTestCountsByClass) + lock (_lock) { +#if NET8_0_OR_GREATER + ref int remainingCount = ref CollectionsMarshal.GetValueRefOrNullRef( + _remainingTestCountsByClass, testMethod.FullClassName); + if (Unsafe.IsNullRef(ref remainingCount)) + { + throw ApplicationStateGuard.Unreachable(); + } + + remainingCount--; + isLastTestInClass = remainingCount == 0; +#else if (!_remainingTestCountsByClass.TryGetValue(testMethod.FullClassName, out int remainingCount)) { throw ApplicationStateGuard.Unreachable(); @@ -35,20 +64,23 @@ public void MarkTestComplete(TestMethod testMethod, out bool isLastTestInClass) remainingCount--; _remainingTestCountsByClass[testMethod.FullClassName] = remainingCount; isLastTestInClass = remainingCount == 0; +#endif } } public void MarkClassComplete(string fullClassName) { - lock (_remainingTestCountsByClass) + lock (_lock) { - if (!_remainingTestCountsByClass.TryRemove(fullClassName, out int remainingTests) || + if (!_remainingTestCountsByClass.TryGetValue(fullClassName, out int remainingTests) || remainingTests != 0) { - // We failed to remove the class, or we are incorrectly marking the class as complete while there are remaining tests. + // We failed to find the class, or we are incorrectly marking the class as complete while there are remaining tests. // This should never happen. throw ApplicationStateGuard.Unreachable(); } + + _remainingTestCountsByClass.Remove(fullClassName); } } diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RandomId.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RandomId.cs index 1f1181702a..516272391e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RandomId.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RandomId.cs @@ -12,6 +12,11 @@ namespace Microsoft.Testing.Extensions; internal static class RandomId { private const string Pool = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +#if NET9_0_OR_GREATER + private static readonly Lock s_lock = new(); +#else + private static readonly object s_lock = new(); +#endif private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create(); /// @@ -23,7 +28,7 @@ private static string Next(int length) { int poolLength = Pool.Length; char[] id = new char[length]; - lock (Pool) + lock (s_lock) { for (int idIndex = 0; idIndex < length; idIndex++) { diff --git a/src/Platform/Microsoft.Testing.Platform/Logging/TypeNameHelper.cs b/src/Platform/Microsoft.Testing.Platform/Logging/TypeNameHelper.cs index addac6ed99..a3caa41593 100644 --- a/src/Platform/Microsoft.Testing.Platform/Logging/TypeNameHelper.cs +++ b/src/Platform/Microsoft.Testing.Platform/Logging/TypeNameHelper.cs @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif + namespace Microsoft.Testing.Platform.Logging; /// @@ -11,6 +15,27 @@ internal static class TypeNameHelper { private const char DefaultNestedTypeDelimiter = '+'; +#if NET8_0_OR_GREATER + private static readonly FrozenDictionary BuiltInTypeNames = new Dictionary + { + { typeof(void), "void" }, + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(long), "long" }, + { typeof(object), "object" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(string), "string" }, + { typeof(uint), "uint" }, + { typeof(ulong), "ulong" }, + { typeof(ushort), "ushort" }, + }.ToFrozenDictionary(); +#else private static readonly Dictionary BuiltInTypeNames = new() { { typeof(void), "void" }, @@ -30,6 +55,7 @@ internal static class TypeNameHelper { typeof(ulong), "ulong" }, { typeof(ushort), "ushort" }, }; +#endif /// /// Pretty print a type name. diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs index 0d1a572f86..daa9ae7a89 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // Note: System.Text.Json is only available in .NET 6.0 and above. @@ -6,6 +6,9 @@ #if !NETCOREAPP using Jsonite; #endif +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Helpers; @@ -13,8 +16,13 @@ namespace Microsoft.Testing.Platform.ServerMode; internal static class SerializerUtilities { +#if NET8_0_OR_GREATER + private static readonly FrozenDictionary Serializers; + private static readonly FrozenDictionary Deserializers; +#else private static readonly Dictionary Serializers; private static readonly Dictionary Deserializers; +#endif /// /// Initializes static members of the class. @@ -23,11 +31,11 @@ internal static class SerializerUtilities /// static SerializerUtilities() { - Serializers = []; - Deserializers = []; + var serializers = new Dictionary(); + var deserializers = new Dictionary(); - Serializers[typeof(object)] = new ObjectSerializer(_ => new Dictionary()); - Serializers[typeof(KeyValuePair)] = new ObjectSerializer>(o => + serializers[typeof(object)] = new ObjectSerializer(_ => new Dictionary()); + serializers[typeof(KeyValuePair)] = new ObjectSerializer>(o => { Dictionary values = new() { @@ -37,7 +45,7 @@ static SerializerUtilities() }); // Serialize response types. - Serializers[typeof(RequestMessage)] = new ObjectSerializer(req => + serializers[typeof(RequestMessage)] = new ObjectSerializer(req => { Dictionary values = new() { @@ -50,7 +58,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(ResponseMessage)] = new ObjectSerializer(res => + serializers[typeof(ResponseMessage)] = new ObjectSerializer(res => { Dictionary values = new() { @@ -62,7 +70,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(NotificationMessage)] = new ObjectSerializer(notification => + serializers[typeof(NotificationMessage)] = new ObjectSerializer(notification => { Dictionary values = new() { @@ -74,7 +82,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(ErrorMessage)] = new ObjectSerializer(error => + serializers[typeof(ErrorMessage)] = new ObjectSerializer(error => { Dictionary values = new() { @@ -92,7 +100,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(InitializeResponseArgs)] = new ObjectSerializer(res => + serializers[typeof(InitializeResponseArgs)] = new ObjectSerializer(res => { Dictionary values = new() { @@ -104,7 +112,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(ServerInfo)] = new ObjectSerializer(info => + serializers[typeof(ServerInfo)] = new ObjectSerializer(info => { Dictionary values = new() { @@ -115,12 +123,12 @@ static SerializerUtilities() return values; }); - Serializers[typeof(ServerCapabilities)] = new ObjectSerializer(capabilities => new Dictionary + serializers[typeof(ServerCapabilities)] = new ObjectSerializer(capabilities => new Dictionary { [JsonRpcStrings.Testing] = Serialize(capabilities.TestingCapabilities), }); - Serializers[typeof(ServerTestingCapabilities)] = new ObjectSerializer(capabilities => new Dictionary + serializers[typeof(ServerTestingCapabilities)] = new ObjectSerializer(capabilities => new Dictionary { [JsonRpcStrings.SupportsDiscovery] = capabilities.SupportsDiscovery, [JsonRpcStrings.MultiRequestSupport] = capabilities.MultiRequestSupport, @@ -129,7 +137,7 @@ static SerializerUtilities() [JsonRpcStrings.MultiConnectionProvider] = capabilities.MultiConnectionProvider, }); - Serializers[typeof(Artifact)] = new ObjectSerializer(res => new Dictionary + serializers[typeof(Artifact)] = new ObjectSerializer(res => new Dictionary { [JsonRpcStrings.Uri] = res.Uri, [JsonRpcStrings.Producer] = res.Producer, @@ -138,14 +146,14 @@ static SerializerUtilities() [JsonRpcStrings.Description] = res.Description, }); - Serializers[typeof(DiscoverResponseArgs)] = new ObjectSerializer(_ => new Dictionary()); + serializers[typeof(DiscoverResponseArgs)] = new ObjectSerializer(_ => new Dictionary()); - Serializers[typeof(RunResponseArgs)] = new ObjectSerializer(res => new Dictionary + serializers[typeof(RunResponseArgs)] = new ObjectSerializer(res => new Dictionary { [JsonRpcStrings.Attachments] = res.Artifacts.Select(f => Serialize(f)).ToList(), }); - Serializers[typeof(TestNodeUpdateMessage)] = new ObjectSerializer(ev => + serializers[typeof(TestNodeUpdateMessage)] = new ObjectSerializer(ev => { // TODO: Fill in the node properties Dictionary values = new() @@ -158,7 +166,7 @@ static SerializerUtilities() }); // Serialize event types. - Serializers[typeof(TestNodeStateChangedEventArgs)] = new ObjectSerializer(ev => + serializers[typeof(TestNodeStateChangedEventArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -169,7 +177,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(TestNode)] = new ObjectSerializer( + serializers[typeof(TestNode)] = new ObjectSerializer( n => { // RECALL TO UPDATE TESTS INSIDE FormatterUtilitiesTests.cs @@ -366,7 +374,7 @@ static SerializerUtilities() return properties; }); - Serializers[typeof(LogEventArgs)] = new ObjectSerializer(ev => + serializers[typeof(LogEventArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -377,7 +385,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(CancelRequestArgs)] = new ObjectSerializer(ev => + serializers[typeof(CancelRequestArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -387,7 +395,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(TelemetryEventArgs)] = new ObjectSerializer(ev => + serializers[typeof(TelemetryEventArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -398,7 +406,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(ProcessInfoArgs)] = new ObjectSerializer(ev => + serializers[typeof(ProcessInfoArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -429,7 +437,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(AttachDebuggerInfoArgs)] = new ObjectSerializer(ev => + serializers[typeof(AttachDebuggerInfoArgs)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -439,7 +447,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(TestsAttachments)] = new ObjectSerializer(ev => + serializers[typeof(TestsAttachments)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -449,7 +457,7 @@ static SerializerUtilities() return values; }); - Serializers[typeof(RunTestAttachment)] = new ObjectSerializer(ev => + serializers[typeof(RunTestAttachment)] = new ObjectSerializer(ev => { Dictionary values = new() { @@ -464,7 +472,7 @@ static SerializerUtilities() }); // Deserialize a generic JSON-RPC message - Deserializers[typeof(RpcMessage)] = new ObjectDeserializer(properties => + deserializers[typeof(RpcMessage)] = new ObjectDeserializer(properties => { ValidateJsonRpcHeader(properties); @@ -524,7 +532,7 @@ static SerializerUtilities() }); // Deserialize requests - Deserializers[typeof(InitializeRequestArgs)] = new ObjectDeserializer(properties => + deserializers[typeof(InitializeRequestArgs)] = new ObjectDeserializer(properties => { int processId = GetRequiredPropertyFromJson(properties, JsonRpcStrings.ProcessId); ClientInfo clientInfo = Deserialize(properties); @@ -533,7 +541,7 @@ static SerializerUtilities() return new InitializeRequestArgs(processId, clientInfo, capabilities); }); - Deserializers[typeof(ClientInfo)] = new ObjectDeserializer(properties => + deserializers[typeof(ClientInfo)] = new ObjectDeserializer(properties => { IDictionary info = GetRequiredPropertyFromJson>(properties, JsonRpcStrings.ClientInfo); string name = GetRequiredPropertyFromJson(info, JsonRpcStrings.Name); @@ -542,7 +550,7 @@ static SerializerUtilities() return new ClientInfo(name, protocolVersion); }); - Deserializers[typeof(ClientCapabilities)] = new ObjectDeserializer(properties => + deserializers[typeof(ClientCapabilities)] = new ObjectDeserializer(properties => { IDictionary capabilities = GetRequiredPropertyFromJson>(properties, JsonRpcStrings.Capabilities); IDictionary testingCapabilities = GetRequiredPropertyFromJson>(capabilities, JsonRpcStrings.Testing); @@ -551,7 +559,7 @@ static SerializerUtilities() return new ClientCapabilities(debuggerProvider); }); - Deserializers[typeof(InitializeResponseArgs)] = new ObjectDeserializer(properties => + deserializers[typeof(InitializeResponseArgs)] = new ObjectDeserializer(properties => { int processId = GetRequiredPropertyFromJson(properties, JsonRpcStrings.ProcessId); ServerInfo serverInfo = Deserialize(GetRequiredPropertyFromJson>(properties, JsonRpcStrings.ServerInfo)); @@ -560,7 +568,7 @@ static SerializerUtilities() return new InitializeResponseArgs(processId, serverInfo, capabilities); }); - Deserializers[typeof(ServerInfo)] = new ObjectDeserializer(properties => + deserializers[typeof(ServerInfo)] = new ObjectDeserializer(properties => { string name = GetRequiredPropertyFromJson(properties, JsonRpcStrings.Name); string protocolVersion = GetRequiredPropertyFromJson(properties, JsonRpcStrings.Version); @@ -568,7 +576,7 @@ static SerializerUtilities() return new ServerInfo(name, protocolVersion); }); - Deserializers[typeof(ServerCapabilities)] = new ObjectDeserializer(properties => + deserializers[typeof(ServerCapabilities)] = new ObjectDeserializer(properties => { IDictionary testingCapabilities = GetRequiredPropertyFromJson>(properties, JsonRpcStrings.Testing); bool supportsDiscovery = GetRequiredPropertyFromJson(testingCapabilities, JsonRpcStrings.SupportsDiscovery); @@ -585,7 +593,7 @@ static SerializerUtilities() MultiConnectionProvider: multiConnectionProvider)); }); - Deserializers[typeof(DiscoverRequestArgs)] = new ObjectDeserializer(properties => + deserializers[typeof(DiscoverRequestArgs)] = new ObjectDeserializer(properties => { _ = Guid.TryParse(GetRequiredPropertyFromJson(properties, JsonRpcStrings.RunId), out Guid runId); @@ -598,7 +606,7 @@ static SerializerUtilities() return new DiscoverRequestArgs(runId, tests, filter); }); - Deserializers[typeof(RunRequestArgs)] = new ObjectDeserializer(properties => + deserializers[typeof(RunRequestArgs)] = new ObjectDeserializer(properties => { _ = Guid.TryParse(GetRequiredPropertyFromJson(properties, JsonRpcStrings.RunId), out Guid runId); @@ -610,7 +618,7 @@ static SerializerUtilities() return new RunRequestArgs(runId, tests, filter); }); - Deserializers[typeof(TestNode)] = new ObjectDeserializer( + deserializers[typeof(TestNode)] = new ObjectDeserializer( properties => { string uid = string.Empty; @@ -654,7 +662,7 @@ static SerializerUtilities() }; }); - Deserializers[typeof(CancelRequestArgs)] = new ObjectDeserializer(properties => + deserializers[typeof(CancelRequestArgs)] = new ObjectDeserializer(properties => { object? idObj = GetOptionalPropertyFromJson(properties, JsonRpcStrings.Id); int id = GetIdFromJson(idObj) ?? throw new MessageFormatException("id field should be a string or an int"); @@ -662,10 +670,10 @@ static SerializerUtilities() return new CancelRequestArgs(id); }); - Deserializers[typeof(ExitRequestArgs)] = new ObjectDeserializer(_ => new ExitRequestArgs()); + deserializers[typeof(ExitRequestArgs)] = new ObjectDeserializer(_ => new ExitRequestArgs()); // Deserialize an error - Deserializers[typeof(ErrorMessage)] = new ObjectDeserializer(properties => + deserializers[typeof(ErrorMessage)] = new ObjectDeserializer(properties => { ValidateJsonRpcHeader(properties); object idObj = GetRequiredPropertyFromJson(properties, JsonRpcStrings.Id); @@ -704,6 +712,14 @@ static SerializerUtilities() Message: errorMessage, Data: data); }); + +#if NET8_0_OR_GREATER + Serializers = serializers.ToFrozenDictionary(); + Deserializers = deserializers.ToFrozenDictionary(); +#else + Serializers = serializers; + Deserializers = deserializers; +#endif } public static IEnumerable SerializerTypes => Serializers.Keys; diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.String.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.String.cs index aafc366e5c..77b2ad6d71 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.String.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.String.cs @@ -338,10 +338,10 @@ public static Tuple CreateStringPreviews(string expected, s } private static string EllipsisEnd(string text) - => $"{text.Substring(0, text.Length - 3)}..."; + => $"{text[..^3]}..."; private static string EllipsisStart(string text) - => $"...{text.Substring(3)}"; + => $"...{text[3..]}"; private static string MakeControlCharactersVisible(string text) { diff --git a/src/TestFramework/TestFramework/Assertions/Assert.That.cs b/src/TestFramework/TestFramework/Assertions/Assert.That.cs index 0894543cc7..95611a3dfe 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.That.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.That.cs @@ -378,7 +378,11 @@ private static string RemoveAnonymousTypeWrappers(string input) while (i < input.Length) { // Look for anonymous type pattern: new <>f__AnonymousType followed by generic parameters +#if NET8_0_OR_GREATER + if (i <= input.Length - 4 && input.AsSpan(i, 4).SequenceEqual("new ") && +#else if (i <= input.Length - 4 && input.Substring(i, 4) == "new " && +#endif i + 4 < input.Length && input.Substring(i + 4).StartsWith("<>f__AnonymousType", StringComparison.Ordinal)) { // Find the start of the constructor parameters @@ -520,7 +524,11 @@ private static bool TryMatchListInitPattern(string input, int startIndex, out st patternEnd = startIndex; // Check for "new " at the start +#if NET8_0_OR_GREATER + if (startIndex + 4 >= input.Length || !input.AsSpan(startIndex, 4).SequenceEqual("new ")) +#else if (startIndex + 4 >= input.Length || !input.Substring(startIndex, 4).Equals("new ", StringComparison.Ordinal)) +#endif { return false; } @@ -540,7 +548,11 @@ private static bool TryMatchListInitPattern(string input, int startIndex, out st foreach (string type in collectionTypes) { if (pos + type.Length < input.Length && +#if NET8_0_OR_GREATER + input.AsSpan(pos, type.Length).SequenceEqual(type.AsSpan())) +#else input.Substring(pos, type.Length).Equals(type, StringComparison.Ordinal)) +#endif { matchedType = type; pos += type.Length; @@ -554,7 +566,11 @@ private static bool TryMatchListInitPattern(string input, int startIndex, out st } // Check for "`1()" pattern +#if NET8_0_OR_GREATER + if (pos + 4 >= input.Length || !input.AsSpan(pos, 4).SequenceEqual("`1()")) +#else if (pos + 4 >= input.Length || !input.Substring(pos, 4).Equals("`1()", StringComparison.Ordinal)) +#endif { return false; } @@ -735,7 +751,11 @@ private static bool TryRemoveWrapper(string input, ref int index, string pattern Func transform, StringBuilder result) { if (index > input.Length - pattern.Length || +#if NET8_0_OR_GREATER + !input.AsSpan(index, pattern.Length).SequenceEqual(pattern.AsSpan())) +#else !string.Equals(input.Substring(index, pattern.Length), pattern, StringComparison.Ordinal)) +#endif { return false; } diff --git a/src/TestFramework/TestFramework/Assertions/AssertScope.cs b/src/TestFramework/TestFramework/Assertions/AssertScope.cs index 0d0b678ea0..e767a06e3a 100644 --- a/src/TestFramework/TestFramework/Assertions/AssertScope.cs +++ b/src/TestFramework/TestFramework/Assertions/AssertScope.cs @@ -38,12 +38,14 @@ internal AssertScope() /// The assertion failure exception. internal void AddError(AssertFailedException error) { -#pragma warning disable CA1513 // Use ObjectDisposedException throw helper - ThrowIf is not available on all target frameworks +#if NET7_0_OR_GREATER + ObjectDisposedException.ThrowIf(_disposed, this); +#else +#pragma warning disable CA1513 if (_disposed) - { throw new ObjectDisposedException(nameof(AssertScope)); - } -#pragma warning restore CA1513 // Use ObjectDisposedException throw helper +#pragma warning restore CA1513 +#endif _errors.Enqueue(ExceptionDispatchInfo.Capture(error)); }