From 8ed3a577fc4a32e465923d4dc5a260d282f334c3 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 14 Apr 2026 12:11:42 +0200 Subject: [PATCH 01/24] Remove Polyfill from MSTest.Engine --- .../Engine/TestArgumentsManager.cs | 6 +- .../MSTest.Engine/MSTest.Engine.csproj | 2 +- .../CompilerFeatureRequiredAttribute.cs | 50 ++++++ src/Polyfills/EmbeddedAttribute.cs | 7 + src/Polyfills/HashHelpers.cs | 19 +++ src/Polyfills/Index.cs | 153 ++++++++++++++++++ src/Polyfills/IsExternalInit.cs | 20 +++ src/Polyfills/NullableAttribtues.cs | 150 +++++++++++++++++ src/Polyfills/Range.cs | 99 ++++++++++++ src/Polyfills/RequiredMemberAttribute.cs | 26 +++ 10 files changed, 527 insertions(+), 5 deletions(-) create mode 100644 src/Polyfills/CompilerFeatureRequiredAttribute.cs create mode 100644 src/Polyfills/EmbeddedAttribute.cs create mode 100644 src/Polyfills/HashHelpers.cs create mode 100644 src/Polyfills/Index.cs create mode 100644 src/Polyfills/IsExternalInit.cs create mode 100644 src/Polyfills/NullableAttribtues.cs create mode 100644 src/Polyfills/Range.cs create mode 100644 src/Polyfills/RequiredMemberAttribute.cs diff --git a/src/Adapter/MSTest.Engine/Engine/TestArgumentsManager.cs b/src/Adapter/MSTest.Engine/Engine/TestArgumentsManager.cs index 01ce2baed0..142f2bfdaa 100644 --- a/src/Adapter/MSTest.Engine/Engine/TestArgumentsManager.cs +++ b/src/Adapter/MSTest.Engine/Engine/TestArgumentsManager.cs @@ -19,10 +19,8 @@ public void RegisterTestArgumentsEntryProvider( throw new InvalidOperationException("Cannot register TestArgumentsEntry provider after registration is frozen."); } - if (!_testArgumentsEntryProviders.TryAdd(testNodeStableUid, argumentPropertiesProviderCallback)) - { - throw new InvalidOperationException($"TestArgumentsEntry provider is already registered for test node with UID '{testNodeStableUid}'."); - } + // Add will throw an exception if the key already exists, which is intended. + _testArgumentsEntryProviders.Add(testNodeStableUid, argumentPropertiesProviderCallback); } internal void FreezeRegistration() => _isRegistrationFrozen = true; diff --git a/src/Adapter/MSTest.Engine/MSTest.Engine.csproj b/src/Adapter/MSTest.Engine/MSTest.Engine.csproj index d9d5992e1e..77fd557b59 100644 --- a/src/Adapter/MSTest.Engine/MSTest.Engine.csproj +++ b/src/Adapter/MSTest.Engine/MSTest.Engine.csproj @@ -60,7 +60,7 @@ This package provides a new experimental engine for MSTest test framework.]]> - + diff --git a/src/Polyfills/CompilerFeatureRequiredAttribute.cs b/src/Polyfills/CompilerFeatureRequiredAttribute.cs new file mode 100644 index 0000000000..d3ecae7cee --- /dev/null +++ b/src/Polyfills/CompilerFeatureRequiredAttribute.cs @@ -0,0 +1,50 @@ +// + +#pragma warning disable + +#if !NETCOREAPP + +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + + +/// +/// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class CompilerFeatureRequiredAttribute : Attribute +{ + /// + /// Initialize a new instance of + /// + public CompilerFeatureRequiredAttribute(string featureName) => + FeatureName = featureName; + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } + + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; init; } + + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof(RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof(RequiredMembers); +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute))] +#endif diff --git a/src/Polyfills/EmbeddedAttribute.cs b/src/Polyfills/EmbeddedAttribute.cs new file mode 100644 index 0000000000..87e20d8d22 --- /dev/null +++ b/src/Polyfills/EmbeddedAttribute.cs @@ -0,0 +1,7 @@ +// + +using System; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class EmbeddedAttribute : Attribute; diff --git a/src/Polyfills/HashHelpers.cs b/src/Polyfills/HashHelpers.cs new file mode 100644 index 0000000000..b8a6fa0634 --- /dev/null +++ b/src/Polyfills/HashHelpers.cs @@ -0,0 +1,19 @@ +// + +#if !NETCOREAPP +using Microsoft.CodeAnalysis; + +namespace System.Numerics.Hashing; + +[Embedded] +internal static class HashHelpers +{ + public static int Combine(int h1, int h2) + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } +} +#endif diff --git a/src/Polyfills/Index.cs b/src/Polyfills/Index.cs new file mode 100644 index 0000000000..04a4f1e06c --- /dev/null +++ b/src/Polyfills/Index.cs @@ -0,0 +1,153 @@ +// + +#pragma warning disable + +#nullable enable + +#if !NETCOREAPP +namespace System; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +internal readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return ToStringFromEnd(); + + return ((uint)Value).ToString(); + } + + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() + { + throw new ArgumentOutOfRangeException("value", "value must be non-negative"); + } + + private string ToStringFromEnd() + { + return '^' + Value.ToString(); + } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Index))] +#endif diff --git a/src/Polyfills/IsExternalInit.cs b/src/Polyfills/IsExternalInit.cs new file mode 100644 index 0000000000..9c102b9700 --- /dev/null +++ b/src/Polyfills/IsExternalInit.cs @@ -0,0 +1,20 @@ +// + +#pragma warning disable + +#if !NETCOREAPP + +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// +/// Reserved to be used by the compiler for tracking metadata. This class should not be used by developers in source code. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[Embedded] +internal static class IsExternalInit; +#else +[assembly: TypeForwardedTo(typeof(IsExternalInit))] +#endif diff --git a/src/Polyfills/NullableAttribtues.cs b/src/Polyfills/NullableAttribtues.cs new file mode 100644 index 0000000000..430eb35275 --- /dev/null +++ b/src/Polyfills/NullableAttribtues.cs @@ -0,0 +1,150 @@ +// + +#nullable enable + +// This was copied from https://github.com/dotnet/runtime/blob/39b9607807f29e48cae4652cd74735182b31182e/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs +// and updated to have the scope of the attributes be internal. + +#if !NETCOREAPP + +namespace System.Diagnostics.CodeAnalysis; + +/// Specifies that null is allowed as an input even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +internal sealed class AllowNullAttribute : Attribute { } + +/// Specifies that null is disallowed as an input even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +internal sealed class DisallowNullAttribute : Attribute { } + +/// Specifies that an output may be null even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +internal sealed class MaybeNullAttribute : Attribute { } + +/// Specifies that an output will not be null even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +internal sealed class NotNullAttribute : Attribute { } + +/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class MaybeNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that the output will be non-null if the named parameter is non-null. +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +internal sealed class NotNullIfNotNullAttribute : Attribute +{ + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } +} + +/// Applied to a method that will never return under any circumstance. +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +internal sealed class DoesNotReturnAttribute : Attribute { } + +/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +internal sealed class DoesNotReturnIfAttribute : Attribute +{ + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } +} + +#endif + +#if !NETCOREAPP + +/// Specifies that the method or property will ensure that the listed field and property members have not-null values. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +internal sealed class MemberNotNullAttribute : Attribute +{ + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } +} + +/// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +internal sealed class MemberNotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } +} + +#endif diff --git a/src/Polyfills/Range.cs b/src/Polyfills/Range.cs new file mode 100644 index 0000000000..71d0c8fae5 --- /dev/null +++ b/src/Polyfills/Range.cs @@ -0,0 +1,99 @@ +// + +#pragma warning disable + +#if !NETCOREAPP +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Numerics.Hashing; + +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +internal readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return HashHelpers.Combine(Start.GetHashCode(), End.GetHashCode()); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start.ToString() + ".." + End.ToString(); + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + ThrowArgumentOutOfRangeException(); + } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("length"); + } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Range))] +#endif diff --git a/src/Polyfills/RequiredMemberAttribute.cs b/src/Polyfills/RequiredMemberAttribute.cs new file mode 100644 index 0000000000..bea74977e3 --- /dev/null +++ b/src/Polyfills/RequiredMemberAttribute.cs @@ -0,0 +1,26 @@ +// + +#pragma warning disable + +#if !NETCOREAPP + +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// +/// Specifies that a type has required members or that a member is required. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +[EditorBrowsable(EditorBrowsableState.Never)] +[Embedded] +internal sealed class RequiredMemberAttribute : Attribute; +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.RequiredMemberAttribute))] +#endif From 025f921a3cd184e516e3dea4f1e66a80bb08be9d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 14 Apr 2026 12:22:42 +0200 Subject: [PATCH 02/24] Remove Polyfill from MSTest.TestAdapter --- .../MSTest.TestAdapter.csproj | 2 +- .../VSTestAdapter/MSTestExecutor.cs | 16 +++++++- .../CallerArgumentExpressionAttribute.cs | 35 +++++++++++++++++ src/Polyfills/Ensure.cs | 39 +++++++++++++++++++ src/Polyfills/ModuleInitializerAttribute.cs | 23 +++++++++++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/Polyfills/CallerArgumentExpressionAttribute.cs create mode 100644 src/Polyfills/Ensure.cs create mode 100644 src/Polyfills/ModuleInitializerAttribute.cs diff --git a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj index b6d063c7d2..5d44f04283 100644 --- a/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj +++ b/src/Adapter/MSTest.TestAdapter/MSTest.TestAdapter.csproj @@ -66,7 +66,7 @@ - + diff --git a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs index aa358c28f5..098ac21a30 100644 --- a/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs +++ b/src/Adapter/MSTest.TestAdapter/VSTestAdapter/MSTestExecutor.cs @@ -107,7 +107,13 @@ internal async Task RunTestsAsync(IEnumerable? tests, IRunContext? run throw new ArgumentNullException(nameof(frameworkHandle)); } - Ensure.NotNullOrEmpty(tests); + // TODO: Verify why VSTest annotates the IEnumerable as nullable. + if (tests is null) + { + throw new ArgumentNullException(nameof(tests)); + } + + Ensure.NotEmpty(tests); if (!MSTestDiscovererHelpers.InitializeDiscovery(from test in tests select test.Source, runContext, frameworkHandle, configuration, new TestSourceHandler())) { @@ -129,7 +135,13 @@ internal async Task RunTestsAsync(IEnumerable? sources, IRunContext? run throw new ArgumentNullException(nameof(frameworkHandle)); } - Ensure.NotNullOrEmpty(sources); + // TODO: Verify why VSTest annotates the IEnumerable as nullable. + if (sources is null) + { + throw new ArgumentNullException(nameof(sources)); + } + + Ensure.NotEmpty(sources); TestSourceHandler testSourceHandler = new(); if (!MSTestDiscovererHelpers.InitializeDiscovery(sources, runContext, frameworkHandle, configuration, testSourceHandler)) diff --git a/src/Polyfills/CallerArgumentExpressionAttribute.cs b/src/Polyfills/CallerArgumentExpressionAttribute.cs new file mode 100644 index 0000000000..6b15f3a39a --- /dev/null +++ b/src/Polyfills/CallerArgumentExpressionAttribute.cs @@ -0,0 +1,35 @@ +// + +#pragma warning disable + +#if !NETCOREAPP + +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// +/// Indicates that a parameter captures the expression passed for another parameter as a string. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +[Embedded] +internal sealed class CallerArgumentExpressionAttribute : + Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public CallerArgumentExpressionAttribute(string parameterName) => + ParameterName = parameterName; + + /// + /// Gets the name of the parameter whose expression should be captured as a string. + /// + public string ParameterName { get; } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] +#endif diff --git a/src/Polyfills/Ensure.cs b/src/Polyfills/Ensure.cs new file mode 100644 index 0000000000..d1c4250222 --- /dev/null +++ b/src/Polyfills/Ensure.cs @@ -0,0 +1,39 @@ +// + +using Microsoft.CodeAnalysis; + +namespace Polyfills; + +[Embedded] +internal static class Ensure +{ + public static void NotEmpty(IEnumerable value, [CallerArgumentExpression(nameof(value))] string name = "") + { + const string ArgumentCannotBeEmptyMsg = "Argument cannot be empty."; + + if (value is null) + { + return; + } + else if (value is ICollection { Count: 0 }) + { + throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); + } + else if (value is ICollection { Count: 0 }) + { + throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); + } + else if (value is IReadOnlyCollection { Count: 0 }) + { + throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); + } + else if (value is T[] { Length: 0 }) + { + throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); + } + else if (!value.Any()) + { + throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); + } + } +} diff --git a/src/Polyfills/ModuleInitializerAttribute.cs b/src/Polyfills/ModuleInitializerAttribute.cs new file mode 100644 index 0000000000..a71d020007 --- /dev/null +++ b/src/Polyfills/ModuleInitializerAttribute.cs @@ -0,0 +1,23 @@ +// + +#if !NETCOREAPP +#pragma warning disable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; +/// +/// Used to indicate to the compiler that a method should be called +/// in its containing module's initializer. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Method, Inherited = false)] +[Embedded] +internal sealed class ModuleInitializerAttribute : Attribute; +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ModuleInitializerAttribute))] +#endif From 1c48411dc2188edcbd1c59d87539c33b84347096 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 10:04:34 +0200 Subject: [PATCH 03/24] Avoid Polyfill in PlatformServices --- .../AssemblyResolver.cs | 15 +- .../Execution/TestExecutionManager.cs | 8 +- .../Execution/TestMethodInfo.cs | 4 +- .../Execution/TypeCache.cs | 191 ++++++++++-------- .../Execution/UnitTestRunner.cs | 11 +- .../Extensions/MethodInfoExtensions.cs | 2 +- .../Helpers/FixtureMethodRunner.cs | 2 + .../Helpers/ReflectHelper.cs | 11 +- .../Helpers/RunSettingsUtilities.cs | 4 +- .../Helpers/TestDataSourceHelpers.cs | 2 +- .../Interfaces/ITestSourceHost.cs | 6 +- .../MSTestAdapter.PlatformServices.csproj | 5 +- .../MSTestSettings.cs | 17 +- .../ObjectModel/UnitTestElement.cs | 5 +- .../RunConfigurationSettings.cs | 8 +- .../Services/MSTestAdapterSettings.cs | 10 +- .../Services/SettingsProvider.cs | 6 +- .../Services/TestDataSource.cs | 4 + .../Services/TestSourceHost.cs | 8 +- .../Services/ThreadOperations.cs | 2 + .../Utilities/DeploymentItemUtility.cs | 4 +- .../Utilities/DeploymentUtility.cs | 29 ++- .../Utilities/DeploymentUtilityBase.cs | 3 - .../Utilities/XmlUtilities.cs | 5 +- src/Polyfills/Ensure.cs | 12 ++ src/Polyfills/ModuleInitializerAttribute.cs | 3 +- src/Polyfills/NullableAttribtues.cs | 31 ++- 27 files changed, 282 insertions(+), 126 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs index 9246404f96..345e8a2a27 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/AssemblyResolver.cs @@ -82,7 +82,11 @@ class AssemblyResolver : /// /// lock for the loaded assemblies cache. /// +#if NET9_0_OR_GREATER private readonly Lock _syncLock = new(); +#else + private readonly object _syncLock = new(); +#endif private static List? s_currentlyLoading; private bool _disposed; @@ -99,7 +103,16 @@ class AssemblyResolver : /// public AssemblyResolver(IList directories) { - Ensure.NotNullOrEmpty(directories); + if (directories is null) + { + throw new ArgumentNullException(nameof(directories)); + } + + // Caller always ensures non-empty. + if (directories.Count == 0) + { + throw ApplicationStateGuard.Unreachable(); + } _searchDirectories = [.. directories]; _directoryList = new Queue(); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs index c66382a236..3ca621511a 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestExecutionManager.cs @@ -547,16 +547,16 @@ private async Task ExecuteTestsWithTestRunnerAsync( // Add tcm properties. if (tcmProperties is not null) { - foreach ((TestProperty key, object? value) in tcmProperties) + foreach (KeyValuePair kvp in tcmProperties) { - testContextProperties[key.Id] = value; + testContextProperties[kvp.Key.Id] = kvp.Value; } } // Add source level parameters. - foreach ((string key, object value) in sourceLevelParameters) + foreach (KeyValuePair kvp in sourceLevelParameters) { - testContextProperties[key] = value; + testContextProperties[kvp.Key] = kvp.Value; } if (unitTestElement.Traits is { Length: > 0 }) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs index c64fd37f63..1b8756af00 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TestMethodInfo.cs @@ -1070,7 +1070,9 @@ private async Task ExecuteInternalWithTimeoutAsync(object?[]? argume else { // Cancel the token source as test has timed out - await TestContext.Context.CancellationTokenSource.CancelAsync().ConfigureAwait(false); +#pragma warning disable VSTHRD103 // Call async methods when in an async method - likely fine in this context. CancelAsync is .NET Core only. We prefer having the same behavior between .NET Core and .NET Framework. + TestContext.Context.CancellationTokenSource.Cancel(); +#pragma warning restore VSTHRD103 // Call async methods when in an async method } TestResult timeoutResult = new() { Outcome = UnitTestOutcome.Timeout, TestFailureException = new TestFailedException(UnitTestOutcome.Timeout, errorMessage) }; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs index fc670325af..8a46685730 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/TypeCache.cs @@ -134,26 +134,41 @@ public IEnumerable AssemblyInfoListWithExecutableCleanupMethod DebugEx.Assert(testMethod != null, "test method is null"); string typeName = testMethod.FullClassName; + // Using GetOrAdd to ensure we calculate only once when this is called by different threads in parallel. // Using a static lambda to ensure we don't capture. - return _classInfoCache.GetOrAdd(typeName, static (typeName, tuple) => +#if NETCOREAPP + return _classInfoCache.GetOrAdd(typeName, CreateTestClassInfo, (this, testMethod)); +#else + // On .NET Framework, we don't have the GetOrAdd overload that prevents capturing lambdas. + // So, we first try to get the value from the cache. + if (_classInfoCache.TryGetValue(typeName, out TestClassInfo? cachedClassInfo)) { - TestMethod testMethod = tuple.testMethod; - TypeCache @this = tuple.Item1; + return cachedClassInfo; + } - // Load the class type - Type? type = LoadType(typeName, testMethod.AssemblyName); + // If value doesn't already exist in the cache, we fallback to the GetOrAdd that allocates. + return _classInfoCache.GetOrAdd(typeName, typeName => CreateTestClassInfo(typeName, (this, testMethod))); +#endif + } - if (type == null) - { - // This means the class containing the test method could not be found. - // Return null so we return a not found result. - return null; - } + private static TestClassInfo? CreateTestClassInfo(string typeName, (TypeCache Cache, TestMethod Method) tuple) + { + TestMethod testMethod = tuple.Method; + TypeCache @this = tuple.Cache; + + // Load the class type + Type? type = LoadType(typeName, testMethod.AssemblyName); + + if (type == null) + { + // This means the class containing the test method could not be found. + // Return null so we return a not found result. + return null; + } - // Get the classInfo - return @this.CreateClassInfo(type); - }, (this, testMethod)); + // Get the classInfo + return @this.CreateClassInfo(type); } /// @@ -321,80 +336,94 @@ private TestClassInfo CreateClassInfo(Type classType) /// The assembly to get its info. /// The instance. private TestAssemblyInfo GetAssemblyInfo(Assembly assembly) + { +#if NETCOREAPP // Using GetOrAdd to ensure we calculate only once when this is called by different threads in parallel. // Using a static lambda to ensure we don't capture. - => _testAssemblyInfoCache.GetOrAdd(assembly, static (assembly, @this) => + return _testAssemblyInfoCache.GetOrAdd(assembly, CreateTestAssemblyInfo, this); +#else + if (_testAssemblyInfoCache.TryGetValue(assembly, out TestAssemblyInfo cachedTestAssemblyInfo)) + { + return cachedTestAssemblyInfo; + } + + // Not cached already. Fallback to GetOrAdd call that captures "this" and allocates. + return _testAssemblyInfoCache.GetOrAdd(assembly, assembly => CreateTestAssemblyInfo(assembly, this)); +#endif + } + + private static TestAssemblyInfo CreateTestAssemblyInfo(Assembly assembly, TypeCache @this) + { + var assemblyInfo = new TestAssemblyInfo(assembly); + + Type[] types = AssemblyEnumerator.GetTypes(assembly); + + foreach (Type t in types) + { + try + { + // Only examine classes which are TestClass or derives from TestClass attribute + if (!@this._reflectionHelper.IsAttributeDefined(t)) + { + continue; + } + } + catch (Exception ex) { - var assemblyInfo = new TestAssemblyInfo(assembly); + // If we fail to discover type from an assembly, then do not abort. Pick the next type. + if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled) + { + PlatformServiceProvider.Instance.AdapterTraceLogger.Warning( + "TypeCache: Exception occurred while checking whether type {0} is a test class or not. {1}", + t.FullName, + ex); + } - Type[] types = AssemblyEnumerator.GetTypes(assembly); + continue; + } - foreach (Type t in types) + // Enumerate through all methods and identify the Assembly Init and cleanup methods. + foreach (MethodInfo methodInfo in PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethods(t)) + { + if (@this.IsAssemblyOrClassInitializeMethod(methodInfo)) { - try - { - // Only examine classes which are TestClass or derives from TestClass attribute - if (!@this._reflectionHelper.IsAttributeDefined(t)) - { - continue; - } - } - catch (Exception ex) + assemblyInfo.AssemblyInitializeMethod = methodInfo; + assemblyInfo.AssemblyInitializeMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyInitialize); + } + else if (@this.IsAssemblyOrClassCleanupMethod(methodInfo)) + { + assemblyInfo.AssemblyCleanupMethod = methodInfo; + assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup); + } + + bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined(methodInfo); + bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined(methodInfo); + + if (isGlobalTestInitialize || isGlobalTestCleanup) + { + // Only try to validate the method if it already has the needed attribute. + // This avoids potential type load exceptions when the return type cannot be resolved. + // NOTE: Users tend to load assemblies in AssemblyInitialize after finishing the discovery. + // We want to avoid loading types early as much as we can. + bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } && + methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) && + methodInfo.IsValidReturnType(); + + if (isValid && isGlobalTestInitialize) { - // If we fail to discover type from an assembly, then do not abort. Pick the next type. - if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsWarningEnabled) - { - PlatformServiceProvider.Instance.AdapterTraceLogger.Warning( - "TypeCache: Exception occurred while checking whether type {0} is a test class or not. {1}", - t.FullName, - ex); - } - - continue; + assemblyInfo.GlobalTestInitializations.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestInitialize))); } - // Enumerate through all methods and identify the Assembly Init and cleanup methods. - foreach (MethodInfo methodInfo in PlatformServiceProvider.Instance.ReflectionOperations.GetDeclaredMethods(t)) + if (isValid && isGlobalTestCleanup) { - if (@this.IsAssemblyOrClassInitializeMethod(methodInfo)) - { - assemblyInfo.AssemblyInitializeMethod = methodInfo; - assemblyInfo.AssemblyInitializeMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyInitialize); - } - else if (@this.IsAssemblyOrClassCleanupMethod(methodInfo)) - { - assemblyInfo.AssemblyCleanupMethod = methodInfo; - assemblyInfo.AssemblyCleanupMethodTimeoutMilliseconds = @this.TryGetTimeoutInfo(methodInfo, FixtureKind.AssemblyCleanup); - } - - bool isGlobalTestInitialize = @this._reflectionHelper.IsAttributeDefined(methodInfo); - bool isGlobalTestCleanup = @this._reflectionHelper.IsAttributeDefined(methodInfo); - - if (isGlobalTestInitialize || isGlobalTestCleanup) - { - // Only try to validate the method if it already has the needed attribute. - // This avoids potential type load exceptions when the return type cannot be resolved. - // NOTE: Users tend to load assemblies in AssemblyInitialize after finishing the discovery. - // We want to avoid loading types early as much as we can. - bool isValid = methodInfo is { IsSpecialName: false, IsPublic: true, IsStatic: true, IsGenericMethod: false, DeclaringType.IsGenericType: false, DeclaringType.IsPublic: true } && - methodInfo.GetParameters() is { } parameters && parameters.Length == 1 && parameters[0].ParameterType == typeof(TestContext) && - methodInfo.IsValidReturnType(); - - if (isValid && isGlobalTestInitialize) - { - assemblyInfo.GlobalTestInitializations.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestInitialize))); - } - - if (isValid && isGlobalTestCleanup) - { - assemblyInfo.GlobalTestCleanups.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestCleanup))); - } - } + assemblyInfo.GlobalTestCleanups.Add((methodInfo, @this.TryGetTimeoutInfo(methodInfo, FixtureKind.TestCleanup))); } } + } + } - return assemblyInfo; - }, this); + return assemblyInfo; + } /// /// Verify if a given method is an Assembly or Class Initialize method. @@ -650,10 +679,14 @@ private DiscoveryTestMethodInfo ResolveTestMethodInfoForDiscovery(TestMethod tes /// The . private MethodInfo GetMethodInfoForTestMethod(TestMethod testMethod, TestClassInfo testClassInfo) { - bool discoverInternals = _discoverInternalsCache.GetOrAdd( - testMethod.AssemblyName, - static (_, testClassInfo) => testClassInfo.Parent.Assembly.GetCustomAttribute() != null, - testClassInfo); + // TODO: The cache key could be TestAssemblyInfo or Assembly which would simplify this to not need to capture. + // TODO: We might not even need a dictionary cache at all, just let it be part of TestAssemblyInfo directly. + if (!_discoverInternalsCache.TryGetValue(testMethod.AssemblyName, out bool discoverInternals)) + { + discoverInternals = _discoverInternalsCache.GetOrAdd( + testMethod.AssemblyName, + _ => testClassInfo.Parent.Assembly.GetCustomAttribute() is not null); + } MethodInfo? testMethodInfo = testMethod.HasManagedMethodAndTypeProperties ? GetMethodInfoUsingManagedNameHelper(testMethod, testClassInfo, discoverInternals) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs index 13c45e5665..4fe41e6f38 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Execution/UnitTestRunner.cs @@ -101,8 +101,15 @@ internal TestResult[] RunSingleTest(UnitTestElement unitTestElement, IDictionary /// The . internal async Task RunSingleTestAsync(UnitTestElement unitTestElement, IDictionary testContextProperties, IMessageLogger messageLogger) { - Ensure.NotNull(unitTestElement); - Ensure.NotNull(testContextProperties); + if (unitTestElement is null) + { + throw new ArgumentNullException(nameof(unitTestElement)); + } + + if (testContextProperties is null) + { + throw new ArgumentNullException(nameof(testContextProperties)); + } TestMethod testMethod = unitTestElement.TestMethod; ITestContext? testContextForTestExecution = null; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs index 933ab3a2f3..99ebd885cc 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Extensions/MethodInfoExtensions.cs @@ -199,7 +199,7 @@ private static bool IsValueTask(this MethodInfo method) private static void InferGenerics(Type parameterType, Type argumentType, List<(Type ParameterType, Type Substitution)> result) { - if (parameterType.IsGenericMethodParameter()) + if (parameterType.IsGenericParameter && parameterType.DeclaringMethod is not null) { // We found a generic parameter. The argument type should be the substitution for it. result.Add((parameterType, argumentType)); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs index a58410df70..e85da3fae8 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs @@ -176,7 +176,9 @@ internal static class FixtureMethodRunner } } +#if NETCOREAPP [SupportedOSPlatform("windows")] +#endif private static TestFailedException? RunWithTimeoutAndCancellationWithSTAThread( Func action, ExecutionContext? executionContext, CancellationTokenSource cancellationTokenSource, int timeout, MethodInfo methodInfo, string methodCanceledMessageFormat, string methodTimedOutMessageFormat) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs index 78d440a885..49de9849b7 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/ReflectHelper.cs @@ -31,7 +31,10 @@ internal class ReflectHelper : MarshalByRefObject public virtual /* for testing */ bool IsAttributeDefined(MemberInfo memberInfo) where TAttribute : Attribute { - Ensure.NotNull(memberInfo); + if (memberInfo is null) + { + throw new ArgumentNullException(nameof(memberInfo)); + } // Get all attributes on the member. Attribute[] attributes = GetCustomAttributesCached(memberInfo); @@ -130,11 +133,7 @@ internal class ReflectHelper : MarshalByRefObject /// The return type to match. /// True if there is a match. internal static bool MatchReturnType(MethodInfo method, Type returnType) - { - Ensure.NotNull(method); - Ensure.NotNull(returnType); - return method.ReturnType.Equals(returnType); - } + => method.ReturnType.Equals(returnType); /// /// Get categories applied to the test method. diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/RunSettingsUtilities.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/RunSettingsUtilities.cs index e4a457b0cc..a8d30dc0de 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/RunSettingsUtilities.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/RunSettingsUtilities.cs @@ -24,7 +24,7 @@ internal static class RunSettingsUtilities /// The runsettings xml. /// The test run parameters. /// If there is no test run parameters section defined in the settingsxml a blank dictionary is returned. - internal static Dictionary? GetTestRunParameters([StringSyntax(StringSyntaxAttribute.Xml, nameof(settingsXml))] string? settingsXml) + internal static Dictionary? GetTestRunParameters(string? settingsXml) => GetNodeValue(settingsXml, Constants.TestRunParametersName, TestRunParameters.FromXml); /// @@ -47,7 +47,7 @@ internal static void ThrowOnHasAttributes(XmlReader reader) } private static T? GetNodeValue( - [StringSyntax(StringSyntaxAttribute.Xml, nameof(settingsXml))] string? settingsXml, + string? settingsXml, string nodeName, Func nodeParser) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/TestDataSourceHelpers.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/TestDataSourceHelpers.cs index 4f3e0b614a..e48a8bff04 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/TestDataSourceHelpers.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/TestDataSourceHelpers.cs @@ -53,7 +53,7 @@ public static bool TryHandleITestDataRow( public static bool TryHandleTupleDataSource(object? data, ParameterInfo[] testMethodParameters, out object?[] array) { if (testMethodParameters.Length == 1 && - data?.GetType().IsAssignableTo(testMethodParameters[0].ParameterType) == true) + testMethodParameters[0].ParameterType?.IsAssignableFrom(data?.GetType()) == true) { array = []; return false; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs index 85e13570f0..8d2498fffc 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs @@ -23,5 +23,9 @@ internal interface ITestSourceHost : IDisposable /// /// An instance of the type created in the host. /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - object? CreateInstanceForType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, object?[]? args); + object? CreateInstanceForType( +#if NETCOREAPP + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + Type type, object?[]? args); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj index 3393d8818c..c957aaca1e 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj +++ b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj @@ -35,10 +35,13 @@ - + + + + diff --git a/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.cs b/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.cs index 1d8692e804..9a318607f9 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/MSTestSettings.cs @@ -298,7 +298,7 @@ internal static void PopulateSettings(IDiscoveryContext? context, IMessageLogger /// The logger for messages. /// The settings if found. Null otherwise. internal static MSTestSettings? GetSettings( - [StringSyntax(StringSyntaxAttribute.Xml, nameof(runSettingsXml))] string? runSettingsXml, + string? runSettingsXml, string settingName, IMessageLogger? logger) { if (StringEx.IsNullOrWhiteSpace(runSettingsXml)) @@ -346,7 +346,10 @@ internal static void Reset() /// An instance of the class. private static MSTestSettings ToSettings(XmlReader reader, IMessageLogger? logger) { - Ensure.NotNull(reader); + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } // Expected format of the xml is: - // @@ -673,7 +676,7 @@ private static void SetParallelSettings(XmlReader reader, MSTestSettings setting CultureInfo.CurrentCulture, Resource.InvalidParallelScopeValue, value, - string.Join(", ", Enum.GetNames()))); + string.Join(", ", Enum.GetNames(typeof(ExecutionScope))))); break; } @@ -700,10 +703,14 @@ private static void SetParallelSettings(XmlReader reader, MSTestSettings setting private static bool TryParseEnum(string value, out T result) where T : struct, Enum => Enum.TryParse(value, true, out result) +#if NETCOREAPP && Enum.IsDefined(result); +#else + && Enum.IsDefined(typeof(T), result); +#endif private static void SetGlobalSettings( - [StringSyntax(StringSyntaxAttribute.Xml, nameof(runsettingsXml))] string runsettingsXml, + string runsettingsXml, MSTestSettings settings, IMessageLogger? logger) { XElement? runConfigElement = XDocument.Parse(runsettingsXml).Element("RunSettings")?.Element("RunConfiguration"); @@ -862,7 +869,7 @@ internal static void SetSettingsFromConfig(IConfiguration configuration, IMessag CultureInfo.CurrentCulture, Resource.InvalidParallelScopeValue, value, - string.Join(", ", Enum.GetNames()))); + string.Join(", ", Enum.GetNames(typeof(ExecutionScope))))); } settings.ParallelizationScope = scope; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs index 7f2390c8e4..ede437c595 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/ObjectModel/UnitTestElement.cs @@ -27,7 +27,10 @@ internal sealed class UnitTestElement /// Thrown when method is null. public UnitTestElement(TestMethod testMethod) { - Ensure.NotNull(testMethod); + if (testMethod is null) + { + throw new ArgumentNullException(nameof(testMethod)); + } DebugEx.Assert(testMethod.FullClassName != null, "Full className cannot be empty"); TestMethod = testMethod; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs b/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs index a064111737..b94619b662 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/RunConfigurationSettings.cs @@ -29,8 +29,7 @@ internal sealed class RunConfigurationSettings /// Gets the configuration settings from the xml. /// /// The xml with the settings passed from the test platform. - public static RunConfigurationSettings GetSettings( - [StringSyntax(StringSyntaxAttribute.Xml, nameof(runSettingsXml))] string? runSettingsXml) + public static RunConfigurationSettings GetSettings(string? runSettingsXml) { if (StringEx.IsNullOrEmpty(runSettingsXml)) { @@ -68,7 +67,10 @@ public static RunConfigurationSettings GetSettings( /// An instance of the class. private static RunConfigurationSettings ToSettings(XmlReader reader) { - Ensure.NotNull(reader); + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } // Expected format of the xml is: - // diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs index a2724fc3af..d844431a45 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/MSTestAdapterSettings.cs @@ -58,7 +58,10 @@ public MSTestAdapterSettings() /// An instance of the class. public static MSTestAdapterSettings ToSettings(XmlReader reader) { - Ensure.NotNull(reader); + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } // Expected format of the xml is: - // @@ -345,7 +348,10 @@ public List GetDirectoryListWithRecursiveProperty(string private void ReadAssemblyResolutionPath(XmlReader reader) { - Ensure.NotNull(reader); + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } // Expected format of the xml is: - // diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/SettingsProvider.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/SettingsProvider.cs index f7ccf31e0d..01556d847f 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/SettingsProvider.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/SettingsProvider.cs @@ -52,7 +52,11 @@ internal static void Load(IConfiguration configuration) public void Load(XmlReader reader) { #if !WINDOWS_UWP - Ensure.NotNull(reader); + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } + var settings = MSTestAdapterSettings.ToSettings(reader); if (!ReferenceEquals(settings, Settings)) { diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDataSource.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDataSource.cs index 2fea97032a..c1a15cfe16 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDataSource.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestDataSource.cs @@ -142,7 +142,11 @@ private static void GetConnectionProperties(DataSourceAttribute dataSourceAttrib providerNameInvariant = ConfigurationManager.ConnectionStrings[element.ConnectionString].ProviderName; connectionString = ConfigurationManager.ConnectionStrings[element.ConnectionString].ConnectionString; tableName = element.DataTableName; +#if NETCOREAPP dataAccessMethod = Enum.Parse(element.DataAccessMethod); +#else + dataAccessMethod = (DataAccessMethod)Enum.Parse(typeof(DataAccessMethod), element.DataAccessMethod); +#endif } #endif } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs index 4748c91562..585a946698 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs @@ -131,7 +131,7 @@ public void SetupHost() if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) { - PlatformServiceProvider.Instance.AdapterTraceLogger.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(',', resolutionPaths)); + PlatformServiceProvider.Instance.AdapterTraceLogger.Info("DesktopTestSourceHost.SetupHost(): Creating assembly resolver with resolution paths {0}.", string.Join(",", resolutionPaths)); } // NOTE: These 2 lines are super important, see https://github.com/microsoft/testfx/issues/2922 @@ -199,7 +199,11 @@ public void SetupHost() /// /// An instance of the type created in the host. /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - public object? CreateInstanceForType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, object?[]? args) => + public object? CreateInstanceForType( +#if NETCOREAPP + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] +#endif + Type type, object?[]? args) => #if NETFRAMEWORK // Honor DisableAppDomain setting if it is present in runsettings _isAppDomainCreationDisabled diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs index 439aa5b379..028046eb4a 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs @@ -45,7 +45,9 @@ private static bool ExecuteWithThreadPool(Action action, int timeout, Cancellati } #endif +#if NETCOREAPP [SupportedOSPlatform("windows")] +#endif private static bool ExecuteWithCustomThread(Action action, int timeout, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs index 432db7470e..294c2366cf 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentItemUtility.cs @@ -257,9 +257,9 @@ private static List GetDeploymentItems(IEnumerable result = []; - foreach ((string? key, string? value) in deploymentItemsData) + foreach (KeyValuePair kvp in deploymentItemsData) { - AddDeploymentItem(result, new DeploymentItem(key, value)); + AddDeploymentItem(result, new DeploymentItem(kvp.Key, kvp.Value)); } return result; diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtility.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtility.cs index 00aaac90be..eee32a30f2 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtility.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtility.cs @@ -57,6 +57,32 @@ public override void AddDeploymentItemsBasedOnMsTestSetting(string testSourceHan #endif } +#if !NETCOREAPP + private static int ProcessId + { + get + { + int processId = field; + if (processId == 0) + { + field = processId = GetProcessId(); + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0, "processId is expected to be non-zero."); + } + + return processId; + + static int GetProcessId() + { + using var process = Process.GetCurrentProcess(); + return process.Id; + } + } + } +#else + private static int ProcessId => Environment.ProcessId; +#endif + /// /// Get root deployment directory. /// @@ -64,7 +90,8 @@ public override void AddDeploymentItemsBasedOnMsTestSetting(string testSourceHan /// Root deployment directory. public override string GetRootDeploymentDirectory(string baseDirectory) { - string dateTimeSuffix = $"{DateTime.Now.ToString("yyyyMMddTHHmmss", DateTimeFormatInfo.InvariantInfo)}_{Environment.ProcessId}"; + string dateTimeSuffix = $"{DateTime.Now.ToString("yyyyMMddTHHmmss", DateTimeFormatInfo.InvariantInfo)}_{ProcessId}"; + string directoryName = string.Format(CultureInfo.InvariantCulture, Resource.TestRunName, DeploymentFolderPrefix, #if NETFRAMEWORK Environment.UserName, diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs index 2f407266ac..c9c93d3fd6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/DeploymentUtilityBase.cs @@ -155,8 +155,6 @@ protected IEnumerable Deploy(IList deploymentItems, stri // Copy the deployment items. (As deployment item can correspond to directories as well, so each deployment item may map to n files) foreach (DeploymentItem deploymentItem in deploymentItems) { - Ensure.NotNull(deploymentItem); - // Validate the output directory. if (!IsOutputDirectoryValid(deploymentItem, deploymentDirectory, warnings)) { @@ -415,7 +413,6 @@ private static void LogWarnings(ITestExecutionRecorder testExecutionRecorder, IE private bool Deploy(string source, IRunContext? runContext, ITestExecutionRecorder testExecutionRecorder, IList deploymentItems, TestRunDirectories runDirectories) { - Ensure.NotNull(runDirectories); if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled) { PlatformServiceProvider.Instance.AdapterTraceLogger.Info("MSTestExecutor: Found that deployment items for source {0} are: ", source); diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/XmlUtilities.cs b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/XmlUtilities.cs index 518180b254..6f583874ed 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Utilities/XmlUtilities.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Utilities/XmlUtilities.cs @@ -100,7 +100,10 @@ private static void AddAssemblyBindingRedirect( string fromVersion, string toVersion) { - Ensure.NotNull(assemblyName); + if (assemblyName is null) + { + throw new ArgumentNullException(nameof(assemblyName)); + } // Convert the public key token into a string. StringBuilder? publicKeyTokenString = null; diff --git a/src/Polyfills/Ensure.cs b/src/Polyfills/Ensure.cs index d1c4250222..22acf3c68b 100644 --- a/src/Polyfills/Ensure.cs +++ b/src/Polyfills/Ensure.cs @@ -36,4 +36,16 @@ public static void NotEmpty(IEnumerable value, [CallerArgumentExpression(n throw new ArgumentException(ArgumentCannotBeEmptyMsg, name); } } + + public static void NotNullOrWhiteSpace([NotNull] string value, [CallerArgumentExpression(nameof(value))] string name = "") + { + if (value is null) + { + throw new ArgumentNullException(name); + } + else if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Argument cannot be whitespace.", name); + } + } } diff --git a/src/Polyfills/ModuleInitializerAttribute.cs b/src/Polyfills/ModuleInitializerAttribute.cs index a71d020007..6b83b52c4c 100644 --- a/src/Polyfills/ModuleInitializerAttribute.cs +++ b/src/Polyfills/ModuleInitializerAttribute.cs @@ -1,8 +1,9 @@ // -#if !NETCOREAPP #pragma warning disable +#if !NETCOREAPP + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/src/Polyfills/NullableAttribtues.cs b/src/Polyfills/NullableAttribtues.cs index 430eb35275..0ae5cbd1cb 100644 --- a/src/Polyfills/NullableAttribtues.cs +++ b/src/Polyfills/NullableAttribtues.cs @@ -1,5 +1,6 @@ // +#pragma warning disable #nullable enable // This was copied from https://github.com/dotnet/runtime/blob/39b9607807f29e48cae4652cd74735182b31182e/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs @@ -7,26 +8,33 @@ #if !NETCOREAPP +using Microsoft.CodeAnalysis; + namespace System.Diagnostics.CodeAnalysis; /// Specifies that null is allowed as an input even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +[Embedded] internal sealed class AllowNullAttribute : Attribute { } /// Specifies that null is disallowed as an input even if the corresponding type allows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +[Embedded] internal sealed class DisallowNullAttribute : Attribute { } /// Specifies that an output may be null even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +[Embedded] internal sealed class MaybeNullAttribute : Attribute { } /// Specifies that an output will not be null even if the corresponding type allows it. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +[Embedded] internal sealed class NotNullAttribute : Attribute { } /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +[Embedded] internal sealed class MaybeNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition. @@ -41,6 +49,7 @@ internal sealed class MaybeNullWhenAttribute : Attribute /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +[Embedded] internal sealed class NotNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition. @@ -55,6 +64,7 @@ internal sealed class NotNullWhenAttribute : Attribute /// Specifies that the output will be non-null if the named parameter is non-null. [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +[Embedded] internal sealed class NotNullIfNotNullAttribute : Attribute { /// Initializes the attribute with the associated parameter name. @@ -69,10 +79,12 @@ internal sealed class NotNullIfNotNullAttribute : Attribute /// Applied to a method that will never return under any circumstance. [AttributeUsage(AttributeTargets.Method, Inherited = false)] +[Embedded] internal sealed class DoesNotReturnAttribute : Attribute { } /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +[Embedded] internal sealed class DoesNotReturnIfAttribute : Attribute { /// Initializes the attribute with the specified parameter value. @@ -86,12 +98,9 @@ internal sealed class DoesNotReturnIfAttribute : Attribute public bool ParameterValue { get; } } -#endif - -#if !NETCOREAPP - /// Specifies that the method or property will ensure that the listed field and property members have not-null values. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +[Embedded] internal sealed class MemberNotNullAttribute : Attribute { /// Initializes the attribute with a field or property member. @@ -112,6 +121,7 @@ internal sealed class MemberNotNullAttribute : Attribute /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +[Embedded] internal sealed class MemberNotNullWhenAttribute : Attribute { /// Initializes the attribute with the specified return value condition and a field or property member. @@ -146,5 +156,16 @@ public MemberNotNullWhenAttribute(bool returnValue, params string[] members) /// Gets field or property member names. public string[] Members { get; } } - +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.AllowNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DisallowNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MaybeNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullWhenAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MemberNotNullAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute))] #endif From 684747917bd90f7ddd3939e2a5040b2d55a614e7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 11:16:56 +0200 Subject: [PATCH 04/24] Avoid Polyfill in analyzers --- .../FlowTestContextCancellationTokenFixer.cs | 6 ++- .../MSTest.Analyzers.CodeFixes.csproj | 5 +- .../DataRowShouldBeValidAnalyzer.cs | 4 +- .../DoNotUseShadowingAnalyzer.cs | 7 ++- ...lowTestContextCancellationTokenAnalyzer.cs | 2 +- .../MSTest.Analyzers/MSTest.Analyzers.csproj | 4 +- .../RoslynAnalyzerHelpers/ArrayBuilder.cs | 4 +- .../TestContextShouldBeValidAnalyzer.cs | 2 +- src/Polyfills/UnreachableException.cs | 53 +++++++++++++++++++ 9 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/Polyfills/UnreachableException.cs diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs b/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs index b598080ce4..d0fb18a707 100644 --- a/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs +++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs @@ -115,7 +115,11 @@ internal static Document ApplyFix( } else { - Ensure.NotNull(testContextMemberName); + if (testContextMemberName is null) + { + throw new ArgumentNullException(testContextMemberName); + } + AddCancellationTokenArgument(editor, invocationExpression, testContextMemberName, cancellationTokenParameterName); } diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/MSTest.Analyzers.CodeFixes.csproj b/src/Analyzers/MSTest.Analyzers.CodeFixes/MSTest.Analyzers.CodeFixes.csproj index ad9bdcf545..681f7842d4 100644 --- a/src/Analyzers/MSTest.Analyzers.CodeFixes/MSTest.Analyzers.CodeFixes.csproj +++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/MSTest.Analyzers.CodeFixes.csproj @@ -8,7 +8,10 @@ - + + + + diff --git a/src/Analyzers/MSTest.Analyzers/DataRowShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DataRowShouldBeValidAnalyzer.cs index a76213bc37..4a0042aeab 100644 --- a/src/Analyzers/MSTest.Analyzers/DataRowShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DataRowShouldBeValidAnalyzer.cs @@ -331,11 +331,11 @@ private static void AnalyzeGenericMethod( if (parameterTypesSubstitutions.TryGetValue(parameterType, out (ITypeSymbol Symbol, Type SystemType) existingType)) { - if (argumentType.IsAssignableTo(existingType.SystemType)) + if (existingType.SystemType.IsAssignableFrom(argumentType)) { continue; } - else if (existingType.SystemType.IsAssignableTo(argumentType)) + else if (argumentType.IsAssignableFrom(existingType.SystemType)) { parameterTypesSubstitutions[parameterType] = (parameterType, argumentType); } diff --git a/src/Analyzers/MSTest.Analyzers/DoNotUseShadowingAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DoNotUseShadowingAnalyzer.cs index 99f2fca090..20eea23415 100644 --- a/src/Analyzers/MSTest.Analyzers/DoNotUseShadowingAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/DoNotUseShadowingAnalyzer.cs @@ -64,7 +64,12 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo Dictionary> membersByName = GetBaseMembers(namedTypeSymbol); foreach (ISymbol member in namedTypeSymbol.GetMembers()) { - foreach (ISymbol baseMember in membersByName.GetValueOrDefault(member.Name, [])) + if (!membersByName.TryGetValue(member.Name, out List? members)) + { + continue; + } + + foreach (ISymbol baseMember in members) { // Check if the member is shadowing a base class member if (IsMemberShadowing(member, baseMember)) diff --git a/src/Analyzers/MSTest.Analyzers/FlowTestContextCancellationTokenAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/FlowTestContextCancellationTokenAnalyzer.cs index 582ff7cceb..b36519882c 100644 --- a/src/Analyzers/MSTest.Analyzers/FlowTestContextCancellationTokenAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/FlowTestContextCancellationTokenAnalyzer.cs @@ -230,7 +230,7 @@ private static bool HasOrCouldHaveTestContextInScope( testContextMember = (testContextMember as IFieldSymbol)?.AssociatedSymbol ?? testContextMember; // Workaround https://github.com/dotnet/roslyn/issues/70208 // https://github.com/dotnet/roslyn/blob/f25ae8e02a91169f45060951a168b233ad588ed3/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs#L47 - testContextMemberNameInScope = testContextMember.Name.StartsWith('<') && testContextMember.Name.EndsWith(">P", StringComparison.Ordinal) + testContextMemberNameInScope = testContextMember.Name.StartsWith("<", StringComparison.Ordinal) && testContextMember.Name.EndsWith(">P", StringComparison.Ordinal) ? testContextMember.Name.Substring(1, testContextMember.Name.Length - 3) : testContextMember.Name; testContextState = TestContextState.InScope; diff --git a/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj b/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj index 77efce2f98..d8d91fad54 100644 --- a/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj +++ b/src/Analyzers/MSTest.Analyzers/MSTest.Analyzers.csproj @@ -20,7 +20,9 @@ - + + + diff --git a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs index 8f4571e6a3..71bf118676 100644 --- a/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs +++ b/src/Analyzers/MSTest.Analyzers/RoslynAnalyzerHelpers/ArrayBuilder.cs @@ -324,9 +324,9 @@ internal Dictionary> ToDictionary(Func keySelector var dictionary = new Dictionary>(accumulator.Count, comparer); // freeze - foreach ((K? key, ArrayBuilder? value) in accumulator) + foreach (KeyValuePair> kvp in accumulator) { - dictionary.Add(key, value.ToImmutableAndFree()); + dictionary.Add(kvp.Key, kvp.Value.ToImmutableAndFree()); } return dictionary; diff --git a/src/Analyzers/MSTest.Analyzers/TestContextShouldBeValidAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/TestContextShouldBeValidAnalyzer.cs index 6838d3bbc7..361668c8f5 100644 --- a/src/Analyzers/MSTest.Analyzers/TestContextShouldBeValidAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/TestContextShouldBeValidAnalyzer.cs @@ -232,7 +232,7 @@ public override void Initialize(AnalysisContext context) if (fieldSymbol.AssociatedSymbol is not null || // Workaround https://github.com/dotnet/roslyn/issues/70208 // https://github.com/dotnet/roslyn/blob/05e49aa98995349ffa26a19020333293ffe99670/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNameKind.cs#L47 - (fieldSymbol.Name.StartsWith('<') && fieldSymbol.Name.EndsWith(">P", StringComparison.Ordinal)) || + (fieldSymbol.Name.StartsWith("<", StringComparison.Ordinal) && fieldSymbol.Name.EndsWith(">P", StringComparison.Ordinal)) || !SymbolEqualityComparer.Default.Equals(fieldSymbol.Type, testContextSymbol)) { continue; diff --git a/src/Polyfills/UnreachableException.cs b/src/Polyfills/UnreachableException.cs new file mode 100644 index 0000000000..4fdac3981b --- /dev/null +++ b/src/Polyfills/UnreachableException.cs @@ -0,0 +1,53 @@ +// + +#nullable enable + +#if !NETCOREAPP + +namespace System.Diagnostics; + +using System; +using System.Diagnostics.CodeAnalysis; + +using Microsoft.CodeAnalysis; + +/// +/// Exception thrown when the program executes an instruction that was thought to be unreachable. +/// +/// +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[Embedded] +internal sealed class UnreachableException : Exception +{ + /// + /// Initializes a new instance of the class with the default error message. + /// + public UnreachableException() + : base("The program executed an instruction that was thought to be unreachable.") + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message. + /// + public UnreachableException(string? message) + : base(message) + { + } + + /// + /// Initializes a new instance of the + /// class with a specified error message and a reference to the inner exception that is the cause of + /// this exception. + /// + public UnreachableException(string? message, Exception? innerException) + : base(message, innerException) + { + } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.UnreachableException))] +#endif From c95846fe64f386a1a4c339947b43d0d414d4ed25 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 12:10:36 +0200 Subject: [PATCH 05/24] MTP extensions partial progress --- .../BannedSymbols.txt | 3 +- ...esting.Extensions.AzureDevOpsReport.csproj | 5 +- ...oft.Testing.Extensions.AzureFoundry.csproj | 5 +- .../BannedSymbols.txt | 3 +- ...rosoft.Testing.Extensions.CrashDump.csproj | 2 +- .../BannedSymbols.txt | 3 +- .../HangDumpActivityIndicator.cs | 2 + .../HangDumpExtensions.cs | 4 ++ .../HangDumpProcessLifetimeHandler.cs | 4 ++ .../Helpers/IProcessExtensions.cs | 14 ++++ ...crosoft.Testing.Extensions.HangDump.csproj | 2 +- .../TestingPlatformBuilderHook.cs | 2 + .../WindowsMiniDumpWriteDump.cs | 3 +- .../BannedSymbols.txt | 3 +- .../HotReloadHandler.cs | 4 ++ ...rosoft.Testing.Extensions.HotReload.csproj | 2 +- .../MSBuildConsumer.cs | 2 + .../MSBuildExtensions.cs | 4 ++ .../MSBuildOrchestratorLifecycleCallbacks.cs | 2 + ...SBuildTestApplicationLifecycleCallbacks.cs | 2 + ...icrosoft.Testing.Extensions.MSBuild.csproj | 2 +- .../TestingPlatformBuilderHook.cs | 2 + ...ft.Testing.Extensions.OpenTelemetry.csproj | 3 +- .../BannedSymbols.txt | 3 +- .../Microsoft.Testing.Extensions.Retry.csproj | 2 +- .../RetryDataConsumer.cs | 2 + .../RetryExecutionFilterFactory.cs | 2 + .../RetryExtensions.cs | 4 ++ .../RetryFailedTestsPipeServer.cs | 2 + .../RetryLifecycleCallbacks.cs | 2 + .../RetryOrchestrator.cs | 2 + .../TestingPlatformBuilderHook.cs | 2 + .../AppInsightsProvider.cs | 8 +-- ...rosoft.Testing.Extensions.Telemetry.csproj | 3 +- .../BannedSymbols.txt | 3 +- ...g.Extensions.TrxReport.Abstractions.csproj | 6 +- .../TrxReportProperties.cs | 22 +++++- .../BannedSymbols.txt | 3 +- ...rosoft.Testing.Extensions.TrxReport.csproj | 6 +- .../TrxCompareTool.cs | 5 ++ .../TrxDataConsumer.cs | 20 +++++- .../TrxModeHelpers.cs | 9 ++- .../TrxProcessLifetimeHandler.cs | 36 +++++++--- .../TrxReportEngine.cs | 29 +++++++- .../TrxReportExtensions.cs | 4 ++ .../TrxTestApplicationLifecycleCallbacks.cs | 2 + ...oft.Testing.Extensions.VSTestBridge.csproj | 9 ++- .../VSTestBridgedTestFrameworkBase.cs | 3 +- .../Microsoft.Testing.Platform.AI.csproj | 5 +- .../Microsoft.Testing.Platform.MSBuild.csproj | 5 +- .../Tasks/ConfigurationFileTask.cs | 7 +- .../Tasks/DotnetMuxerLocator.cs | 2 + .../Tasks/InvokeTestingPlatformTask.cs | 17 +++++ .../BannedSymbols.txt | 4 +- .../Helpers/TimeSpanParser.cs | 8 +-- .../IPC/NamedPipeClient.cs | 8 ++- .../IPC/NamedPipeServer.cs | 8 ++- src/Polyfills/Ensure.cs | 2 + src/Polyfills/ExperimentalAttribute.cs | 55 +++++++++++++++ src/Polyfills/ProcessExtensions.cs | 70 +++++++++++++++++++ src/Polyfills/UnreachableException.cs | 2 + .../TrxTests.cs | 39 +++++++++-- 62 files changed, 410 insertions(+), 89 deletions(-) create mode 100644 src/Polyfills/ExperimentalAttribute.cs create mode 100644 src/Polyfills/ProcessExtensions.cs diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/BannedSymbols.txt index 09d715c49c..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'ArgumentGuard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/Microsoft.Testing.Extensions.AzureDevOpsReport.csproj b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/Microsoft.Testing.Extensions.AzureDevOpsReport.csproj index 9c467429e1..c3c7cf4e4c 100644 --- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/Microsoft.Testing.Extensions.AzureDevOpsReport.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/Microsoft.Testing.Extensions.AzureDevOpsReport.csproj @@ -42,10 +42,7 @@ This package extends Microsoft Testing Platform to provide a Azure DevOps report - - - - + diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureFoundry/Microsoft.Testing.Extensions.AzureFoundry.csproj b/src/Platform/Microsoft.Testing.Extensions.AzureFoundry/Microsoft.Testing.Extensions.AzureFoundry.csproj index 93bcbdbb9f..57beb22aea 100644 --- a/src/Platform/Microsoft.Testing.Extensions.AzureFoundry/Microsoft.Testing.Extensions.AzureFoundry.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.AzureFoundry/Microsoft.Testing.Extensions.AzureFoundry.csproj @@ -31,7 +31,10 @@ - + + + + diff --git a/src/Platform/Microsoft.Testing.Extensions.CrashDump/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.CrashDump/BannedSymbols.txt index 09d715c49c..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.CrashDump/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.CrashDump/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'ArgumentGuard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.CrashDump/Microsoft.Testing.Extensions.CrashDump.csproj b/src/Platform/Microsoft.Testing.Extensions.CrashDump/Microsoft.Testing.Extensions.CrashDump.csproj index d69669e55b..8aeeb45416 100644 --- a/src/Platform/Microsoft.Testing.Extensions.CrashDump/Microsoft.Testing.Extensions.CrashDump.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.CrashDump/Microsoft.Testing.Extensions.CrashDump.csproj @@ -47,7 +47,7 @@ This package extends Microsoft Testing Platform to provide a crash dump function - + diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.HangDump/BannedSymbols.txt index 09d715c49c..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'ArgumentGuard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs index 172393a11c..463e21b581 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs @@ -16,7 +16,9 @@ namespace Microsoft.Testing.Extensions.Diagnostics; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class HangDumpActivityIndicator : IDataConsumer, ITestSessionLifetimeHandler, #if NETCOREAPP IAsyncDisposable, diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs index 2e76ca62cb..619c2e7d0f 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs @@ -18,11 +18,14 @@ public static class HangDumpExtensions /// Adds hang dump support to the test application. /// /// The test application builder. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif public static void AddHangDumpProvider(this ITestApplicationBuilder builder) { +#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException("Hang dump extension is not available on browser"); @@ -32,6 +35,7 @@ public static void AddHangDumpProvider(this ITestApplicationBuilder builder) { throw new PlatformNotSupportedException("Hang dump extension is not available on ios nor tvos"); } +#endif PipeNameDescription pipeNameDescription = NamedPipeServer.GetPipeName(Guid.NewGuid().ToString("N")); diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs index 6c2daf0283..ad4e82772b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs @@ -23,9 +23,11 @@ namespace Microsoft.Testing.Extensions.Diagnostics; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif internal sealed class HangDumpProcessLifetimeHandler : ITestHostProcessLifetimeHandler, IOutputDeviceDataProducer, IDataProducer, #if NETCOREAPP IAsyncDisposable, @@ -218,9 +220,11 @@ public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testH } } +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif private async Task TakeDumpOfTreeAsync(CancellationToken cancellationToken) { ApplicationStateGuard.Ensure(_testHostProcessInformation is not null); diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs index 9dc68a520e..ac6b33b053 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs @@ -7,6 +7,10 @@ using Microsoft.Testing.Platform.Logging; using Microsoft.Testing.Platform.OutputDevice; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions.Diagnostics.Helpers; /// @@ -16,9 +20,11 @@ internal static class IProcessExtensions { private const int InvalidProcessId = -1; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif public static async Task> GetProcessTreeAsync(this IProcess process, ILogger logger, OutputDeviceWriter outputDisplay, CancellationToken cancellationToken) { var childProcesses = Process.GetProcesses() @@ -67,7 +73,9 @@ internal static async Task GetParentPidAsync(Process process, ILogger logge await GetParentPidMacOsAsync(process, logger, outputDisplay, cancellationToken).ConfigureAwait(false) : throw new PlatformNotSupportedException(); +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal static int GetParentPidWindows(Process process) { IntPtr handle = process.Handle; @@ -81,7 +89,9 @@ internal static int GetParentPidWindows(Process process) /// Read the /proc file system for information about the parent. /// The process to get the parent process from. /// The process id. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal static int GetParentPidLinux(Process process) { int pid = process.Id; @@ -98,9 +108,11 @@ internal static int GetParentPidLinux(Process process) return ppid; } +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif internal static async Task GetParentPidMacOsAsync(Process process, ILogger logger, OutputDeviceWriter outputDisplay, CancellationToken cancellationToken) { var output = new StringBuilder(); @@ -154,9 +166,11 @@ private static void ResolveChildren(IProcess parent, ILogger logger, List - + diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/TestingPlatformBuilderHook.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/TestingPlatformBuilderHook.cs index 0cc7d036d7..bacb20aa07 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/TestingPlatformBuilderHook.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/TestingPlatformBuilderHook.cs @@ -15,9 +15,11 @@ public static class TestingPlatformBuilderHook /// /// The test application builder. /// The command line arguments. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] +#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddHangDumpProvider(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/WindowsMiniDumpWriteDump.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/WindowsMiniDumpWriteDump.cs index 12233d34ce..1963ed4715 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/WindowsMiniDumpWriteDump.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/WindowsMiniDumpWriteDump.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +#if !NETCOREAPP using Microsoft.Win32.SafeHandles; namespace Microsoft.Testing.Extensions.Diagnostics; internal static class MiniDumpWriteDump { - [UnsupportedOSPlatform("browser")] public static void CollectDumpUsingMiniDumpWriteDump(int pid, string outputFile, MiniDumpTypeOption type) { using var process = Process.GetProcessById(pid); @@ -112,3 +112,4 @@ internal enum MiniDumpTypeOption Mini, } } +#endif diff --git a/src/Platform/Microsoft.Testing.Extensions.HotReload/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.HotReload/BannedSymbols.txt index 6b0c437d6e..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HotReload/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.HotReload/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'Guard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs index 28426efe70..5ffd899503 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs @@ -43,6 +43,7 @@ public HotReloadHandler(IConsole console, IOutputDevice outputDevice, IOutputDev } } +#if NETCOREAPP [SupportedOSPlatformGuard("android")] [SupportedOSPlatformGuard("ios")] [SupportedOSPlatformGuard("tvos")] @@ -54,6 +55,9 @@ private static bool IsCancelKeyPressNotSupported() OperatingSystem.IsTvOS() || OperatingSystem.IsWasi() || OperatingSystem.IsBrowser(); +#else + private static bool IsCancelKeyPressNotSupported() => false; +#endif // Called automatically by the runtime through the MetadataUpdateHandlerAttribute public static void ClearCache(Type[]? _) diff --git a/src/Platform/Microsoft.Testing.Extensions.HotReload/Microsoft.Testing.Extensions.HotReload.csproj b/src/Platform/Microsoft.Testing.Extensions.HotReload/Microsoft.Testing.Extensions.HotReload.csproj index 5c4f85a4ff..1af9733bc9 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HotReload/Microsoft.Testing.Extensions.HotReload.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.HotReload/Microsoft.Testing.Extensions.HotReload.csproj @@ -29,7 +29,7 @@ This package extends Microsoft Testing Platform to provide Hot Reload support.]] - + diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs index ce04d52500..d3b8652033 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs @@ -12,7 +12,9 @@ namespace Microsoft.Testing.Extensions.MSBuild; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class MSBuildConsumer : IDataConsumer, ITestSessionLifetimeHandler { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs index 224a5dfce8..d2f94edbab 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs @@ -17,13 +17,17 @@ public static class MSBuildExtensions /// Adds MSBuild support to the test application builder. /// /// The test application builder. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif public static void AddMSBuild(this ITestApplicationBuilder builder) { +#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException("MSBuild extension is not supported in browser environments."); } +#endif builder.CommandLine.AddProvider(() => new MSBuildCommandLineProvider()); builder.TestHost.AddTestHostApplicationLifetime( diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs index e8b4b37ff2..ff0c8c5586 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs @@ -12,7 +12,9 @@ namespace Microsoft.Testing.Extensions.MSBuild; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class MSBuildOrchestratorLifetime : ITestHostOrchestratorApplicationLifetime { private readonly IConfiguration _configuration; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs index 4230348eeb..bfd3315025 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs @@ -12,7 +12,9 @@ namespace Microsoft.Testing.Extensions.MSBuild; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class MSBuildTestApplicationLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { private readonly IConfiguration _configuration; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/Microsoft.Testing.Extensions.MSBuild.csproj b/src/Platform/Microsoft.Testing.Extensions.MSBuild/Microsoft.Testing.Extensions.MSBuild.csproj index c5bf06bfbf..ffce9c36df 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/Microsoft.Testing.Extensions.MSBuild.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/Microsoft.Testing.Extensions.MSBuild.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs index a84a7ec7d4..6e69aee65e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs @@ -15,7 +15,9 @@ public static class TestingPlatformBuilderHook /// /// The test application builder. /// The command line arguments. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddMSBuild(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.OpenTelemetry/Microsoft.Testing.Extensions.OpenTelemetry.csproj b/src/Platform/Microsoft.Testing.Extensions.OpenTelemetry/Microsoft.Testing.Extensions.OpenTelemetry.csproj index 276c796200..1a5a8dfb48 100644 --- a/src/Platform/Microsoft.Testing.Extensions.OpenTelemetry/Microsoft.Testing.Extensions.OpenTelemetry.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.OpenTelemetry/Microsoft.Testing.Extensions.OpenTelemetry.csproj @@ -29,11 +29,10 @@ This package provides Open Telemetry for the platform.]]> - - + diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.Retry/BannedSymbols.txt index 7be39f73d1..2a7440f410 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'Guard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/Microsoft.Testing.Extensions.Retry.csproj b/src/Platform/Microsoft.Testing.Extensions.Retry/Microsoft.Testing.Extensions.Retry.csproj index 7c6b529f5b..8d50a5b6c7 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/Microsoft.Testing.Extensions.Retry.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/Microsoft.Testing.Extensions.Retry.csproj @@ -40,7 +40,7 @@ This package extends Microsoft Testing Platform to provide a retry policy system - + diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs index 1b2317c0bf..05c0cded56 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs @@ -14,7 +14,9 @@ namespace Microsoft.Testing.Extensions.Policy; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class RetryDataConsumer : IDataConsumer, ITestSessionLifetimeHandler, IAsyncInitializableExtension { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs index 5e75951d3f..c61aa27c1a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs @@ -10,7 +10,9 @@ namespace Microsoft.Testing.Extensions.Policy; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class RetryExecutionFilterFactory : ITestExecutionFilterFactory { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs index 404fce2aaf..33b7594261 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs @@ -18,13 +18,17 @@ public static class RetryExtensions /// Adds the retry provider to the test application. /// /// The test application builder. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif public static void AddRetryProvider(this ITestApplicationBuilder builder) { +#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException(ExtensionResources.RetryExtensionNotSupportedOnBrowserErrorMessage); } +#endif builder.CommandLine.AddProvider(() => new RetryCommandLineOptionsProvider()); diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs index e61d4cd29a..2e3b91910e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs @@ -11,7 +11,9 @@ namespace Microsoft.Testing.Extensions.Policy; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class RetryFailedTestsPipeServer : IDisposable { private readonly NamedPipeServer _singleConnectionNamedPipeServer; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs index 25141d818a..4cc762bead 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs @@ -14,7 +14,9 @@ namespace Microsoft.Testing.Extensions.Policy; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class RetryLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs index 5aeb86304a..344367b58a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs @@ -13,7 +13,9 @@ namespace Microsoft.Testing.Extensions.Policy; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class RetryOrchestrator : ITestHostExecutionOrchestrator, IOutputDeviceDataProducer { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs index 01b0410042..f03a506d8e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs @@ -15,7 +15,9 @@ public static class TestingPlatformBuilderHook /// /// The test application builder. /// The command line arguments. +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddRetryProvider(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.Telemetry/AppInsightsProvider.cs b/src/Platform/Microsoft.Testing.Extensions.Telemetry/AppInsightsProvider.cs index eafc424bfe..7d9cc606c1 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Telemetry/AppInsightsProvider.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Telemetry/AppInsightsProvider.cs @@ -192,14 +192,14 @@ private async Task IngestLoopAsync() { StringBuilder builder = new(); builder.AppendLine(CultureInfo.InvariantCulture, $"Send telemetry event: {eventName}"); - foreach ((string key, string value) in properties) + foreach (KeyValuePair kvp in properties) { - builder.AppendLine(CultureInfo.InvariantCulture, $" {key}: {value}"); + builder.AppendLine(CultureInfo.InvariantCulture, $" {kvp.Key}: {kvp.Value}"); } - foreach ((string key, double value) in metrics) + foreach (KeyValuePair kvp in metrics) { - builder.AppendLine(CultureInfo.InvariantCulture, $" {key}: {value.ToString("f", CultureInfo.InvariantCulture)}"); + builder.AppendLine(CultureInfo.InvariantCulture, $" {kvp.Key}: {kvp.Value.ToString("f", CultureInfo.InvariantCulture)}"); } await _logger.LogTraceAsync(builder.ToString()).ConfigureAwait(false); diff --git a/src/Platform/Microsoft.Testing.Extensions.Telemetry/Microsoft.Testing.Extensions.Telemetry.csproj b/src/Platform/Microsoft.Testing.Extensions.Telemetry/Microsoft.Testing.Extensions.Telemetry.csproj index 3d8e7ce51c..7aebf8abb5 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Telemetry/Microsoft.Testing.Extensions.Telemetry.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.Telemetry/Microsoft.Testing.Extensions.Telemetry.csproj @@ -48,7 +48,6 @@ This package provides telemetry for the platform.]]> - @@ -60,7 +59,7 @@ This package provides telemetry for the platform.]]> - + diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/BannedSymbols.txt index 09d715c49c..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'ArgumentGuard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj index 2ad2e8b49a..43beebafb8 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/Microsoft.Testing.Extensions.TrxReport.Abstractions.csproj @@ -16,11 +16,7 @@ - - - - - + diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/TrxReportProperties.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/TrxReportProperties.cs index 58d45e4283..285373a7da 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/TrxReportProperties.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/TrxReportProperties.cs @@ -198,7 +198,16 @@ public override string ToString() builder.Append(" { "); builder.Append($"{nameof(Messages)} = ["); - builder.AppendJoin(", ", Messages.Select(x => x.ToString())); + + for (int i = 0; i < Messages.Length; i++) + { + builder.Append(Messages[i].ToString()); + if (i < Messages.Length - 1) + { + builder.Append(", "); + } + } + builder.Append(']'); builder.Append(" }"); return builder.ToString(); @@ -229,7 +238,16 @@ public override string ToString() builder.Append(nameof(TrxCategoriesProperty)); builder.Append(" { "); builder.Append($"{nameof(Categories)} = ["); - builder.AppendJoin(", ", Categories); + + for (int i = 0; i < Categories.Length; i++) + { + builder.Append(Categories[i]); + if (i < Categories.Length - 1) + { + builder.Append(", "); + } + } + builder.Append(']'); builder.Append(" }"); return builder.ToString(); diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Extensions.TrxReport/BannedSymbols.txt index 09d715c49c..64ef236c50 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/BannedSymbols.txt @@ -1,5 +1,4 @@ -T:System.ArgumentNullException; Use 'ArgumentGuard' instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead M:System.Threading.Tasks.Task.Run(System.Action); Use 'ITask' instead M:System.Threading.Tasks.Task.WhenAll(System.Threading.Tasks.Task[]); Use 'ITask' instead diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/Microsoft.Testing.Extensions.TrxReport.csproj b/src/Platform/Microsoft.Testing.Extensions.TrxReport/Microsoft.Testing.Extensions.TrxReport.csproj index d9f92058ae..fd807bce97 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/Microsoft.Testing.Extensions.TrxReport.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/Microsoft.Testing.Extensions.TrxReport.csproj @@ -64,7 +64,7 @@ This package extends Microsoft Testing Platform to provide TRX test reports.]]> - + @@ -74,10 +74,6 @@ This package extends Microsoft Testing Platform to provide TRX test reports.]]> - - - - diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs index fce5f10989..5dbb643375 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxCompareTool.cs @@ -180,7 +180,12 @@ private static void AppendResultsAndIssues(string category, string filePath, private static async Task CollectEntriesAndErrorsAsync(string trxFile, XNamespace ns, List results, List issues) { using FileStream stream = File.OpenRead(trxFile); +#if NETCOREAPP XElement trxTestRun = await XElement.LoadAsync(stream, LoadOptions.None, CancellationToken.None).ConfigureAwait(false); +#else + var trxTestRun = XElement.Load(stream, LoadOptions.None); +#endif + int testResultIndex = 0; foreach (XElement testResult in trxTestRun.Elements(ns + "Results").Elements(ns + "UnitTestResult")) { diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs index d22516738a..80460a8577 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs @@ -191,9 +191,23 @@ public async Task OnTestSessionFinishingAsync(ITestSessionContext testSessionCon ApplicationStateGuard.Ensure(_testStartTime is not null); int exitCode = _testApplicationProcessExitCode.GetProcessExitCode(); - var trxReportGeneratorEngine = new TrxReportEngine(_fileSystem, _testApplicationModuleInfo, _environment, _commandLineOptionsService, _configuration, - _clock, _artifactsByExtension, - _testFramework, _testStartTime.Value, exitCode, cancellationToken); + var trxReportGeneratorEngine = new TrxReportEngine( + _fileSystem, + _testApplicationModuleInfo, + _environment, + _commandLineOptionsService, + _configuration, + _clock, + _artifactsByExtension, + _testFramework, + _testStartTime.Value, +#if NETCOREAPP + exitCode, + cancellationToken); +#else + exitCode); +#endif + (string reportFileName, string? warning) = await trxReportGeneratorEngine.GenerateReportAsync([.. _tests]).ConfigureAwait(false); if (warning is not null) { diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs index efcd1ba83f..5050f5258e 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs @@ -7,8 +7,13 @@ namespace Microsoft.Testing.Extensions.TrxReport; internal static class TrxModeHelpers { +#if NETCOREAPP [UnsupportedOSPlatformGuard("BROWSER")] +#endif public static bool ShouldUseOutOfProcessTrxGeneration(ICommandLineOptions commandLineOptions) - => commandLineOptions.IsOptionSet(CrashDumpCommandLineOptions.CrashDumpOptionName) && - !OperatingSystem.IsBrowser(); + => commandLineOptions.IsOptionSet(CrashDumpCommandLineOptions.CrashDumpOptionName) +#if NETCOREAPP + && !OperatingSystem.IsBrowser() +#endif + ; } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs index 21a57d9588..60ce0cf162 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs @@ -115,7 +115,9 @@ public Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) return Task.CompletedTask; } +#if NETCOREAPP [UnsupportedOSPlatform("BROWSER")] +#endif private void BeforeTestHostProcessStartCore(CancellationToken cancellationToken) => _waitConnectionTask = _task.Run( async () => @@ -177,13 +179,22 @@ public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testH // If _fileNameRequest is null, that means that the TestHost crashed before it wrote the TRX file. if (_fileNameRequest is null) { - var trxReportGeneratorEngine = new TrxReportEngine(_fileSystem, _testApplicationModuleInfo, _environment, _commandLineOptions, _configuration, + var trxReportGeneratorEngine = new TrxReportEngine( + _fileSystem, + _testApplicationModuleInfo, + _environment, + _commandLineOptions, + _configuration, _clock, artifacts, new TestAdapterInfo(_testAdapterInformationRequest!.TestAdapterId, _testAdapterInformationRequest.TestAdapterVersion), _startTime, +#if NETCOREAPP testHostProcessInformation.ExitCode, cancellationToken); +#else + testHostProcessInformation.ExitCode); +#endif (string fileName, string? warning) = await trxReportGeneratorEngine.GenerateReportAsync( [], @@ -213,13 +224,22 @@ await _messageBus.PublishAsync( // Add attachments to the trx. if (_fileArtifacts.Count > 0) { - var trxReportGeneratorEngine = new TrxReportEngine(_fileSystem, _testApplicationModuleInfo, _environment, _commandLineOptions, _configuration, - _clock, - artifacts, - new TestAdapterInfo(_testAdapterInformationRequest!.TestAdapterId, _testAdapterInformationRequest.TestAdapterVersion), - _startTime, - testHostProcessInformation.ExitCode, - cancellationToken); + var trxReportGeneratorEngine = new TrxReportEngine( + _fileSystem, + _testApplicationModuleInfo, + _environment, + _commandLineOptions, + _configuration, + _clock, + artifacts, + new TestAdapterInfo(_testAdapterInformationRequest!.TestAdapterId, _testAdapterInformationRequest.TestAdapterVersion), + _startTime, +#if NETCOREAPP + testHostProcessInformation.ExitCode, + cancellationToken); +#else + testHostProcessInformation.ExitCode); +#endif await trxReportGeneratorEngine.AddArtifactsAsync(trxFile, artifacts).ConfigureAwait(false); } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index d32cb388db..61ebb27157 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -83,11 +83,28 @@ internal sealed partial class TrxReportEngine private readonly Dictionary> _artifactsByExtension; private readonly ITestFramework _testFrameworkAdapter; private readonly DateTimeOffset _testStartTime; +#if NETCOREAPP private readonly CancellationToken _cancellationToken; +#endif private readonly int _exitCode; private readonly IFileSystem _fileSystem; - public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment, ICommandLineOptions commandLineOptionsService, IConfiguration configuration, IClock clock, Dictionary> artifactsByExtension, ITestFramework testFrameworkAdapter, DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken) + public TrxReportEngine( + IFileSystem fileSystem, + ITestApplicationModuleInfo testApplicationModuleInfo, + IEnvironment environment, + ICommandLineOptions commandLineOptionsService, + IConfiguration configuration, + IClock clock, + Dictionary> artifactsByExtension, + ITestFramework testFrameworkAdapter, + DateTimeOffset testStartTime, +#if NETCOREAPP + int exitCode, + CancellationToken cancellationToken) +#else + int exitCode) +#endif { _testApplicationModuleInfo = testApplicationModuleInfo; _environment = environment; @@ -97,7 +114,9 @@ public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testAp _artifactsByExtension = artifactsByExtension; _testFrameworkAdapter = testFrameworkAdapter; _testStartTime = testStartTime; +#if NETCOREAPP _cancellationToken = cancellationToken; +#endif _exitCode = exitCode; _fileSystem = fileSystem; } @@ -174,7 +193,11 @@ public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testAp // Note that we need to dispose the IFileStream, not the inner stream. // IFileStream implementations will be responsible to dispose their inner stream. using IFileStream stream = _fileSystem.NewFileStream(finalFileName, isFileNameExplicitlyProvided ? FileMode.Create : FileMode.CreateNew); +#if NETCOREAPP await document.SaveAsync(stream.Stream, SaveOptions.None, _cancellationToken).ConfigureAwait(false); +#else + document.Save(stream.Stream, SaveOptions.None); +#endif return isFileNameExplicitlyProvidedAndFileExists ? (finalFileName, string.Format(CultureInfo.InvariantCulture, ExtensionResources.TrxFileExistsAndWillBeOverwritten, finalFileName)) : (finalFileName, null); @@ -228,7 +251,11 @@ public async Task AddArtifactsAsync(FileInfo trxFile, Dictionary> artifacts, XElement collectorDataEntries, string runDeploymentRoot) diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs index d0bfbffc1a..32bb2f0e16 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs @@ -48,7 +48,9 @@ public static void AddTrxReportProvider(this ITestApplicationBuilder builder) serviceProvider.GetService(), serviceProvider.GetLoggerFactory().CreateLogger())); +#if NETCOREAPP if (!OperatingSystem.IsBrowser()) +#endif { NonBrowserRegistrations(builder); } @@ -68,7 +70,9 @@ public static void AddTrxReportProvider(this ITestApplicationBuilder builder) serviceProvider.GetRequiredService())); } +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif private static void NonBrowserRegistrations(ITestApplicationBuilder builder) { builder.TestHost.AddTestHostApplicationLifetime(serviceProvider => diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs index 123f40848d..7e7c24217b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs @@ -14,7 +14,9 @@ namespace Microsoft.Testing.Extensions.TrxReport.Abstractions; internal sealed class TrxTestApplicationLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { +#if NETCOREAPP [UnsupportedOSPlatformGuard("BROWSER")] +#endif private readonly bool _isEnabled; private readonly IEnvironment _environment; diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj index e6df831a7a..9083c8659c 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/Microsoft.Testing.Extensions.VSTestBridge.csproj @@ -17,10 +17,13 @@ - + + + + @@ -41,8 +44,4 @@ This package provides a bridge integration for test adapters wanting to target b - - - - diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs index 8947174a9f..84b1b3c677 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs @@ -29,8 +29,7 @@ public abstract class VSTestBridgedTestFrameworkBase : ITestFramework, IDataProd /// The test framework capabilities. protected VSTestBridgedTestFrameworkBase(IServiceProvider serviceProvider, ITestFrameworkCapabilities capabilities) { - Ensure.NotNull(serviceProvider); - ServiceProvider = serviceProvider; + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); // NOTE: It's too early to determine from the capability at this point whether or not trx is enabled. // We store the capability and check it later when IsTrxEnabled is accessed. _trxReportCapability = capabilities.GetCapability(); diff --git a/src/Platform/Microsoft.Testing.Platform.AI/Microsoft.Testing.Platform.AI.csproj b/src/Platform/Microsoft.Testing.Platform.AI/Microsoft.Testing.Platform.AI.csproj index 402bba36b9..1c763e42eb 100644 --- a/src/Platform/Microsoft.Testing.Platform.AI/Microsoft.Testing.Platform.AI.csproj +++ b/src/Platform/Microsoft.Testing.Platform.AI/Microsoft.Testing.Platform.AI.csproj @@ -13,7 +13,10 @@ - + + + + diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Microsoft.Testing.Platform.MSBuild.csproj b/src/Platform/Microsoft.Testing.Platform.MSBuild/Microsoft.Testing.Platform.MSBuild.csproj index 6eb4132a3d..9184c4549d 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Microsoft.Testing.Platform.MSBuild.csproj +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Microsoft.Testing.Platform.MSBuild.csproj @@ -29,7 +29,10 @@ - + + + + diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/ConfigurationFileTask.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/ConfigurationFileTask.cs index d9f67c5476..1c885722d1 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/ConfigurationFileTask.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/ConfigurationFileTask.cs @@ -17,11 +17,8 @@ public sealed class ConfigurationFileTask : Build.Utilities.Task private const string ConfigurationFileNameSuffix = "testconfig.json"; private readonly IFileSystem _fileSystem; - internal ConfigurationFileTask(IFileSystem? fileSystem) - { - Ensure.NotNull(fileSystem); - _fileSystem = fileSystem; - } + internal ConfigurationFileTask(IFileSystem fileSystem) + => _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); /// /// Initializes a new instance of the class. diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs index c7e32b4f19..2c3cb7a023 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs @@ -6,7 +6,9 @@ namespace Microsoft.Testing.Platform.MSBuild.Tasks; +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class DotnetMuxerLocator { // Mach-O magic numbers from https://en.wikipedia.org/wiki/Mach-O diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs index 544781520b..4199bc05b5 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs @@ -23,7 +23,9 @@ namespace Microsoft.Testing.Platform.MSBuild; /// /// Task that invokes the Testing Platform. /// +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif public class InvokeTestingPlatformTask : Build.Utilities.ToolTask, IDisposable { private const string MonoRunnerName = "mono"; @@ -34,7 +36,13 @@ public class InvokeTestingPlatformTask : Build.Utilities.ToolTask, IDisposable private readonly CancellationTokenSource _waitForConnections = new(); private readonly List _connections = []; private readonly StringBuilder _output = new(); + +#if NET9_0_OR_GREATER private readonly Lock _initLock = new(); +#else + private readonly object _initLock = new(); +#endif + private readonly Architecture _currentProcessArchitecture = RuntimeInformation.ProcessArchitecture; private Task? _connectionLoopTask; @@ -208,7 +216,12 @@ protected override string ToolName } Log.LogMessage(MessageImportance.Low, $"Current process architecture '{_currentProcessArchitecture}'. Requested test architecture '{TestArchitecture.ItemSpec}'"); + +#if NETCOREAPP PlatformArchitecture targetArchitecture = Enum.Parse(TestArchitecture.ItemSpec, ignoreCase: true); +#else + var targetArchitecture = (PlatformArchitecture)Enum.Parse(typeof(PlatformArchitecture), TestArchitecture.ItemSpec, ignoreCase: true); +#endif StringBuilder resolutionLog = new(); DotnetMuxerLocator dotnetMuxerLocator = new(log => resolutionLog.AppendLine(log)); if (dotnetMuxerLocator.TryGetDotnetPathByArchitecture(targetArchitecture, out string? dotnetPath)) @@ -242,7 +255,11 @@ protected override string ToolName } private bool IsCurrentProcessArchitectureCompatible() => +#if NETCOREAPP _currentProcessArchitecture == Enum.Parse(TestArchitecture.ItemSpec, ignoreCase: true); +#else + _currentProcessArchitecture == (Architecture)Enum.Parse(typeof(Architecture), TestArchitecture.ItemSpec, ignoreCase: true); +#endif private string? TryGetRunCommand() { diff --git a/src/Platform/Microsoft.Testing.Platform/BannedSymbols.txt b/src/Platform/Microsoft.Testing.Platform/BannedSymbols.txt index 96906f3552..970d4b8294 100644 --- a/src/Platform/Microsoft.Testing.Platform/BannedSymbols.txt +++ b/src/Platform/Microsoft.Testing.Platform/BannedSymbols.txt @@ -1,6 +1,4 @@ -M:System.ArgumentNullException.ThrowIfNull(System.Object,System.String); Use 'ArgumentGuard' class instead -M:System.ArgumentNullException.#ctor; Use 'ArgumentGuard' class instead -P:System.DateTime.Now; Use 'IClock' instead +P:System.DateTime.Now; Use 'IClock' instead P:System.DateTime.UtcNow; Use 'IClock' instead T:System.Environment; Use 'IEnvironment' instead M:System.Diagnostics.Process.GetCurrentProcess(); Use 'IProcess' instead diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs index 32a4077998..85108aeaf5 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/TimeSpanParser.cs @@ -51,25 +51,25 @@ public static bool TryParse(string? time, out TimeSpan result) return true; } - if (suffix.StartsWith('s')) + if (suffix.StartsWith("s", StringComparison.Ordinal)) { result = TimeSpan.FromSeconds(number); return true; } - if (suffix.StartsWith('m')) + if (suffix.StartsWith("m", StringComparison.Ordinal)) { result = TimeSpan.FromMinutes(number); return true; } - if (suffix.StartsWith('h')) + if (suffix.StartsWith("h", StringComparison.Ordinal)) { result = TimeSpan.FromHours(number); return true; } - if (suffix.StartsWith('d')) + if (suffix.StartsWith("d", StringComparison.Ordinal)) { result = TimeSpan.FromDays(number); return true; diff --git a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs index a0bf10606d..5c8c0386ad 100644 --- a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs +++ b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs @@ -13,7 +13,9 @@ namespace Microsoft.Testing.Platform.IPC; [Embedded] +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class NamedPipeClient : NamedPipeBase, IClient { private const PipeOptions CurrentUserPipeOptions = PipeOptions.None @@ -39,7 +41,11 @@ public NamedPipeClient(string name) public NamedPipeClient(string name, IEnvironment environment) { - Ensure.NotNull(name); + if (name is null) + { + throw new ArgumentNullException(nameof(name)); + } + _namedPipeClientStream = new(".", name, PipeDirection.InOut, CurrentUserPipeOptions); PipeName = name; _environment = environment; diff --git a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs index 4b7adb31d3..f1fceb6bdb 100644 --- a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs +++ b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs @@ -12,7 +12,9 @@ namespace Microsoft.Testing.Platform.IPC; [Embedded] +#if NETCOREAPP [UnsupportedOSPlatform("browser")] +#endif internal sealed class NamedPipeServer : NamedPipeBase, IServer { private const PipeOptions AsyncCurrentUserPipeOptions = PipeOptions.Asynchronous @@ -69,7 +71,11 @@ public NamedPipeServer( int maxNumberOfServerInstances, CancellationToken cancellationToken) { - Ensure.NotNull(pipeNameDescription); + if (pipeNameDescription is null) + { + throw new ArgumentNullException(nameof(pipeNameDescription)); + } + _namedPipeServerStream = new((PipeName = pipeNameDescription).Name, PipeDirection.InOut, maxNumberOfServerInstances, PipeTransmissionMode.Byte, AsyncCurrentUserPipeOptions); _callback = callback; _environment = environment; diff --git a/src/Polyfills/Ensure.cs b/src/Polyfills/Ensure.cs index 22acf3c68b..ab6fa163f2 100644 --- a/src/Polyfills/Ensure.cs +++ b/src/Polyfills/Ensure.cs @@ -1,5 +1,7 @@ // +#pragma warning disable + using Microsoft.CodeAnalysis; namespace Polyfills; diff --git a/src/Polyfills/ExperimentalAttribute.cs b/src/Polyfills/ExperimentalAttribute.cs new file mode 100644 index 0000000000..b1215e3e14 --- /dev/null +++ b/src/Polyfills/ExperimentalAttribute.cs @@ -0,0 +1,55 @@ +// + +#pragma warning disable + +#if !NETCOREAPP + +using Microsoft.CodeAnalysis; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that a parameter captures the expression passed for another parameter as a string. +/// +/// +/// Indicates that an API is experimental and it may change in the future. +/// +[AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Enum | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Event | + AttributeTargets.Interface | + AttributeTargets.Delegate, Inherited = false)] +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[Embedded] +internal sealed class ExperimentalAttribute : Attribute +{ + /// + /// Initializes a new instance of the class, specifying the ID that the compiler will use + /// when reporting a use of the API the attribute applies to. + /// + public ExperimentalAttribute(string diagnosticId) => + DiagnosticId = diagnosticId; + + /// + /// Gets the ID that the compiler will use when reporting a use of the API the attribute applies to. + /// + public string DiagnosticId { get; } + + /// + /// Gets or sets the URL for corresponding documentation. + /// The API accepts a format string instead of an actual URL, creating a generic URL that includes the diagnostic ID. + /// + public string? UrlFormat { get; set; } +} + +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.ExperimentalAttribute))] +#endif diff --git a/src/Polyfills/ProcessExtensions.cs b/src/Polyfills/ProcessExtensions.cs new file mode 100644 index 0000000000..d8a8da7f25 --- /dev/null +++ b/src/Polyfills/ProcessExtensions.cs @@ -0,0 +1,70 @@ +// + +#pragma warning disable + +using System; +using System.Diagnostics; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis; + +namespace Polyfills; + +[Embedded] +internal static partial class Polyfill +{ +#if !NET + + /// + /// Instructs the Process component to wait for the associated process to exit, or + /// for the to be canceled. + /// + public static async Task WaitForExitAsync(this Process target, CancellationToken cancellationToken = default) + { + if (!target.HasExited) + { + cancellationToken.ThrowIfCancellationRequested(); + } + + try + { + target.EnableRaisingEvents = true; + } + catch (InvalidOperationException) + { + if (target.HasExited) + { + // await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); + return; + } + + throw; + } + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + EventHandler handler = (_, _) => tcs.TrySetResult(null); + target.Exited += handler; + + try + { + if (!target.HasExited) + { + // NOTE: BCL uses UnsafeRegister instead. + using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken))) + { + await tcs.Task.ConfigureAwait(false); + } + } + + // await WaitUntilOutputEOF(cancellationToken).ConfigureAwait(false); + } + finally + { + target.Exited -= handler; + } + } +#endif +} diff --git a/src/Polyfills/UnreachableException.cs b/src/Polyfills/UnreachableException.cs index 4fdac3981b..94314e1f57 100644 --- a/src/Polyfills/UnreachableException.cs +++ b/src/Polyfills/UnreachableException.cs @@ -1,5 +1,7 @@ // +#pragma warning disable + #nullable enable #if !NETCOREAPP diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs index 34729b1a8c..491a882efe 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs @@ -537,9 +537,22 @@ public async Task TrxReportEngine_GenerateReportAsync_FileAlreadyExists_WillRetr _ = _configurationMock.SetupGet(_ => _[It.IsAny()]).Returns(string.Empty); _ = _environmentMock.SetupGet(_ => _.MachineName).Returns("MachineName"); _ = _testApplicationModuleInfoMock.Setup(_ => _.GetCurrentTestApplicationFullPath()).Returns("TestAppPath"); - var trxReportEngine = new TrxReportEngine(_fileSystem.Object, _testApplicationModuleInfoMock.Object, _environmentMock.Object, _commandLineOptionsMock.Object, - _configurationMock.Object, _clockMock.Object, - _artifactsByExtension, _testFrameworkMock.Object, DateTime.UtcNow, 0, CancellationToken.None); + var trxReportEngine = new TrxReportEngine( + _fileSystem.Object, + _testApplicationModuleInfoMock.Object, + _environmentMock.Object, + _commandLineOptionsMock.Object, + _configurationMock.Object, + _clockMock.Object, + _artifactsByExtension, + _testFrameworkMock.Object, + DateTime.UtcNow, +#if NETCOREAPP + 0, + CancellationToken.None); +#else + 0); +#endif // Act _ = await trxReportEngine.GenerateReportAsync([]); @@ -693,7 +706,6 @@ private static TestNodeUpdateMessage CreateTestNodeUpdate(string uid, string dis private TrxReportEngine GenerateTrxReportEngine(MemoryFileStream memoryStream, bool isExplicitFileName = false) { DateTime testStartTime = DateTime.Now; - CancellationToken cancellationToken = CancellationToken.None; _ = _fileSystem.Setup(x => x.ExistFile(It.IsAny())).Returns(false); _ = _fileSystem.Setup(x => x.NewFileStream(It.IsAny(), isExplicitFileName ? FileMode.Create : FileMode.CreateNew)) @@ -703,9 +715,22 @@ private TrxReportEngine GenerateTrxReportEngine(MemoryFileStream memoryStream, b _ = _environmentMock.SetupGet(_ => _.MachineName).Returns("MachineName"); _ = _testApplicationModuleInfoMock.Setup(_ => _.GetCurrentTestApplicationFullPath()).Returns("TestAppPath"); - return new TrxReportEngine(_fileSystem.Object, _testApplicationModuleInfoMock.Object, _environmentMock.Object, _commandLineOptionsMock.Object, - _configurationMock.Object, _clockMock.Object, - _artifactsByExtension, _testFrameworkMock.Object, testStartTime, 0, cancellationToken); + return new TrxReportEngine( + _fileSystem.Object, + _testApplicationModuleInfoMock.Object, + _environmentMock.Object, + _commandLineOptionsMock.Object, + _configurationMock.Object, + _clockMock.Object, + _artifactsByExtension, + _testFrameworkMock.Object, + testStartTime, +#if NETCOREAPP + 0, + CancellationToken.None); +#else + 0); +#endif } private sealed class MemoryFileStream : IFileStream From 413581de8c31c52b5a32e0875d4ee38e8525dcc9 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 12:36:03 +0200 Subject: [PATCH 06/24] MTP --- .../HangDumpActivityIndicator.cs | 2 - .../HangDumpExtensions.cs | 4 - .../HangDumpProcessLifetimeHandler.cs | 4 - .../Helpers/IProcessExtensions.cs | 10 - .../TestingPlatformBuilderHook.cs | 2 - .../HotReloadHandler.cs | 4 - .../MSBuildConsumer.cs | 2 - .../MSBuildExtensions.cs | 4 - .../MSBuildOrchestratorLifecycleCallbacks.cs | 2 - ...SBuildTestApplicationLifecycleCallbacks.cs | 2 - .../TestingPlatformBuilderHook.cs | 2 - .../RetryDataConsumer.cs | 2 - .../RetryExecutionFilterFactory.cs | 2 - .../RetryExtensions.cs | 4 - .../RetryFailedTestsPipeServer.cs | 2 - .../RetryLifecycleCallbacks.cs | 2 - .../RetryOrchestrator.cs | 2 - .../TestingPlatformBuilderHook.cs | 2 - .../TrxModeHelpers.cs | 9 +- .../TrxProcessLifetimeHandler.cs | 2 - .../TrxReportExtensions.cs | 4 - .../TrxTestApplicationLifecycleCallbacks.cs | 2 - .../Tasks/DotnetMuxerLocator.cs | 2 - .../Tasks/InvokeTestingPlatformTask.cs | 2 - .../Builder/TestApplication.cs | 18 +- .../Configurations/ConfigurationExtensions.cs | 6 +- .../Extensions/CompositeExtensionsFactory.cs | 15 +- .../Helpers/ExtensionValidationHelper.cs | 6 +- .../Helpers/System/SystemEnvironment.cs | 24 +++ .../Hosts/ServerTestHost.cs | 12 +- .../IPC/NamedPipeClient.cs | 2 - .../IPC/NamedPipeServer.cs | 2 - .../Logging/LoggerFactoryExtensions.cs | 5 +- .../Messages/MessageBusProxy.cs | 5 +- .../Messages/PropertyBag.cs | 6 +- .../Microsoft.Testing.Platform.csproj | 2 +- .../Terminal/TerminalTestReporter.cs | 5 + .../TestProgressStateAwareTerminal.cs | 4 + .../Requests/TreeNodeFilter/TreeNodeFilter.cs | 2 +- .../ServerMode/JsonRpc/SerializerUtilities.cs | 25 ++- .../Services/ServiceProviderExtensions.cs | 10 +- .../TestHost/TestHostManager.cs | 23 +- .../TestHostOrchestratorManager.cs | 6 +- .../Tools/ToolsManager.cs | 5 +- .../CompilerLoweringPreserveAttribute.cs | 13 ++ .../DynamicallyAccessedMemberTypes.cs | 173 +++++++++++++++ .../DynamicallyAccessedMembersAttribute.cs | 60 ++++++ src/Polyfills/PlatformAttributes.cs | 200 ++++++++++++++++++ 48 files changed, 554 insertions(+), 150 deletions(-) create mode 100644 src/Polyfills/CompilerLoweringPreserveAttribute.cs create mode 100644 src/Polyfills/DynamicallyAccessedMemberTypes.cs create mode 100644 src/Polyfills/DynamicallyAccessedMembersAttribute.cs create mode 100644 src/Polyfills/PlatformAttributes.cs diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs index 463e21b581..172393a11c 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs @@ -16,9 +16,7 @@ namespace Microsoft.Testing.Extensions.Diagnostics; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class HangDumpActivityIndicator : IDataConsumer, ITestSessionLifetimeHandler, #if NETCOREAPP IAsyncDisposable, diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs index 619c2e7d0f..2e76ca62cb 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs @@ -18,14 +18,11 @@ public static class HangDumpExtensions /// Adds hang dump support to the test application. /// /// The test application builder. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif public static void AddHangDumpProvider(this ITestApplicationBuilder builder) { -#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException("Hang dump extension is not available on browser"); @@ -35,7 +32,6 @@ public static void AddHangDumpProvider(this ITestApplicationBuilder builder) { throw new PlatformNotSupportedException("Hang dump extension is not available on ios nor tvos"); } -#endif PipeNameDescription pipeNameDescription = NamedPipeServer.GetPipeName(Guid.NewGuid().ToString("N")); diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs index ad4e82772b..6c2daf0283 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpProcessLifetimeHandler.cs @@ -23,11 +23,9 @@ namespace Microsoft.Testing.Extensions.Diagnostics; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif internal sealed class HangDumpProcessLifetimeHandler : ITestHostProcessLifetimeHandler, IOutputDeviceDataProducer, IDataProducer, #if NETCOREAPP IAsyncDisposable, @@ -220,11 +218,9 @@ public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testH } } -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif private async Task TakeDumpOfTreeAsync(CancellationToken cancellationToken) { ApplicationStateGuard.Ensure(_testHostProcessInformation is not null); diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs index ac6b33b053..09cf63650b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/Helpers/IProcessExtensions.cs @@ -20,11 +20,9 @@ internal static class IProcessExtensions { private const int InvalidProcessId = -1; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif public static async Task> GetProcessTreeAsync(this IProcess process, ILogger logger, OutputDeviceWriter outputDisplay, CancellationToken cancellationToken) { var childProcesses = Process.GetProcesses() @@ -73,9 +71,7 @@ internal static async Task GetParentPidAsync(Process process, ILogger logge await GetParentPidMacOsAsync(process, logger, outputDisplay, cancellationToken).ConfigureAwait(false) : throw new PlatformNotSupportedException(); -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal static int GetParentPidWindows(Process process) { IntPtr handle = process.Handle; @@ -89,9 +85,7 @@ internal static int GetParentPidWindows(Process process) /// Read the /proc file system for information about the parent. /// The process to get the parent process from. /// The process id. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal static int GetParentPidLinux(Process process) { int pid = process.Id; @@ -108,11 +102,9 @@ internal static int GetParentPidLinux(Process process) return ppid; } -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif internal static async Task GetParentPidMacOsAsync(Process process, ILogger logger, OutputDeviceWriter outputDisplay, CancellationToken cancellationToken) { var output = new StringBuilder(); @@ -166,11 +158,9 @@ private static void ResolveChildren(IProcess parent, ILogger logger, List /// The test application builder. /// The command line arguments. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] -#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddHangDumpProvider(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs index 5ffd899503..28426efe70 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs @@ -43,7 +43,6 @@ public HotReloadHandler(IConsole console, IOutputDevice outputDevice, IOutputDev } } -#if NETCOREAPP [SupportedOSPlatformGuard("android")] [SupportedOSPlatformGuard("ios")] [SupportedOSPlatformGuard("tvos")] @@ -55,9 +54,6 @@ private static bool IsCancelKeyPressNotSupported() OperatingSystem.IsTvOS() || OperatingSystem.IsWasi() || OperatingSystem.IsBrowser(); -#else - private static bool IsCancelKeyPressNotSupported() => false; -#endif // Called automatically by the runtime through the MetadataUpdateHandlerAttribute public static void ClearCache(Type[]? _) diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs index d3b8652033..ce04d52500 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildConsumer.cs @@ -12,9 +12,7 @@ namespace Microsoft.Testing.Extensions.MSBuild; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class MSBuildConsumer : IDataConsumer, ITestSessionLifetimeHandler { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs index d2f94edbab..224a5dfce8 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs @@ -17,17 +17,13 @@ public static class MSBuildExtensions /// Adds MSBuild support to the test application builder. /// /// The test application builder. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif public static void AddMSBuild(this ITestApplicationBuilder builder) { -#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException("MSBuild extension is not supported in browser environments."); } -#endif builder.CommandLine.AddProvider(() => new MSBuildCommandLineProvider()); builder.TestHost.AddTestHostApplicationLifetime( diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs index ff0c8c5586..e8b4b37ff2 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildOrchestratorLifecycleCallbacks.cs @@ -12,9 +12,7 @@ namespace Microsoft.Testing.Extensions.MSBuild; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class MSBuildOrchestratorLifetime : ITestHostOrchestratorApplicationLifetime { private readonly IConfiguration _configuration; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs index bfd3315025..4230348eeb 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildTestApplicationLifecycleCallbacks.cs @@ -12,9 +12,7 @@ namespace Microsoft.Testing.Extensions.MSBuild; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class MSBuildTestApplicationLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { private readonly IConfiguration _configuration; diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs index 6e69aee65e..a84a7ec7d4 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/TestingPlatformBuilderHook.cs @@ -15,9 +15,7 @@ public static class TestingPlatformBuilderHook /// /// The test application builder. /// The command line arguments. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddMSBuild(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs index 05c0cded56..1b2317c0bf 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryDataConsumer.cs @@ -14,9 +14,7 @@ namespace Microsoft.Testing.Extensions.Policy; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class RetryDataConsumer : IDataConsumer, ITestSessionLifetimeHandler, IAsyncInitializableExtension { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs index c61aa27c1a..5e75951d3f 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExecutionFilterFactory.cs @@ -10,9 +10,7 @@ namespace Microsoft.Testing.Extensions.Policy; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class RetryExecutionFilterFactory : ITestExecutionFilterFactory { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs index 33b7594261..404fce2aaf 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs @@ -18,17 +18,13 @@ public static class RetryExtensions /// Adds the retry provider to the test application. /// /// The test application builder. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif public static void AddRetryProvider(this ITestApplicationBuilder builder) { -#if NETCOREAPP if (OperatingSystem.IsBrowser()) { throw new PlatformNotSupportedException(ExtensionResources.RetryExtensionNotSupportedOnBrowserErrorMessage); } -#endif builder.CommandLine.AddProvider(() => new RetryCommandLineOptionsProvider()); diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs index 2e3b91910e..e61d4cd29a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryFailedTestsPipeServer.cs @@ -11,9 +11,7 @@ namespace Microsoft.Testing.Extensions.Policy; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class RetryFailedTestsPipeServer : IDisposable { private readonly NamedPipeServer _singleConnectionNamedPipeServer; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs index 4cc762bead..25141d818a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryLifecycleCallbacks.cs @@ -14,9 +14,7 @@ namespace Microsoft.Testing.Extensions.Policy; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class RetryLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs index 344367b58a..5aeb86304a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryOrchestrator.cs @@ -13,9 +13,7 @@ namespace Microsoft.Testing.Extensions.Policy; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class RetryOrchestrator : ITestHostExecutionOrchestrator, IOutputDeviceDataProducer { private readonly IServiceProvider _serviceProvider; diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs index f03a506d8e..01b0410042 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/TestingPlatformBuilderHook.cs @@ -15,9 +15,7 @@ public static class TestingPlatformBuilderHook /// /// The test application builder. /// The command line arguments. -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif public static void AddExtensions(ITestApplicationBuilder testApplicationBuilder, string[] _) => testApplicationBuilder.AddRetryProvider(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs index 5050f5258e..52d69fb7a6 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs @@ -7,13 +7,8 @@ namespace Microsoft.Testing.Extensions.TrxReport; internal static class TrxModeHelpers { -#if NETCOREAPP [UnsupportedOSPlatformGuard("BROWSER")] -#endif public static bool ShouldUseOutOfProcessTrxGeneration(ICommandLineOptions commandLineOptions) - => commandLineOptions.IsOptionSet(CrashDumpCommandLineOptions.CrashDumpOptionName) -#if NETCOREAPP - && !OperatingSystem.IsBrowser() -#endif - ; + => commandLineOptions.IsOptionSet(CrashDumpCommandLineOptions.CrashDumpOptionName) && + !OperatingSystem.IsBrowser(); } diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs index 60ce0cf162..75f7a507cd 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs @@ -115,9 +115,7 @@ public Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) return Task.CompletedTask; } -#if NETCOREAPP [UnsupportedOSPlatform("BROWSER")] -#endif private void BeforeTestHostProcessStartCore(CancellationToken cancellationToken) => _waitConnectionTask = _task.Run( async () => diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs index 32bb2f0e16..d0bfbffc1a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs @@ -48,9 +48,7 @@ public static void AddTrxReportProvider(this ITestApplicationBuilder builder) serviceProvider.GetService(), serviceProvider.GetLoggerFactory().CreateLogger())); -#if NETCOREAPP if (!OperatingSystem.IsBrowser()) -#endif { NonBrowserRegistrations(builder); } @@ -70,9 +68,7 @@ public static void AddTrxReportProvider(this ITestApplicationBuilder builder) serviceProvider.GetRequiredService())); } -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif private static void NonBrowserRegistrations(ITestApplicationBuilder builder) { builder.TestHost.AddTestHostApplicationLifetime(serviceProvider => diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs index 7e7c24217b..123f40848d 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxTestApplicationLifecycleCallbacks.cs @@ -14,9 +14,7 @@ namespace Microsoft.Testing.Extensions.TrxReport.Abstractions; internal sealed class TrxTestApplicationLifecycleCallbacks : ITestHostApplicationLifetime, IDisposable { -#if NETCOREAPP [UnsupportedOSPlatformGuard("BROWSER")] -#endif private readonly bool _isEnabled; private readonly IEnvironment _environment; diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs index 2c3cb7a023..c7e32b4f19 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/DotnetMuxerLocator.cs @@ -6,9 +6,7 @@ namespace Microsoft.Testing.Platform.MSBuild.Tasks; -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class DotnetMuxerLocator { // Mach-O magic numbers from https://en.wikipedia.org/wiki/Mach-O diff --git a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs index 4199bc05b5..5b3fec02c0 100644 --- a/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs +++ b/src/Platform/Microsoft.Testing.Platform.MSBuild/Tasks/InvokeTestingPlatformTask.cs @@ -23,9 +23,7 @@ namespace Microsoft.Testing.Platform.MSBuild; /// /// Task that invokes the Testing Platform. /// -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif public class InvokeTestingPlatformTask : Build.Utilities.ToolTask, IDisposable { private const string MonoRunnerName = "mono"; diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index b0b5f331e0..72bee2056e 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -6,7 +6,9 @@ using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Hosts; using Microsoft.Testing.Platform.Logging; +#if NETCOREAPP using Microsoft.Testing.Platform.Resources; +#endif using Microsoft.Testing.Platform.Services; using Microsoft.Testing.Platform.TestHostControllers; @@ -81,10 +83,8 @@ public static async Task CreateBuilderAsync(string[] ar { throw new PlatformNotSupportedException(PlatformResources.WaitDebuggerAttachNotSupportedInBrowserErrorMessage); } - else - { - WaitForDebuggerToAttach(systemEnvironment, systemConsole, systemProcess); - } + + WaitForDebuggerToAttach(systemEnvironment, systemConsole, systemProcess); } TestHostControllerInfo testHostControllerInfo = new(parseResult); @@ -235,10 +235,8 @@ private static void AttachDebuggerIfNeeded(SystemEnvironment environment, System { throw new PlatformNotSupportedException(PlatformResources.WaitDebuggerAttachNotSupportedInBrowserErrorMessage); } - else - { - WaitForDebuggerToAttach(environment, console, systemProcess); - } + + WaitForDebuggerToAttach(environment, console, systemProcess); } } @@ -294,7 +292,11 @@ private static ApplicationLoggingState CreateFileLoggerIfDiagnosticIsEnabled( if (result.TryGetOptionArgumentList(PlatformCommandLineProvider.DiagnosticVerbosityOptionKey, out string[]? verbosity)) { +#if NETCOREAPP logLevel = Enum.Parse(verbosity[0], true); +#else + logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), verbosity[0], true); +#endif } // Override the log level if the environment variable is set diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationExtensions.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationExtensions.cs index 0462e1f0df..116217d5fb 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationExtensions.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/ConfigurationExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.Testing.Platform.Helpers; + namespace Microsoft.Testing.Platform.Configurations; /// @@ -16,7 +18,7 @@ public static class ConfigurationExtensions public static string GetTestResultDirectory(this IConfiguration configuration) { string? resultDirectory = configuration[PlatformConfigurationConstants.PlatformResultDirectory]; - return Ensure.NotNull(resultDirectory); + return resultDirectory ?? throw ApplicationStateGuard.Unreachable(); } /// @@ -27,6 +29,6 @@ public static string GetTestResultDirectory(this IConfiguration configuration) public static string GetCurrentWorkingDirectory(this IConfiguration configuration) { string? workingDirectory = configuration[PlatformConfigurationConstants.PlatformCurrentWorkingDirectory]; - return Ensure.NotNull(workingDirectory); + return workingDirectory ?? throw ApplicationStateGuard.Unreachable(); } } diff --git a/src/Platform/Microsoft.Testing.Platform/Extensions/CompositeExtensionsFactory.cs b/src/Platform/Microsoft.Testing.Platform/Extensions/CompositeExtensionsFactory.cs index c5113f0b2e..a7f21e3103 100644 --- a/src/Platform/Microsoft.Testing.Platform/Extensions/CompositeExtensionsFactory.cs +++ b/src/Platform/Microsoft.Testing.Platform/Extensions/CompositeExtensionsFactory.cs @@ -17,7 +17,12 @@ namespace Microsoft.Testing.Platform.Extensions; public class CompositeExtensionFactory : ICompositeExtensionFactory, ICloneable where TExtension : class, IExtension { +#if NET9_0_OR_GREATER private readonly Lock _syncLock = new(); +#else + private readonly object _syncLock = new(); +#endif + private readonly Func? _factoryWithServiceProvider; private readonly Func? _factory; private TExtension? _instance; @@ -35,20 +40,14 @@ You cannot compose extensions that belong to different areas. /// /// The factory function that creates the extension with a service provider. public CompositeExtensionFactory(Func factory) - { - Ensure.NotNull(factory); - _factoryWithServiceProvider = factory; - } + => _factoryWithServiceProvider = factory ?? throw new ArgumentNullException(nameof(factory)); /// /// Initializes a new instance of the class. /// /// The factory function that creates the extension. public CompositeExtensionFactory(Func factory) - { - Ensure.NotNull(factory); - _factory = factory; - } + => _factory = factory ?? throw new ArgumentNullException(nameof(factory)); /// object ICloneable.Clone() diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/ExtensionValidationHelper.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/ExtensionValidationHelper.cs index 834763d9d7..cf10332bfa 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/ExtensionValidationHelper.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/ExtensionValidationHelper.cs @@ -18,9 +18,9 @@ internal static class ExtensionValidationHelper /// Function to extract the IExtension from the collection item. public static void ValidateUniqueExtension(this IEnumerable existingExtensions, IExtension newExtension, Func extensionSelector) { - Ensure.NotNull(existingExtensions); - Ensure.NotNull(newExtension); - Ensure.NotNull(extensionSelector); + _ = existingExtensions ?? throw new ArgumentNullException(nameof(existingExtensions)); + _ = newExtension ?? throw new ArgumentNullException(nameof(newExtension)); + _ = extensionSelector ?? throw new ArgumentNullException(nameof(extensionSelector)); T[] duplicates = [.. existingExtensions.Where(x => extensionSelector(x).Uid == newExtension.Uid)]; if (duplicates.Length > 0) diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemEnvironment.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemEnvironment.cs index 5723b9cfa5..ece5ffc2ad 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemEnvironment.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemEnvironment.cs @@ -12,7 +12,31 @@ internal sealed class SystemEnvironment : IEnvironment public string NewLine => Environment.NewLine; +#if !NETCOREAPP + public int ProcessId + { + get + { + int processId = field; + if (processId == 0) + { + field = processId = GetProcessId(); + // Assume that process Id zero is invalid for user processes. It holds for all mainstream operating systems. + Debug.Assert(processId != 0, "processId is expected to be non-zero."); + } + + return processId; + + static int GetProcessId() + { + using var process = Process.GetCurrentProcess(); + return process.Id; + } + } + } +#else public int ProcessId => Environment.ProcessId; +#endif public string OsVersion => Environment.OSVersion.ToString(); diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs index 1d849b6ad4..fe2a8d5d96 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.cs @@ -205,7 +205,9 @@ private async Task HandleMessagesAsync(CancellationToken cancellationToken) if (!_serverClosingTokenSource.IsCancellationRequested) { await _logger.LogDebugAsync("Server requested to shutdown").ConfigureAwait(false); - await _serverClosingTokenSource.CancelAsync().ConfigureAwait(false); +#pragma warning disable VSTHRD103 // Call async methods when in an async method + _serverClosingTokenSource.Cancel(); +#pragma warning restore VSTHRD103 // Call async methods when in an async method } // Signal the exit call @@ -214,7 +216,9 @@ private async Task HandleMessagesAsync(CancellationToken cancellationToken) // If there're no in-flight request we can close the server if (_clientToServerRequests.IsEmpty) { - await _stopMessageHandler.CancelAsync().ConfigureAwait(false); +#pragma warning disable VSTHRD103 // Call async methods when in an async method + _stopMessageHandler.Cancel(); +#pragma warning restore VSTHRD103 // Call async methods when in an async method } continue; @@ -711,7 +715,11 @@ await SendMessageAsync( private sealed class RpcInvocationState : IDisposable { +#if NET9_0_OR_GREATER private readonly Lock _cancellationTokenSourceLock = new(); +#else + private readonly object _cancellationTokenSourceLock = new(); +#endif private readonly CancellationTokenSource _cancellationTokenSource = new(); private volatile bool _isDisposed; diff --git a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs index 5c8c0386ad..61f9b2a93f 100644 --- a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs +++ b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeClient.cs @@ -13,9 +13,7 @@ namespace Microsoft.Testing.Platform.IPC; [Embedded] -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class NamedPipeClient : NamedPipeBase, IClient { private const PipeOptions CurrentUserPipeOptions = PipeOptions.None diff --git a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs index f1fceb6bdb..3ce97de543 100644 --- a/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs +++ b/src/Platform/Microsoft.Testing.Platform/IPC/NamedPipeServer.cs @@ -12,9 +12,7 @@ namespace Microsoft.Testing.Platform.IPC; [Embedded] -#if NETCOREAPP [UnsupportedOSPlatform("browser")] -#endif internal sealed class NamedPipeServer : NamedPipeBase, IServer { private const PipeOptions AsyncCurrentUserPipeOptions = PipeOptions.Asynchronous diff --git a/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryExtensions.cs b/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryExtensions.cs index 316b19a27d..6cd15ff20d 100644 --- a/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryExtensions.cs +++ b/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryExtensions.cs @@ -15,8 +15,5 @@ public static class LoggerFactoryExtensions /// The logger factory. /// A logger instance. public static ILogger CreateLogger(this ILoggerFactory factory) - { - Ensure.NotNull(factory); - return new Logger(factory); - } + => new Logger(factory ?? throw new ArgumentNullException(nameof(factory))); } diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/MessageBusProxy.cs b/src/Platform/Microsoft.Testing.Platform/Messages/MessageBusProxy.cs index 0ca1b6732b..ff84a5e799 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/MessageBusProxy.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/MessageBusProxy.cs @@ -20,10 +20,7 @@ public override async Task InitAsync() } public void SetBuiltMessageBus(BaseMessageBus messageBus) - { - Ensure.NotNull(messageBus); - _messageBus = messageBus; - } + => _messageBus = messageBus ?? throw new ArgumentNullException(nameof(messageBus)); public override async Task PublishAsync(IDataProducer dataProducer, IData data) { diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs index 7a11f8dbbe..63fe5ccee4 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.cs @@ -35,7 +35,7 @@ public PropertyBag() /// The collection of properties. public PropertyBag(params IProperty[] properties) { - Ensure.NotNull(properties); + _ = properties ?? throw new ArgumentNullException(nameof(properties)); if (properties.Length == 0) { @@ -78,7 +78,7 @@ public PropertyBag(params IProperty[] properties) /// The collection of properties. public PropertyBag(IEnumerable properties) { - Ensure.NotNull(properties); + _ = properties ?? throw new ArgumentNullException(nameof(properties)); foreach (IProperty property in properties) { @@ -123,7 +123,7 @@ public PropertyBag(IEnumerable properties) /// The property to add. public void Add(IProperty property) { - Ensure.NotNull(property); + _ = property ?? throw new ArgumentNullException(nameof(property)); // Optimized access to the TestNodeStateProperty, it's one of the most used property. if (property is TestNodeStateProperty testNodeStateProperty) diff --git a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj index 0c69386a00..9e4e140ae8 100644 --- a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj +++ b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj @@ -83,7 +83,7 @@ This package provides the core platform and the .NET implementation of the proto - + diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs index 21e715888d..cf6782a065 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TerminalTestReporter.cs @@ -46,7 +46,12 @@ internal event EventHandler OnProgressStopUpdate private readonly TerminalTestReporterOptions _options; private readonly TestProgressStateAwareTerminal _terminalWithProgress; + +#if NET9_0_OR_GREATER private readonly Lock _lock = new(); +#else + private readonly object _lock = new(); +#endif private readonly uint? _originalConsoleMode; diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs index 4167806c4c..23a0a6c773 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/Terminal/TestProgressStateAwareTerminal.cs @@ -17,7 +17,11 @@ internal sealed partial class TestProgressStateAwareTerminal : IDisposable /// /// Protects access to state shared between the logger callbacks and the rendering thread. /// +#if NET9_0_OR_GREATER private readonly Lock _lock = new(); +#else + private readonly object _lock = new(); +#endif private readonly ITerminal _terminal; private readonly Func _showProgress; diff --git a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs index 3bc210353c..4edad877df 100644 --- a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs +++ b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs @@ -24,7 +24,7 @@ public sealed class TreeNodeFilter : ITestExecutionFilter internal TreeNodeFilter(string filter) { - Filter = Ensure.NotNull(filter); + Filter = filter ?? throw new ArgumentNullException(nameof(filter)); _filters = ParseFilter(filter); } diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs index 7c16ce0d93..0d1a572f86 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs @@ -224,7 +224,7 @@ static SerializerUtilities() : $"{testMethodIdentifierProperty.Namespace}.{testMethodIdentifierProperty.TypeName}"; properties["location.method"] = testMethodIdentifierProperty.ParameterTypeFullNames.Length > 0 - ? $"{testMethodIdentifierProperty.MethodName}({string.Join(',', testMethodIdentifierProperty.ParameterTypeFullNames)})" + ? $"{testMethodIdentifierProperty.MethodName}({string.Join(",", testMethodIdentifierProperty.ParameterTypeFullNames)})" : testMethodIdentifierProperty.MethodName; properties["location.method-arity"] = testMethodIdentifierProperty.MethodArity; @@ -354,7 +354,14 @@ static SerializerUtilities() } } - properties.TryAdd("node-type", "group"); +#if NETCOREAPP + properties.Add("node-type", "group"); +#else + if (!properties.ContainsKey("node-type")) + { + properties.Add("node-type", "group"); + } +#endif return properties; }); @@ -406,11 +413,11 @@ static SerializerUtilities() values[JsonRpcStrings.EnvironmentVariables] = ev.EnvironmentVariables; #else JsonArray collection = []; - foreach ((string? key, string? value) in ev.EnvironmentVariables) + foreach (KeyValuePair kvp in ev.EnvironmentVariables) { JsonObject o = new() { - { key, value }, + { kvp.Key, kvp.Value }, }; collection.Add(o); } @@ -610,17 +617,17 @@ static SerializerUtilities() string displayName = string.Empty; PropertyBag propertyBag = new(); - foreach ((string? key, object? value) in properties) + foreach (KeyValuePair kvp in properties) { - if (key == JsonRpcStrings.Uid) + if (kvp.Key == JsonRpcStrings.Uid) { - uid = value as string ?? string.Empty; + uid = kvp.Value as string ?? string.Empty; continue; } - if (key == JsonRpcStrings.DisplayName) + if (kvp.Key == JsonRpcStrings.DisplayName) { - displayName = value as string ?? string.Empty; + displayName = kvp.Value as string ?? string.Empty; continue; } } diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs b/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs index 0b44510b11..6bea6de92d 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ServiceProviderExtensions.cs @@ -32,7 +32,7 @@ public static class ServiceProviderExtensions public static TService GetRequiredService(this IServiceProvider provider) where TService : notnull { - Ensure.NotNull(provider); + _ = provider ?? throw new ArgumentNullException(nameof(provider)); object? service = provider.GetService(typeof(TService)); ApplicationStateGuard.Ensure(service is not null, string.Format(CultureInfo.InvariantCulture, PlatformResources.ServiceProviderCannotFindServiceErrorMessage, typeof(TService))); @@ -50,7 +50,7 @@ public static TService GetRequiredService(this IServiceProvider provid public static TService? GetService(this IServiceProvider provider) where TService : class { - Ensure.NotNull(provider); + _ = provider ?? throw new ArgumentNullException(nameof(provider)); return provider.GetService(typeof(TService)) as TService; } @@ -108,7 +108,7 @@ public static IClientInfo GetClientInfo(this IServiceProvider serviceProvider) internal static TService GetRequiredServiceInternal(this IServiceProvider provider) where TService : notnull { - Ensure.NotNull(provider); + _ = provider ?? throw new ArgumentNullException(nameof(provider)); object? service = ((ServiceProvider)provider).GetServiceInternal(typeof(TService)); ApplicationStateGuard.Ensure(service is not null, string.Format(CultureInfo.InvariantCulture, PlatformResources.ServiceProviderCannotFindServiceErrorMessage, typeof(TService))); @@ -119,7 +119,7 @@ internal static TService GetRequiredServiceInternal(this IServiceProvi internal static TService? GetServiceInternal(this IServiceProvider provider) where TService : class { - Ensure.NotNull(provider); + _ = provider ?? throw new ArgumentNullException(nameof(provider)); return ((ServiceProvider)provider).GetServiceInternal(typeof(TService)) as TService; } @@ -127,7 +127,7 @@ internal static TService GetRequiredServiceInternal(this IServiceProvi internal static IEnumerable GetServicesInternal(this IServiceProvider provider) where TService : notnull { - Ensure.NotNull(provider); + _ = provider ?? throw new ArgumentNullException(nameof(provider)); return ((ServiceProvider)provider).GetServicesInternal(typeof(TService)).Cast(); } diff --git a/src/Platform/Microsoft.Testing.Platform/TestHost/TestHostManager.cs b/src/Platform/Microsoft.Testing.Platform/TestHost/TestHostManager.cs index 2c640d3258..e54c1c5f26 100644 --- a/src/Platform/Microsoft.Testing.Platform/TestHost/TestHostManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/TestHost/TestHostManager.cs @@ -30,13 +30,12 @@ internal sealed class TestHostManager : ITestHostManager public void AddTestFrameworkInvoker(Func testFrameworkInvokerFactory) { - Ensure.NotNull(testFrameworkInvokerFactory); if (_testFrameworkInvokerFactory is not null) { throw new InvalidOperationException(PlatformResources.TestAdapterInvokerFactoryAlreadySetErrorMessage); } - _testFrameworkInvokerFactory = testFrameworkInvokerFactory; + _testFrameworkInvokerFactory = testFrameworkInvokerFactory ?? throw new ArgumentNullException(nameof(testFrameworkInvokerFactory)); } internal async Task> TryBuildTestAdapterInvokerAsync(ServiceProvider serviceProvider) @@ -61,13 +60,12 @@ internal async Task> TryBuildTestAdapterInvo public void AddTestExecutionFilterFactory(Func testExecutionFilterFactory) { - Ensure.NotNull(testExecutionFilterFactory); if (_testExecutionFilterFactory is not null) { throw new InvalidOperationException(PlatformResources.TEstExecutionFilterFactoryFactoryAlreadySetErrorMessage); } - _testExecutionFilterFactory = testExecutionFilterFactory; + _testExecutionFilterFactory = testExecutionFilterFactory ?? throw new ArgumentNullException(nameof(testExecutionFilterFactory)); } internal async Task> TryBuildTestExecutionFilterFactoryAsync(ServiceProvider serviceProvider) @@ -91,10 +89,7 @@ internal async Task> TryBuildTestExecu } public void AddTestHostApplicationLifetime(Func testHostApplicationLifetime) - { - Ensure.NotNull(testHostApplicationLifetime); - _testApplicationLifecycleCallbacksFactories.Add(testHostApplicationLifetime); - } + => _testApplicationLifecycleCallbacksFactories.Add(testHostApplicationLifetime ?? throw new ArgumentNullException(nameof(testHostApplicationLifetime))); internal async Task BuildTestApplicationLifecycleCallbackAsync(ServiceProvider serviceProvider) { @@ -121,16 +116,14 @@ internal async Task BuildTestApplicationLifecycl public void AddDataConsumer(Func dataConsumerFactory) { - Ensure.NotNull(dataConsumerFactory); - _dataConsumerFactories.Add(dataConsumerFactory); + _dataConsumerFactories.Add(dataConsumerFactory ?? throw new ArgumentNullException(nameof(dataConsumerFactory))); _factoryOrdering.Add(dataConsumerFactory); } public void AddDataConsumer(CompositeExtensionFactory compositeServiceFactory) where T : class, IDataConsumer { - Ensure.NotNull(compositeServiceFactory); - if (_dataConsumersCompositeServiceFactories.Contains(compositeServiceFactory)) + if (_dataConsumersCompositeServiceFactories.Contains(compositeServiceFactory ?? throw new ArgumentNullException(nameof(compositeServiceFactory)))) { throw new ArgumentException(PlatformResources.CompositeServiceFactoryInstanceAlreadyRegistered); } @@ -218,16 +211,14 @@ public void AddTestSessionLifetimeHandle(CompositeExtensionFactory composi public void AddTestSessionLifetimeHandler(Func testSessionLifetimeHandleFactory) { - Ensure.NotNull(testSessionLifetimeHandleFactory); - _testSessionLifetimeHandlerFactories.Add(testSessionLifetimeHandleFactory); + _testSessionLifetimeHandlerFactories.Add(testSessionLifetimeHandleFactory ?? throw new ArgumentNullException(nameof(testSessionLifetimeHandleFactory))); _factoryOrdering.Add(testSessionLifetimeHandleFactory); } public void AddTestSessionLifetimeHandler(CompositeExtensionFactory compositeServiceFactory) where T : class, ITestSessionLifetimeHandler { - Ensure.NotNull(compositeServiceFactory); - if (_testSessionLifetimeHandlerCompositeFactories.Contains(compositeServiceFactory)) + if (_testSessionLifetimeHandlerCompositeFactories.Contains(compositeServiceFactory ?? throw new ArgumentNullException(nameof(compositeServiceFactory)))) { throw new ArgumentException(PlatformResources.CompositeServiceFactoryInstanceAlreadyRegistered); } diff --git a/src/Platform/Microsoft.Testing.Platform/TestHostOrchestrator/TestHostOrchestratorManager.cs b/src/Platform/Microsoft.Testing.Platform/TestHostOrchestrator/TestHostOrchestratorManager.cs index e76b966f2e..5c8832e445 100644 --- a/src/Platform/Microsoft.Testing.Platform/TestHostOrchestrator/TestHostOrchestratorManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/TestHostOrchestrator/TestHostOrchestratorManager.cs @@ -15,14 +15,14 @@ internal class TestHostOrchestratorManager : ITestHostOrchestratorManager, Exten public void AddTestHostOrchestrator(Func factory) { - Ensure.NotNull(factory); + _ = factory ?? throw new ArgumentNullException(nameof(factory)); _factories ??= []; _factories.Add(factory); } void Extensions.TestHostOrchestrator.ITestHostOrchestratorManager.AddTestHostOrchestrator(Func factory) { - Ensure.NotNull(factory); + _ = factory ?? throw new ArgumentNullException(nameof(factory)); _factories ??= []; _factories.Add(sp => factory(sp)); } @@ -60,7 +60,7 @@ internal async Task BuildAsync(ServiceProvide public void AddTestHostOrchestratorApplicationLifetime(Func testHostOrchestratorApplicationLifetimeFactory) { - Ensure.NotNull(testHostOrchestratorApplicationLifetimeFactory); + _ = testHostOrchestratorApplicationLifetimeFactory ?? throw new ArgumentNullException(nameof(testHostOrchestratorApplicationLifetimeFactory)); _testHostOrchestratorApplicationLifetimeFactories.Add(testHostOrchestratorApplicationLifetimeFactory); } diff --git a/src/Platform/Microsoft.Testing.Platform/Tools/ToolsManager.cs b/src/Platform/Microsoft.Testing.Platform/Tools/ToolsManager.cs index 86b782afe8..843989fff9 100644 --- a/src/Platform/Microsoft.Testing.Platform/Tools/ToolsManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/Tools/ToolsManager.cs @@ -10,10 +10,7 @@ internal sealed class ToolsManager : IToolsManager private readonly List> _toolsFactories = []; public void AddTool(Func toolFactory) - { - Ensure.NotNull(toolFactory); - _toolsFactories.Add(toolFactory); - } + => _toolsFactories.Add(toolFactory ?? throw new ArgumentNullException(nameof(toolFactory))); internal async Task> BuildAsync(IServiceProvider serviceProvider) { diff --git a/src/Polyfills/CompilerLoweringPreserveAttribute.cs b/src/Polyfills/CompilerLoweringPreserveAttribute.cs new file mode 100644 index 0000000000..1386db411c --- /dev/null +++ b/src/Polyfills/CompilerLoweringPreserveAttribute.cs @@ -0,0 +1,13 @@ +// + +#if !NET10_0_OR_GREATER +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +[Embedded] +internal sealed class CompilerLoweringPreserveAttribute : Attribute; +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute))] +#endif diff --git a/src/Polyfills/DynamicallyAccessedMemberTypes.cs b/src/Polyfills/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 0000000000..b8e8eaa6b0 --- /dev/null +++ b/src/Polyfills/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,173 @@ +// + +#if !NETCOREAPP +using System.ComponentModel; + +using Microsoft.CodeAnalysis; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies the types of members that are dynamically accessed. +/// +/// This enumeration has a attribute that allows a +/// bitwise combination of its member values. +/// +[Flags] +[Embedded] +internal enum DynamicallyAccessedMemberTypes +{ + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all non-public constructors, including those inherited from base classes. + /// + NonPublicConstructorsWithInherited = NonPublicConstructors | 0x4000, + + /// + /// Specifies all non-public methods, including those inherited from base classes. + /// + NonPublicMethodsWithInherited = NonPublicMethods | 0x8000, + + /// + /// Specifies all non-public fields, including those inherited from base classes. + /// + NonPublicFieldsWithInherited = NonPublicFields | 0x10000, + + /// + /// Specifies all non-public nested types, including those inherited from base classes. + /// + NonPublicNestedTypesWithInherited = NonPublicNestedTypes | 0x20000, + + /// + /// Specifies all non-public properties, including those inherited from base classes. + /// + NonPublicPropertiesWithInherited = NonPublicProperties | 0x40000, + + /// + /// Specifies all non-public events, including those inherited from base classes. + /// + NonPublicEventsWithInherited = NonPublicEvents | 0x80000, + + /// + /// Specifies all public constructors, including those inherited from base classes. + /// + PublicConstructorsWithInherited = PublicConstructors | 0x100000, + + /// + /// Specifies all public nested types, including those inherited from base classes. + /// + PublicNestedTypesWithInherited = PublicNestedTypes | 0x200000, + + /// + /// Specifies all constructors, including those inherited from base classes. + /// + AllConstructors = PublicConstructorsWithInherited | NonPublicConstructorsWithInherited, + + /// + /// Specifies all methods, including those inherited from base classes. + /// + AllMethods = PublicMethods | NonPublicMethodsWithInherited, + + /// + /// Specifies all fields, including those inherited from base classes. + /// + AllFields = PublicFields | NonPublicFieldsWithInherited, + + /// + /// Specifies all nested types, including those inherited from base classes. + /// + AllNestedTypes = PublicNestedTypesWithInherited | NonPublicNestedTypesWithInherited, + + /// + /// Specifies all properties, including those inherited from base classes. + /// + AllProperties = PublicProperties | NonPublicPropertiesWithInherited, + + /// + /// Specifies all events, including those inherited from base classes. + /// + AllEvents = PublicEvents | NonPublicEventsWithInherited, + + /// + /// Specifies all members. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + All = ~None +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes))] +#endif diff --git a/src/Polyfills/DynamicallyAccessedMembersAttribute.cs b/src/Polyfills/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 0000000000..67abf55693 --- /dev/null +++ b/src/Polyfills/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,60 @@ +// + +#if !NETCOREAPP + +using System.Runtime.CompilerServices; + +using Microsoft.CodeAnalysis; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that certain members on a specified are accessed dynamically, +/// for example through . +/// +/// +/// This allows tools to understand which members are being accessed during the execution +/// of a program. +/// +/// This attribute is valid on members whose type is or . +/// +/// When this attribute is applied to a location of type , the assumption is +/// that the string represents a fully qualified type name. +/// +/// When this attribute is applied to a class, interface, or struct, the members specified +/// can be accessed dynamically on instances returned from calling +/// on instances of that class, interface, or struct. +/// +/// If the attribute is applied to a method it's treated as a special case and it implies +/// the attribute should be applied to the "this" parameter of the method. As such the attribute +/// should only be used on instance methods of types assignable to System.Type (or string, but no methods +/// will use it there). +/// +[CompilerLoweringPreserve] +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] +[Embedded] +internal sealed class DynamicallyAccessedMembersAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute))] +#endif diff --git a/src/Polyfills/PlatformAttributes.cs b/src/Polyfills/PlatformAttributes.cs new file mode 100644 index 0000000000..45a3a32801 --- /dev/null +++ b/src/Polyfills/PlatformAttributes.cs @@ -0,0 +1,200 @@ +// + +#nullable enable + +#if !NETCOREAPP +using Microsoft.CodeAnalysis; + +namespace System.Runtime.Versioning; + +/// +/// Base type for all platform-specific API attributes. +/// +[Embedded] +internal abstract class OSPlatformAttribute : Attribute +{ + private protected OSPlatformAttribute(string platformName) + { + PlatformName = platformName; + } + + public string PlatformName { get; } +} + +/// +/// Records the platform that the project targeted. +/// +[AttributeUsage(AttributeTargets.Assembly, + AllowMultiple = false, Inherited = false)] +[Embedded] +internal sealed class TargetPlatformAttribute : OSPlatformAttribute +{ + public TargetPlatformAttribute(string platformName) : base(platformName) + { + } +} + +/// +/// Records the operating system (and minimum version) that supports an API. Multiple attributes can be +/// applied to indicate support on multiple operating systems. +/// +/// +/// Callers can apply a +/// or use guards to prevent calls to APIs on unsupported operating systems. +/// +/// A given platform should only be specified once. +/// +[AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Enum | + AttributeTargets.Event | + AttributeTargets.Field | + AttributeTargets.Interface | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class SupportedOSPlatformAttribute : OSPlatformAttribute +{ + public SupportedOSPlatformAttribute(string platformName) : base(platformName) + { + } +} + +/// +/// Marks APIs that were removed in a given operating system version. +/// +/// +/// Primarily used by OS bindings to indicate APIs that are only available in +/// earlier versions. +/// +[AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Enum | + AttributeTargets.Event | + AttributeTargets.Field | + AttributeTargets.Interface | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class UnsupportedOSPlatformAttribute : OSPlatformAttribute +{ + public UnsupportedOSPlatformAttribute(string platformName) : base(platformName) + { + } + public UnsupportedOSPlatformAttribute(string platformName, string? message) : base(platformName) + { + Message = message; + } + public string? Message { get; } +} + +/// +/// Marks APIs that were obsoleted in a given operating system version. +/// +/// +/// Primarily used by OS bindings to indicate APIs that should not be used anymore. +/// +[AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Enum | + AttributeTargets.Event | + AttributeTargets.Field | + AttributeTargets.Interface | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Struct, + AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class ObsoletedOSPlatformAttribute : OSPlatformAttribute +{ + /// + /// Initializes a new instance of the class with the specified platform name. + /// + /// The name of the platform where the API was obsoleted. + public ObsoletedOSPlatformAttribute(string platformName) : base(platformName) + { + } + + /// + /// Initializes a new instance of the class with the specified platform name and message. + /// + /// The name of the platform where the API was obsoleted. + /// The message that explains the obsolescence. + public ObsoletedOSPlatformAttribute(string platformName, string? message) : base(platformName) + { + Message = message; + } + + /// + /// Gets the message that explains the obsolescence. + /// + public string? Message { get; } + + /// + /// Gets or sets the URL that provides more information about the obsolescence. + /// + public string? Url { get; set; } +} + +/// +/// Annotates a custom guard field, property or method with a supported platform name and optional version. +/// Multiple attributes can be applied to indicate guard for multiple supported platforms. +/// +/// +/// Callers can apply a to a field, property or method +/// and use that field, property or method in a conditional or assert statements in order to safely call platform specific APIs. +/// +/// The type of the field or property should be boolean, the method return type should be boolean in order to be used as platform guard. +/// +[AttributeUsage(AttributeTargets.Field | + AttributeTargets.Method | + AttributeTargets.Property, + AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class SupportedOSPlatformGuardAttribute : OSPlatformAttribute +{ + public SupportedOSPlatformGuardAttribute(string platformName) : base(platformName) + { + } +} + +/// +/// Annotates the custom guard field, property or method with an unsupported platform name and optional version. +/// Multiple attributes can be applied to indicate guard for multiple unsupported platforms. +/// +/// +/// Callers can apply a to a field, property or method +/// and use that field, property or method in a conditional or assert statements as a guard to safely call APIs unsupported on those platforms. +/// +/// The type of the field or property should be boolean, the method return type should be boolean in order to be used as platform guard. +/// +[AttributeUsage(AttributeTargets.Field | + AttributeTargets.Method | + AttributeTargets.Property, + AllowMultiple = true, Inherited = false)] +[Embedded] +internal sealed class UnsupportedOSPlatformGuardAttribute : OSPlatformAttribute +{ + public UnsupportedOSPlatformGuardAttribute(string platformName) : base(platformName) + { + } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.OSPlatformAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.TargetPlatformAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.SupportedOSPlatformAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.UnsupportedOSPlatformAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.ObsoletedOSPlatformAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.SupportedOSPlatformGuardAttribute))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute))] +#endif From 89049694d73de4c002c98179a5ddba6172b19c02 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 13:14:26 +0200 Subject: [PATCH 07/24] Platfor --- .../Helpers/FixtureMethodRunner.cs | 2 -- .../Interfaces/ITestSourceHost.cs | 6 +----- .../Services/TestSourceHost.cs | 6 +----- .../Services/ThreadOperations.cs | 2 -- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs index e85da3fae8..a58410df70 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/FixtureMethodRunner.cs @@ -176,9 +176,7 @@ internal static class FixtureMethodRunner } } -#if NETCOREAPP [SupportedOSPlatform("windows")] -#endif private static TestFailedException? RunWithTimeoutAndCancellationWithSTAThread( Func action, ExecutionContext? executionContext, CancellationTokenSource cancellationTokenSource, int timeout, MethodInfo methodInfo, string methodCanceledMessageFormat, string methodTimedOutMessageFormat) diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs index 8d2498fffc..85e13570f0 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Interfaces/ITestSourceHost.cs @@ -23,9 +23,5 @@ internal interface ITestSourceHost : IDisposable /// /// An instance of the type created in the host. /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - object? CreateInstanceForType( -#if NETCOREAPP - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - Type type, object?[]? args); + object? CreateInstanceForType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, object?[]? args); } diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs index 585a946698..0e8c0c8be6 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/TestSourceHost.cs @@ -199,11 +199,7 @@ public void SetupHost() /// /// An instance of the type created in the host. /// If a type is to be created in isolation then it needs to be a MarshalByRefObject. - public object? CreateInstanceForType( -#if NETCOREAPP - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - Type type, object?[]? args) => + public object? CreateInstanceForType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, object?[]? args) => #if NETFRAMEWORK // Honor DisableAppDomain setting if it is present in runsettings _isAppDomainCreationDisabled diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs b/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs index 028046eb4a..439aa5b379 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Services/ThreadOperations.cs @@ -45,9 +45,7 @@ private static bool ExecuteWithThreadPool(Action action, int timeout, Cancellati } #endif -#if NETCOREAPP [SupportedOSPlatform("windows")] -#endif private static bool ExecuteWithCustomThread(Action action, int timeout, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) From 9e6566c9c120792d8fde986166d1c21287fe1c83 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 13:19:50 +0200 Subject: [PATCH 08/24] Fix polyfills --- src/Polyfills/DynamicallyAccessedMemberTypes.cs | 2 ++ src/Polyfills/DynamicallyAccessedMembersAttribute.cs | 2 ++ src/Polyfills/PlatformAttributes.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Polyfills/DynamicallyAccessedMemberTypes.cs b/src/Polyfills/DynamicallyAccessedMemberTypes.cs index b8e8eaa6b0..fc4c6002ad 100644 --- a/src/Polyfills/DynamicallyAccessedMemberTypes.cs +++ b/src/Polyfills/DynamicallyAccessedMemberTypes.cs @@ -1,5 +1,7 @@ // +#pragma warning disable + #if !NETCOREAPP using System.ComponentModel; diff --git a/src/Polyfills/DynamicallyAccessedMembersAttribute.cs b/src/Polyfills/DynamicallyAccessedMembersAttribute.cs index 67abf55693..fa05a7dacf 100644 --- a/src/Polyfills/DynamicallyAccessedMembersAttribute.cs +++ b/src/Polyfills/DynamicallyAccessedMembersAttribute.cs @@ -1,5 +1,7 @@ // +#pragma warning disable + #if !NETCOREAPP using System.Runtime.CompilerServices; diff --git a/src/Polyfills/PlatformAttributes.cs b/src/Polyfills/PlatformAttributes.cs index 45a3a32801..313a47c53c 100644 --- a/src/Polyfills/PlatformAttributes.cs +++ b/src/Polyfills/PlatformAttributes.cs @@ -2,6 +2,8 @@ #nullable enable +#pragma warning disable + #if !NETCOREAPP using Microsoft.CodeAnalysis; From 49d8c800e17e2eaab5d674657fb02503c9f7171a Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 13:42:07 +0200 Subject: [PATCH 09/24] MTP --- .../MSTestAdapter.PlatformServices.csproj | 2 + .../HangDumpExtensions.cs | 4 + .../HotReloadHandler.cs | 4 + .../MSBuildExtensions.cs | 4 + .../RetryExtensions.cs | 4 + .../TrxModeHelpers.cs | 4 + .../TrxReportExtensions.cs | 4 + .../Builder/TestApplication.cs | 2 - .../CommandLine/Parser.cs | 6 +- ...vironmentVariablesConfigurationProvider.cs | 3 +- .../Helpers/Sha256Hasher.cs | 55 +++++++++++ .../Helpers/System/SystemFileSystem.cs | 29 ++++++ .../Helpers/System/SystemProcess.cs | 4 + .../Helpers/TaskExtensions.cs | 83 ++++++++++++++++ .../Logging/LoggerFactoryProxy.cs | 5 +- .../Logging/LoggingManager.cs | 5 +- .../Messages/AsynchronousMessageBus.cs | 14 ++- .../Messages/PropertyBag.Property.cs | 5 +- .../Messages/TestNodeProperties.cs | 22 ++++- .../Microsoft.Testing.Platform.csproj | 6 +- .../OutputDevice/OutputDeviceManager.cs | 5 +- .../OutputDevice/TerminalOutputDevice.cs | 7 +- .../Requests/TreeNodeFilter/TreeNodeFilter.cs | 4 +- .../DiscoveredTestMessagesSerializer.cs | 5 +- .../Serializers/HandshakeMessageSerializer.cs | 6 +- .../PerRequestServerDataConsumerService.cs | 1 - .../JsonRpc/ServerModePerCallOutputDevice.cs | 4 +- .../ServerMode/JsonRpc/TcpMessageHandler.cs | 4 + .../Services/ExecutableInfo.cs | 2 +- .../Telemetry/TelemetryManager.cs | 10 +- .../TestHostControllersManager.cs | 10 +- src/Polyfills/Ensure.cs | 12 ++- src/Polyfills/OperatingSystem.cs | 33 +++++++ .../UnconditionalSuppressMessageAttribute.cs | 94 +++++++++++++++++++ 34 files changed, 405 insertions(+), 57 deletions(-) create mode 100644 src/Polyfills/OperatingSystem.cs create mode 100644 src/Polyfills/UnconditionalSuppressMessageAttribute.cs diff --git a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj index c957aaca1e..c55d5ca1a7 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj +++ b/src/Adapter/MSTestAdapter.PlatformServices/MSTestAdapter.PlatformServices.csproj @@ -12,6 +12,8 @@ Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices $(DefineConstants);TRACE + + $(DefineConstants);EXCLUDE_OS_POLYFILL true diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs index 2e76ca62cb..d3c0435c38 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpExtensions.cs @@ -7,6 +7,10 @@ using Microsoft.Testing.Platform.IPC; using Microsoft.Testing.Platform.Services; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions; /// diff --git a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs index 28426efe70..7821028351 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HotReload/HotReloadHandler.cs @@ -14,6 +14,10 @@ [assembly: MetadataUpdateHandler(typeof(HotReloadHandler))] #endif +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions.Hosting; internal sealed class HotReloadHandler diff --git a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs index 224a5dfce8..953e1148d5 100644 --- a/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs @@ -6,6 +6,10 @@ using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.Services; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Platform.MSBuild; /// diff --git a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs index 404fce2aaf..68f4d7f923 100644 --- a/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.Retry/RetryExtensions.cs @@ -7,6 +7,10 @@ using Microsoft.Testing.Platform.Extensions; using Microsoft.Testing.Platform.TestHost; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions; /// diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs index 52d69fb7a6..753a155420 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxModeHelpers.cs @@ -3,6 +3,10 @@ using Microsoft.Testing.Platform.CommandLine; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions.TrxReport; internal static class TrxModeHelpers diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs index d0bfbffc1a..6219790214 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportExtensions.cs @@ -11,6 +11,10 @@ using Microsoft.Testing.Platform.Services; using Microsoft.Testing.Platform.TestHostControllers; +#if !NETCOREAPP +using Polyfills; +#endif + namespace Microsoft.Testing.Extensions; /// diff --git a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs index 72bee2056e..1421a35431 100644 --- a/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs +++ b/src/Platform/Microsoft.Testing.Platform/Builder/TestApplication.cs @@ -6,9 +6,7 @@ using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Hosts; using Microsoft.Testing.Platform.Logging; -#if NETCOREAPP using Microsoft.Testing.Platform.Resources; -#endif using Microsoft.Testing.Platform.Services; using Microsoft.Testing.Platform.TestHostControllers; diff --git a/src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs b/src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs index 58a06e7c63..6ce2d0ab8d 100644 --- a/src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs +++ b/src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs @@ -46,7 +46,7 @@ private static CommandLineParseResult Parse(List args, IEnvironment envi { string? currentArg = args[i]; - if (currentArg.StartsWith('@') && ResponseFileHelper.TryReadResponseFile(currentArg.Substring(1), errors, out string[]? newArguments)) + if (currentArg.StartsWith("@", StringComparison.Ordinal) && ResponseFileHelper.TryReadResponseFile(currentArg.Substring(1), errors, out string[]? newArguments)) { args.InsertRange(i + 1, newArguments); continue; @@ -121,7 +121,7 @@ static bool TryUnescape(string input, string? option, IEnvironment environment, // Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. // A single-quote cannot occur within single-quotes. - if (input.StartsWith('\'') && input.EndsWith('\'')) + if (input.StartsWith("\'", StringComparison.Ordinal) && input.EndsWith("\'", StringComparison.Ordinal)) { if (input.IndexOf('\'', 1, input.Length - 2) != -1) { @@ -141,7 +141,7 @@ static bool TryUnescape(string input, string? option, IEnvironment environment, // * The shall retain its special meaning introducing parameter expansion. [NOT SUPPORTED] // * The backslash shall retain its special meaning as an escape character only when followed by one of the following characters when considered special: // $ ` " \ - if (input.StartsWith('"') && input.EndsWith('"')) + if (input.StartsWith("\"", StringComparison.Ordinal) && input.EndsWith("\"", StringComparison.Ordinal)) { unescapedArg = input[1..^1].Replace(@"\\", "\\") .Replace(@"\""", "\"") diff --git a/src/Platform/Microsoft.Testing.Platform/Configurations/EnvironmentVariablesConfigurationProvider.cs b/src/Platform/Microsoft.Testing.Platform/Configurations/EnvironmentVariablesConfigurationProvider.cs index 3adf515856..31f068a7d3 100644 --- a/src/Platform/Microsoft.Testing.Platform/Configurations/EnvironmentVariablesConfigurationProvider.cs +++ b/src/Platform/Microsoft.Testing.Platform/Configurations/EnvironmentVariablesConfigurationProvider.cs @@ -74,7 +74,8 @@ public Task LoadAsync() public bool TryGet(string key, out string? value) { - Ensure.NotNullOrEmpty(key, nameof(key)); + _ = key ?? throw new ArgumentNullException(nameof(key)); + Ensure.NotEmpty(key); return _data.TryGetValue(key, out value); } diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/Sha256Hasher.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/Sha256Hasher.cs index 019ce3a00d..46ed29371a 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/Sha256Hasher.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/Sha256Hasher.cs @@ -15,7 +15,62 @@ internal static class Sha256Hasher public static string HashWithNormalizedCasing(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text.ToUpperInvariant()); +#if NETCOREAPP byte[] hash = SHA256.HashData(bytes); +#else + using var sha256 = SHA256.Create(); + byte[] hash = sha256.ComputeHash(bytes); +#endif + +#if NET9_0_OR_GREATER return Convert.ToHexStringLower(hash); +#else + return ToHexStringLower(hash); +#endif + } + +#if !NET9_0_OR_GREATER && NETCOREAPP + private static string ToHexStringLower(byte[] bytes) + => string.Create(bytes.Length * 2, bytes, static (chars, args) => EncodeToUtf16(args, chars)); + + private static void EncodeToUtf16(byte[] source, Span destination) + { + ApplicationStateGuard.Ensure(destination.Length >= (source.Length * 2)); + + for (int pos = 0; pos < source.Length; pos++) + { + ToCharsBuffer(source[pos], destination, pos * 2); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0) + { +#pragma warning disable IDE0004 // Remove Unnecessary Cast +#pragma warning disable IDE0047 // Remove unnecessary parentheses + uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)0x2020U; + + buffer[startingIndex + 1] = (char)(packedResult & 0xFF); + buffer[startingIndex] = (char)(packedResult >> 8); +#pragma warning restore IDE0047 // Remove unnecessary parentheses +#pragma warning restore IDE0004 // Remove Unnecessary Cast + } +#elif !NETCOREAPP + private static string ToHexStringLower(byte[] bytes) + { + char[] chars = new char[bytes.Length * 2]; + + string hexAlphabet = "0123456789abcdef"; + int charIndex = 0; + for (int i = 0; i < bytes.Length; i++) + { + byte b = bytes[i]; + chars[charIndex++] = hexAlphabet[b >> 4]; + chars[charIndex++] = hexAlphabet[b & 0xF]; + } + + return new string(chars); } +#endif } diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemFileSystem.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemFileSystem.cs index d88cbf742a..98033e087c 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemFileSystem.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemFileSystem.cs @@ -9,7 +9,22 @@ internal sealed class SystemFileSystem : IFileSystem public string CreateDirectory(string path) => Directory.CreateDirectory(path).FullName; +#if NETCOREAPP public void MoveFile(string sourceFileName, string destFileName, bool overwrite = false) => File.Move(sourceFileName, destFileName, overwrite); +#else + public void MoveFile(string sourceFileName, string destFileName, bool overwrite = false) + { + if (overwrite) + { + File.Copy(sourceFileName, destFileName, overwrite: true); + File.Delete(sourceFileName); + } + else + { + File.Move(sourceFileName, destFileName); + } + } +#endif public IFileStream NewFileStream(string path, FileMode mode) => new SystemFileStream(path, mode); @@ -17,7 +32,21 @@ internal sealed class SystemFileSystem : IFileSystem public string ReadAllText(string path) => File.ReadAllText(path); +#if NETCOREAPP public Task ReadAllTextAsync(string path) => File.ReadAllTextAsync(path); +#else + public Task ReadAllTextAsync(string path) + { + using StreamReader reader = AsyncStreamReader(path, Encoding.UTF8); + return reader.ReadToEndAsync(); + + static StreamReader AsyncStreamReader(string path, Encoding encoding) + { + var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan); + return new(stream, encoding, detectEncodingFromByteOrderMarks: true); + } + } +#endif public void CopyFile(string sourceFileName, string destFileName, bool overwrite = false) => File.Copy(sourceFileName, destFileName, overwrite); diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemProcess.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemProcess.cs index c3a680454b..3222589c3f 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemProcess.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/System/SystemProcess.cs @@ -45,7 +45,11 @@ public Task WaitForExitAsync() [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] public void Kill() +#if NETCOREAPP => _process.Kill(entireProcessTree: true); +#else + => _process.Kill(); +#endif public void Dispose() => _process.Dispose(); } diff --git a/src/Platform/Microsoft.Testing.Platform/Helpers/TaskExtensions.cs b/src/Platform/Microsoft.Testing.Platform/Helpers/TaskExtensions.cs index ae1ac1c92c..925928da13 100644 --- a/src/Platform/Microsoft.Testing.Platform/Helpers/TaskExtensions.cs +++ b/src/Platform/Microsoft.Testing.Platform/Helpers/TaskExtensions.cs @@ -6,6 +6,89 @@ namespace Microsoft.Testing.Platform.Helpers; // The idea was taken from https://github.com/dotnet/aspnetcore/blob/main/src/Shared/TaskExtensions.cs internal static class TaskExtensions { +#if !NETCOREAPP + private const uint MaxSupportedTimeout = 0xfffffffe; + + public static Task WaitAsync(this Task target, CancellationToken cancellationToken) => + target.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); + + public static Task WaitAsync( + this Task target, + TimeSpan timeout) => + target.WaitAsync(timeout, default); + + public static async Task WaitAsync( + this Task target, + TimeSpan timeout, + CancellationToken cancellationToken) + { + long milliseconds = (long)timeout.TotalMilliseconds; + if (milliseconds is < -1 or > MaxSupportedTimeout) + { + throw new ArgumentOutOfRangeException(nameof(timeout)); + } + + if (target.IsCompleted || + (!cancellationToken.CanBeCanceled && timeout == Timeout.InfiniteTimeSpan)) + { + await target.ConfigureAwait(false); + return; + } + + if (cancellationToken.IsCancellationRequested) + { + await Task.FromCanceled(cancellationToken).ConfigureAwait(false); + } + + if (timeout == TimeSpan.Zero) + { + throw new TimeoutException(); + } + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(timeout); + + var cancellationTask = new TaskCompletionSource(); +#pragma warning disable SA1312 // Variable names should begin with lower-case letter + using CancellationTokenRegistration _ = cts.Token.Register(tcs => ((TaskCompletionSource)tcs!).TrySetResult(true), cancellationTask); +#pragma warning restore SA1312 // Variable names should begin with lower-case letter + await Task.WhenAny(target, cancellationTask.Task).ConfigureAwait(false); + + if (!target.IsCompleted) + { + if (cancellationToken.IsCancellationRequested) + { + await Task.FromCanceled(cancellationToken).ConfigureAwait(false); + } + + throw new TimeoutException(); + } + + await target.ConfigureAwait(false); + } + + public static Task WaitAsync( + this Task target, + CancellationToken cancellationToken) => + target.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); + + public static Task WaitAsync( + this Task target, + TimeSpan timeout) => + target.WaitAsync(timeout, default); + + public static async Task WaitAsync( + this Task target, + TimeSpan timeout, + CancellationToken cancellationToken) + { + await ((Task)target).WaitAsync(timeout, cancellationToken).ConfigureAwait(false); +#pragma warning disable VSTHRD103 // Call async methods when in an async method + return target.Result; +#pragma warning restore VSTHRD103 // Call async methods when in an async method + } +#endif + // We observe by default because usually we're no more interested in the result of the task public static async Task WithCancellationAsync(this Task task, CancellationToken cancellationToken, bool observeException = true) { diff --git a/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryProxy.cs b/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryProxy.cs index 2af77c9caf..3cfda76bb6 100644 --- a/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryProxy.cs +++ b/src/Platform/Microsoft.Testing.Platform/Logging/LoggerFactoryProxy.cs @@ -12,8 +12,5 @@ public ILogger CreateLogger(string categoryName) => _loggerFactory is null : _loggerFactory.CreateLogger(categoryName); public void SetLoggerFactory(ILoggerFactory loggerFactory) - { - Ensure.NotNull(loggerFactory); - _loggerFactory = loggerFactory; - } + => _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } diff --git a/src/Platform/Microsoft.Testing.Platform/Logging/LoggingManager.cs b/src/Platform/Microsoft.Testing.Platform/Logging/LoggingManager.cs index 3e04e4e3cb..294ebde2f5 100644 --- a/src/Platform/Microsoft.Testing.Platform/Logging/LoggingManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/Logging/LoggingManager.cs @@ -11,10 +11,7 @@ internal sealed class LoggingManager : ILoggingManager private readonly List> _loggerProviderFullFactories = []; public void AddProvider(Func loggerProviderFactory) - { - Ensure.NotNull(loggerProviderFactory); - _loggerProviderFullFactories.Add(loggerProviderFactory); - } + => _loggerProviderFullFactories.Add(loggerProviderFactory ?? throw new ArgumentNullException(nameof(loggerProviderFactory))); internal async Task BuildAsync(IServiceProvider serviceProvider, LogLevel logLevel, IMonitor monitor) { diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/AsynchronousMessageBus.cs b/src/Platform/Microsoft.Testing.Platform/Messages/AsynchronousMessageBus.cs index 601876c206..c6246bffa4 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/AsynchronousMessageBus.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/AsynchronousMessageBus.cs @@ -149,9 +149,9 @@ public override async Task DrainDataAsync() StringBuilder builder = new(); builder.Append(CultureInfo.InvariantCulture, $"Publisher/Consumer loop detected during the drain after {stopwatch.Elapsed}.\n{builder}"); - foreach ((IAsyncConsumerDataProcessor key, long value) in consumerToDrain) + foreach (KeyValuePair kvp in consumerToDrain) { - builder.AppendLine(CultureInfo.InvariantCulture, $"Consumer '{key.DataConsumer}' payload received {value}."); + builder.AppendLine(CultureInfo.InvariantCulture, $"Consumer '{kvp.Key.DataConsumer}' payload received {kvp.Value}."); } throw new InvalidOperationException(builder.ToString()); @@ -163,8 +163,16 @@ public override async Task DrainDataAsync() { foreach (IAsyncConsumerDataProcessor asyncMultiProducerMultiConsumerDataProcessor in dataProcessors) { +#if NETCOREAPP consumerToDrain.TryAdd(asyncMultiProducerMultiConsumerDataProcessor, 0); - +#else +#pragma warning disable CA1854 // Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method + if (!consumerToDrain.ContainsKey(asyncMultiProducerMultiConsumerDataProcessor)) + { + consumerToDrain.Add(asyncMultiProducerMultiConsumerDataProcessor, 0); + } +#pragma warning restore CA1854 // Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +#endif long totalPayloadReceived = await asyncMultiProducerMultiConsumerDataProcessor.DrainDataAsync().ConfigureAwait(false); if (consumerToDrain[asyncMultiProducerMultiConsumerDataProcessor] != totalPayloadReceived) { diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.Property.cs b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.Property.cs index 18a0b9f0cc..7a1b23c628 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.Property.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/PropertyBag.Property.cs @@ -141,10 +141,7 @@ internal sealed class PropertyDebugView private readonly Property _property; public PropertyDebugView(Property property) - { - Ensure.NotNull(property); - _property = property; - } + => _property = property ?? throw new ArgumentNullException(nameof(property)); [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public IProperty[] Items => [.. _property.AsEnumerable()]; diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs index 38120f4144..f245e6ed87 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs @@ -681,7 +681,16 @@ public override string ToString() builder.Append($"{nameof(GlobalTiming)} = "); builder.Append(GlobalTiming); builder.Append($", {nameof(StepTimings)} = ["); - builder.AppendJoin(", ", StepTimings.Select(x => x.ToString())); + + for (int i = 0; i < StepTimings.Length; i++) + { + builder.Append(StepTimings[i].ToString()); + if (i < StepTimings.Length - 1) + { + builder.Append(", "); + } + } + builder.Append(']'); builder.Append(" }"); return builder.ToString(); @@ -1013,7 +1022,16 @@ public override string ToString() builder.Append($", {nameof(MethodArity)} = "); builder.Append(MethodArity); builder.Append($", {nameof(ParameterTypeFullNames)} = ["); - builder.AppendJoin(", ", ParameterTypeFullNames); + + for (int i = 0; i < ParameterTypeFullNames.Length; i++) + { + builder.Append(ParameterTypeFullNames[i]); + if (i < ParameterTypeFullNames.Length - 1) + { + builder.Append(", "); + } + } + builder.Append($"], {nameof(ReturnTypeFullName)} = "); builder.Append(ReturnTypeFullName); builder.Append(" }"); diff --git a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj index 9e4e140ae8..2beb00ee4a 100644 --- a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj +++ b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj @@ -1,4 +1,4 @@ - + $(SupportedNetFrameworks);netstandard2.0 @@ -86,6 +86,10 @@ This package provides the core platform and the .NET implementation of the proto + + + + diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs index 192b07b483..b487d11f1c 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/OutputDeviceManager.cs @@ -15,10 +15,7 @@ internal sealed class PlatformOutputDeviceManager private Func? _platformOutputDeviceFactory; public void SetPlatformOutputDevice(Func platformOutputDeviceFactory) - { - Ensure.NotNull(platformOutputDeviceFactory); - _platformOutputDeviceFactory = platformOutputDeviceFactory; - } + => _platformOutputDeviceFactory = platformOutputDeviceFactory ?? throw new ArgumentNullException(nameof(platformOutputDeviceFactory)); internal async Task BuildAsync(ServiceProvider serviceProvider, bool useServerModeOutputDevice) { diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs index 326b984aef..af33dc591c 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs @@ -195,9 +195,10 @@ string s when TerminalTestReporterCommandLineOptionsProvider.ShowOutputNoneArgum : OutputShowMode.All; private static string GetShortArchitecture(string runtimeIdentifier) - => runtimeIdentifier.Contains(Dash) - ? runtimeIdentifier.Split(Dash, 2)[1] - : runtimeIdentifier; + { + int firstIndexOfDash = runtimeIdentifier.IndexOf(Dash); + return firstIndexOfDash < 0 ? runtimeIdentifier : runtimeIdentifier.Substring(firstIndexOfDash + 1); + } public Type[] DataTypesConsumed { get; } = [ diff --git a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs index 4edad877df..736ebbbd91 100644 --- a/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs +++ b/src/Platform/Microsoft.Testing.Platform/Requests/TreeNodeFilter/TreeNodeFilter.cs @@ -482,8 +482,8 @@ private static IEnumerable TokenizeFilter(string filter) /// The URL encoded node properties. public bool MatchesFilter(string testNodeFullPath, PropertyBag filterableProperties) { - Ensure.NotNullOrEmpty(testNodeFullPath); - ArgumentGuard.Ensure(testNodeFullPath[0] == PathSeparator, nameof(testNodeFullPath), + _ = testNodeFullPath ?? throw new ArgumentNullException(nameof(testNodeFullPath)); + ArgumentGuard.Ensure(testNodeFullPath.Length > 0 && testNodeFullPath[0] == PathSeparator, nameof(testNodeFullPath), string.Format(CultureInfo.InvariantCulture, PlatformResources.TreeNodeFilterPathShouldStartWithSlashErrorMessage, PathSeparator)); int currentCharIndex = 1; diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/DiscoveredTestMessagesSerializer.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/DiscoveredTestMessagesSerializer.cs index 6039c0003e..ee7e88314e 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/DiscoveredTestMessagesSerializer.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/DiscoveredTestMessagesSerializer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.IPC.Models; namespace Microsoft.Testing.Platform.IPC.Serializers; @@ -234,8 +235,8 @@ private static TestMetadataProperty[] ReadTraitsPayload(Stream stream) } } - Ensure.NotNull(key); - Ensure.NotNull(value); + _ = key ?? throw ApplicationStateGuard.Unreachable(); + _ = value ?? throw ApplicationStateGuard.Unreachable(); traits[i] = new TestMetadataProperty(key, value); } diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/HandshakeMessageSerializer.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/HandshakeMessageSerializer.cs index 554ae3b12d..03ef54774e 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/HandshakeMessageSerializer.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/Serializers/HandshakeMessageSerializer.cs @@ -38,10 +38,10 @@ public void Serialize(object objectToSerialize, Stream stream) return; } - foreach ((byte key, string value) in handshakeMessage.Properties) + foreach (KeyValuePair kvp in handshakeMessage.Properties) { - WriteField(stream, key); - WriteField(stream, value); + WriteField(stream, kvp.Key); + WriteField(stream, kvp.Value); } } } diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/PerRequestServerDataConsumerService.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/PerRequestServerDataConsumerService.cs index cce977c3b0..dd6a5ed4a2 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/PerRequestServerDataConsumerService.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/PerRequestServerDataConsumerService.cs @@ -131,7 +131,6 @@ private async Task SendTestNodeUpdatesOnIdleAsync(Guid runId) using CancellationTokenRegistration registration = cancellationToken.Register(_testSessionEnd.SetCanceled); // When batch timer expire or we're at the end of the session we can unblock the message drain - Ensure.NotNull(_task); await Task.WhenAny(_task.Delay(TimeSpan.FromMilliseconds(TestNodeUpdateDelayInMs), cancellationToken), _testSessionEnd.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/ServerModePerCallOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/ServerModePerCallOutputDevice.cs index a2783246a4..5be8024158 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/ServerModePerCallOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/ServerModePerCallOutputDevice.cs @@ -39,12 +39,10 @@ internal async Task InitializeAsync(ServerTestHost serverTestHost) // messages to Test Explorer as well. _serverTestHost = serverTestHost; - foreach (ServerLogMessage message in _messages) + while (_messages.TryTake(out ServerLogMessage? message)) { await LogAsync(message, serverTestHost.ServiceProvider.GetTestApplicationCancellationTokenSource().CancellationToken).ConfigureAwait(false); } - - _messages.Clear(); } public string Uid => nameof(ServerModePerCallOutputDevice); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs index 6267a624ab..542dcd4245 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/TcpMessageHandler.cs @@ -124,7 +124,11 @@ public async Task WriteRequestAsync(RpcMessage message, CancellationToken cancel await _writer.WriteLineAsync("Content-Type: application/testingplatform").ConfigureAwait(false); await _writer.WriteLineAsync().ConfigureAwait(false); await _writer.WriteAsync(messageStr).ConfigureAwait(false); +#if NETCOREAPP await _writer.FlushAsync(cancellationToken).ConfigureAwait(false); +#else + await _writer.FlushAsync().ConfigureAwait(false); +#endif } public void Dispose() diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs b/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs index 17b6023e2b..80b35d1441 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ExecutableInfo.cs @@ -12,5 +12,5 @@ internal sealed class ExecutableInfo(string filePath, IEnumerable argume public string Workspace { get; } = workspace; public override string ToString() - => $"Process: {FilePath}, Arguments: {string.Join(' ', Arguments)}, Workspace: {Workspace}"; + => $"Process: {FilePath}, Arguments: {string.Join(" ", Arguments)}, Workspace: {Workspace}"; } diff --git a/src/Platform/Microsoft.Testing.Platform/Telemetry/TelemetryManager.cs b/src/Platform/Microsoft.Testing.Platform/Telemetry/TelemetryManager.cs index 5f688ea895..4b415a3eb3 100644 --- a/src/Platform/Microsoft.Testing.Platform/Telemetry/TelemetryManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/Telemetry/TelemetryManager.cs @@ -26,16 +26,10 @@ internal sealed class TelemetryManager : ITelemetryManager, IOutputDeviceDataPro public string Description => string.Empty; public void AddTelemetryCollectorProvider(Func telemetryFactory) - { - Ensure.NotNull(telemetryFactory); - _telemetryFactory = telemetryFactory; - } + => _telemetryFactory = telemetryFactory ?? throw new ArgumentNullException(nameof(telemetryFactory)); public void AddOpenTelemetryProvider(Func openTelemetryProviderFactory) - { - Ensure.NotNull(openTelemetryProviderFactory); - _openTelemetryProviderFactory = openTelemetryProviderFactory; - } + => _openTelemetryProviderFactory = openTelemetryProviderFactory ?? throw new ArgumentNullException(nameof(openTelemetryProviderFactory)); public IOpenTelemetryProvider? BuildOTelProvider(ServiceProvider serviceProvider) => _openTelemetryProviderFactory is null diff --git a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs index 7684c596b9..acaa00bfce 100644 --- a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs +++ b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/TestHostControllersManager.cs @@ -30,7 +30,7 @@ public void AddEnvironmentVariableProvider(Func(CompositeExtensionFactory compo throw new PlatformNotSupportedException(PlatformResources.TestHostControllerProcessRestartNotSupportedOnWebAssembly); } - Ensure.NotNull(compositeServiceFactory); + _ = compositeServiceFactory ?? throw new ArgumentNullException(nameof(compositeServiceFactory)); if (_environmentVariableProviderCompositeFactories.Contains(compositeServiceFactory)) { throw new ArgumentException(PlatformResources.CompositeServiceFactoryInstanceAlreadyRegistered); @@ -62,7 +62,7 @@ public void AddProcessLifetimeHandler(Func(CompositeExtensionFactory compositeS throw new PlatformNotSupportedException(PlatformResources.TestHostControllerProcessRestartNotSupportedOnWebAssembly); } - Ensure.NotNull(compositeServiceFactory); + _ = compositeServiceFactory ?? throw new ArgumentNullException(nameof(compositeServiceFactory)); if (_lifetimeHandlerCompositeFactories.Contains(compositeServiceFactory)) { throw new ArgumentException(PlatformResources.CompositeServiceFactoryInstanceAlreadyRegistered); @@ -95,7 +95,7 @@ public void AddDataConsumer(CompositeExtensionFactory compositeServiceFact throw new PlatformNotSupportedException(PlatformResources.TestHostControllerProcessRestartNotSupportedOnWebAssembly); } - Ensure.NotNull(compositeServiceFactory); + _ = compositeServiceFactory ?? throw new ArgumentNullException(nameof(compositeServiceFactory)); if (_dataConsumersCompositeServiceFactories.Contains(compositeServiceFactory)) { throw new ArgumentException(PlatformResources.CompositeServiceFactoryInstanceAlreadyRegistered); diff --git a/src/Polyfills/Ensure.cs b/src/Polyfills/Ensure.cs index ab6fa163f2..36ec6711ef 100644 --- a/src/Polyfills/Ensure.cs +++ b/src/Polyfills/Ensure.cs @@ -39,7 +39,7 @@ public static void NotEmpty(IEnumerable value, [CallerArgumentExpression(n } } - public static void NotNullOrWhiteSpace([NotNull] string value, [CallerArgumentExpression(nameof(value))] string name = "") + public static string NotNullOrWhiteSpace([NotNull] string value, [CallerArgumentExpression(nameof(value))] string name = "") { if (value is null) { @@ -49,5 +49,15 @@ public static void NotNullOrWhiteSpace([NotNull] string value, [CallerArgumentEx { throw new ArgumentException("Argument cannot be whitespace.", name); } + + return value; + } + + public static void NotEmpty(string value, [CallerArgumentExpression(nameof(value))] string name = "") + { + if (value?.Length == 0) + { + throw new ArgumentException("Argument cannot be empty.", name); + } } } diff --git a/src/Polyfills/OperatingSystem.cs b/src/Polyfills/OperatingSystem.cs new file mode 100644 index 0000000000..610f53f13f --- /dev/null +++ b/src/Polyfills/OperatingSystem.cs @@ -0,0 +1,33 @@ +// + +#if !NETCOREAPP && !EXCLUDE_OS_POLYFILL + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using Microsoft.CodeAnalysis; + +namespace Polyfills; + +internal static partial class Polyfill +{ + extension(OperatingSystem) + { + public static bool IsWasi() + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("WASI")); + + public static bool IsAndroid() + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID")); + + public static bool IsIOS() + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS")); + + public static bool IsTvOS() + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("TVOS")); + + public static bool IsBrowser() + => RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); + } +} +#endif diff --git a/src/Polyfills/UnconditionalSuppressMessageAttribute.cs b/src/Polyfills/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 0000000000..dd3eb68f54 --- /dev/null +++ b/src/Polyfills/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,94 @@ +// + +#nullable enable +#pragma warning disable + +#if !NETCOREAPP + +using Microsoft.CodeAnalysis; + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a +/// single code artifact. +/// +/// +/// is different than +/// in that it doesn't have a +/// . So it is always preserved in the compiled assembly. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] +[Embedded] +internal sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute))] +#endif From c27356162fb68cae280bf84b6bd88fe5e5c31821 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:01:00 +0200 Subject: [PATCH 10/24] MSTest TestFramework --- ...erpolatedStringHandlerArgumentAttribute.cs | 31 +++++++++++ .../InterpolatedStringHandlerAttribute.cs | 20 +++++++ .../Attributes/WinUITestTargetAttribute.cs | 2 +- .../TestFramework.Extensions/PrivateObject.cs | 55 +++++++++---------- .../TestFramework.Extensions/PrivateType.cs | 38 ++++++------- .../RuntimeTypeHelper.cs | 2 - .../TestFramework.Extensions.csproj | 6 +- .../Assertions/Assert.AreEqual.cs | 4 +- .../Assertions/Assert.ThrowsException.cs | 16 +++--- src/TestFramework/TestFramework/Logger.cs | 4 +- .../TestFramework/TestFramework.csproj | 2 +- 11 files changed, 114 insertions(+), 66 deletions(-) create mode 100644 src/Polyfills/InterpolatedStringHandlerArgumentAttribute.cs create mode 100644 src/Polyfills/InterpolatedStringHandlerAttribute.cs diff --git a/src/Polyfills/InterpolatedStringHandlerArgumentAttribute.cs b/src/Polyfills/InterpolatedStringHandlerArgumentAttribute.cs new file mode 100644 index 0000000000..69357154f8 --- /dev/null +++ b/src/Polyfills/InterpolatedStringHandlerArgumentAttribute.cs @@ -0,0 +1,31 @@ +// + +#pragma warning disable + +#if !NETCOREAPP +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// Indicates which arguments to a method involving an interpolated string handler should be passed to that handler. +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] +[Embedded] +internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute +{ + /// Initializes a new instance of the class. + /// The name of the argument that should be passed to the handler. + /// The empty string may be used as the name of the receiver in an instance method. + public InterpolatedStringHandlerArgumentAttribute(string argument) => Arguments = [argument]; + + /// Initializes a new instance of the class. + /// The names of the arguments that should be passed to the handler. + /// The empty string may be used as the name of the receiver in an instance method. + public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) => Arguments = arguments; + + /// Gets the names of the arguments that should be passed to the handler. + /// The empty string may be used as the name of the receiver in an instance method. + public string[] Arguments { get; } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute))] +#endif diff --git a/src/Polyfills/InterpolatedStringHandlerAttribute.cs b/src/Polyfills/InterpolatedStringHandlerAttribute.cs new file mode 100644 index 0000000000..cc992adf5c --- /dev/null +++ b/src/Polyfills/InterpolatedStringHandlerAttribute.cs @@ -0,0 +1,20 @@ +// + +#pragma warning disable + +#if !NETCOREAPP +using Microsoft.CodeAnalysis; + +namespace System.Runtime.CompilerServices; + +/// Indicates the attributed type is to be used as an interpolated string handler. +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +[Embedded] +internal sealed class InterpolatedStringHandlerAttribute : Attribute +{ + /// Initializes the . + public InterpolatedStringHandlerAttribute() { } +} +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute))] +#endif diff --git a/src/TestFramework/TestFramework.Extensions/Attributes/WinUITestTargetAttribute.cs b/src/TestFramework/TestFramework.Extensions/Attributes/WinUITestTargetAttribute.cs index a0b932045c..b8a5d28456 100644 --- a/src/TestFramework/TestFramework.Extensions/Attributes/WinUITestTargetAttribute.cs +++ b/src/TestFramework/TestFramework.Extensions/Attributes/WinUITestTargetAttribute.cs @@ -19,7 +19,7 @@ public class WinUITestTargetAttribute : Attribute /// public WinUITestTargetAttribute(Type applicationType) { - Ensure.NotNull(applicationType); + _ = applicationType ?? throw new ArgumentNullException(nameof(applicationType)); if (!typeof(UI.Xaml.Application).IsAssignableFrom(applicationType)) { diff --git a/src/TestFramework/TestFramework.Extensions/PrivateObject.cs b/src/TestFramework/TestFramework.Extensions/PrivateObject.cs index 2a68edfd1d..31a6905155 100644 --- a/src/TestFramework/TestFramework.Extensions/PrivateObject.cs +++ b/src/TestFramework/TestFramework.Extensions/PrivateObject.cs @@ -32,7 +32,7 @@ public class PrivateObject [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] public PrivateObject(object obj, string memberToAccess) { - Ensure.NotNull(obj); + _ = obj ?? throw new ArgumentNullException(nameof(obj)); ValidateAccessString(memberToAccess); var temp = obj as PrivateObject; @@ -75,8 +75,8 @@ public PrivateObject(string assemblyName, string typeName, params object?[]? arg public PrivateObject(string assemblyName, string typeName, Type[]? parameterTypes, object?[]? args) : this(Type.GetType(string.Format(CultureInfo.InvariantCulture, "{0}, {1}", typeName, assemblyName), false), parameterTypes, args) { - Ensure.NotNull(assemblyName); - Ensure.NotNull(typeName); + _ = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName)); + _ = typeName ?? throw new ArgumentNullException(nameof(typeName)); } /// @@ -87,7 +87,7 @@ public PrivateObject(string assemblyName, string typeName, Type[]? parameterType /// Arguments to pass to the constructor. public PrivateObject(Type type, params object?[]? args) : this(type, null, args) => - Ensure.NotNull(type); + _ = type ?? throw new ArgumentNullException(nameof(type)); /// /// Initializes a new instance of the class that wraps the @@ -98,7 +98,7 @@ public PrivateObject(Type type, params object?[]? args) /// Arguments to pass to the constructor. public PrivateObject(Type type, Type[]? parameterTypes, object?[]? args) { - Ensure.NotNull(type); + _ = type ?? throw new ArgumentNullException(nameof(type)); object? o; if (parameterTypes != null) { @@ -124,7 +124,6 @@ public PrivateObject(Type type, Type[]? parameterTypes, object?[]? args) o = Activator.CreateInstance(type, ConstructorFlags, null, args, null); } - Ensure.NotNull(o); _target = o; RealType = o.GetType(); } @@ -137,8 +136,7 @@ public PrivateObject(Type type, Type[]? parameterTypes, object?[]? args) [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] public PrivateObject(object obj) { - Ensure.NotNull(obj); - _target = obj; + _target = obj ?? throw new ArgumentNullException(nameof(obj)); RealType = obj.GetType(); } @@ -151,7 +149,7 @@ public PrivateObject(object obj) [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "We don't know anything about the object other than that it's an object, so 'obj' seems reasonable")] public PrivateObject(object obj, PrivateType type) { - Ensure.NotNull(type); + _ = type ?? throw new ArgumentNullException(nameof(type)); _target = obj; RealType = type.ReferencedType; } @@ -166,8 +164,7 @@ public object Target get => _target; set { - Ensure.NotNull(value); - _target = value; + _target = value ?? throw new ArgumentNullException(nameof(value)); RealType = value.GetType(); } } @@ -230,7 +227,7 @@ public override bool Equals(object? obj) /// Result of method call. public object? Invoke(string name, params object?[]? args) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return Invoke(name, null, args, CultureInfo.InvariantCulture); } @@ -324,7 +321,7 @@ public override bool Equals(object? obj) /// Result of method call. public object? Invoke(string name, BindingFlags bindingFlags, Type[]? parameterTypes, object?[]? args, CultureInfo? culture, Type[]? typeArguments) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes == null) { return InvokeHelper(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); @@ -385,7 +382,7 @@ public override bool Equals(object? obj) /// An array of elements. public object GetArrayElement(string name, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetArrayElement(name, BindToEveryThing, indices); } @@ -397,7 +394,7 @@ public object GetArrayElement(string name, params int[] indices) /// the indices of array. public void SetArrayElement(string name, object value, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetArrayElement(name, BindToEveryThing, value, indices); } @@ -410,7 +407,7 @@ public void SetArrayElement(string name, object value, params int[] indices) /// An array of elements. public object GetArrayElement(string name, BindingFlags bindingFlags, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); var arr = (Array?)InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); DebugEx.Assert(arr is not null, "arr should not be null"); return arr.GetValue(indices); @@ -425,7 +422,7 @@ public object GetArrayElement(string name, BindingFlags bindingFlags, params int /// the indices of array. public void SetArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); var arr = (Array?)InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); DebugEx.Assert(arr is not null, "arr should not be null"); arr.SetValue(value, indices); @@ -438,7 +435,7 @@ public void SetArrayElement(string name, BindingFlags bindingFlags, object value /// The field. public object? GetField(string name) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetField(name, BindToEveryThing); } @@ -449,7 +446,7 @@ public void SetArrayElement(string name, BindingFlags bindingFlags, object value /// value to set. public void SetField(string name, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetField(name, BindToEveryThing, value); } @@ -461,7 +458,7 @@ public void SetField(string name, object value) /// The field. public object? GetField(string name, BindingFlags bindingFlags) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return InvokeHelper(name, BindingFlags.GetField | bindingFlags, null, CultureInfo.InvariantCulture); } @@ -473,7 +470,7 @@ public void SetField(string name, object value) /// value to set. public void SetField(string name, BindingFlags bindingFlags, object? value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); InvokeHelper(name, BindingFlags.SetField | bindingFlags, [value], CultureInfo.InvariantCulture); } @@ -484,7 +481,7 @@ public void SetField(string name, BindingFlags bindingFlags, object? value) /// The field or property. public object? GetFieldOrProperty(string name) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetFieldOrProperty(name, BindToEveryThing); } @@ -495,7 +492,7 @@ public void SetField(string name, BindingFlags bindingFlags, object? value) /// value to set. public void SetFieldOrProperty(string name, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetFieldOrProperty(name, BindToEveryThing, value); } @@ -507,7 +504,7 @@ public void SetFieldOrProperty(string name, object value) /// The field or property. public object? GetFieldOrProperty(string name, BindingFlags bindingFlags) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return InvokeHelper(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); } @@ -519,7 +516,7 @@ public void SetFieldOrProperty(string name, object value) /// value to set. public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object? value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); InvokeHelper(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags, [value], CultureInfo.InvariantCulture); } @@ -576,7 +573,7 @@ public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object? v /// The property. public object? GetProperty(string name, BindingFlags bindingFlags, Type[]? parameterTypes, object?[]? args) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes == null) { return InvokeHelper(name, bindingFlags | BindingFlags.GetProperty, args, null); @@ -606,7 +603,7 @@ public void SetFieldOrProperty(string name, BindingFlags bindingFlags, object? v /// Arguments to pass to the member to invoke. public void SetProperty(string name, BindingFlags bindingFlags, object? value, Type[]? parameterTypes, object?[]? args) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes == null) { @@ -631,7 +628,7 @@ public void SetProperty(string name, BindingFlags bindingFlags, object? value, T /// access string. private static void ValidateAccessString(string access) { - Ensure.NotNull(access); + _ = access ?? throw new ArgumentNullException(nameof(access)); if (access.Length == 0) { throw new ArgumentException(FrameworkMessages.AccessStringInvalidSyntax); @@ -657,7 +654,7 @@ private static void ValidateAccessString(string access) /// Result of the invocation. private object? InvokeHelper(string name, BindingFlags bindingFlags, object?[]? args, CultureInfo? culture) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); DebugEx.Assert(_target != null, "Internal Error: Null reference is returned for internal object"); // Invoke the actual Method diff --git a/src/TestFramework/TestFramework.Extensions/PrivateType.cs b/src/TestFramework/TestFramework.Extensions/PrivateType.cs index 5fbbea51ff..7a60c16684 100644 --- a/src/TestFramework/TestFramework.Extensions/PrivateType.cs +++ b/src/TestFramework/TestFramework.Extensions/PrivateType.cs @@ -24,8 +24,8 @@ public class PrivateType /// fully qualified name of the. public PrivateType(string assemblyName, string typeName) { - Ensure.NotNull(typeName); - Ensure.NotNull(assemblyName); + _ = typeName ?? throw new ArgumentNullException(nameof(typeName)); + _ = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName)); var asm = Assembly.Load(assemblyName); ReferencedType = asm.GetType(typeName, true); @@ -37,7 +37,7 @@ public PrivateType(string assemblyName, string typeName) /// /// The wrapped Type to create. public PrivateType(Type type) => - ReferencedType = Ensure.NotNull(type); + ReferencedType = type ?? throw new ArgumentNullException(nameof(type)); /// /// Gets the referenced type. @@ -142,7 +142,7 @@ public PrivateType(Type type) => /// Result of invocation. public object InvokeStatic(string name, BindingFlags bindingFlags, Type[]? parameterTypes, object?[]? args, CultureInfo? culture, Type[]? typeArguments) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes == null) { return InvokeHelperStatic(name, bindingFlags | BindingFlags.InvokeMethod, args, culture); @@ -185,7 +185,7 @@ public object InvokeStatic(string name, BindingFlags bindingFlags, Type[]? param /// element at the specified location. public object GetStaticArrayElement(string name, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetStaticArrayElement(name, BindToEveryThing, indices); } @@ -200,7 +200,7 @@ public object GetStaticArrayElement(string name, params int[] indices) /// public void SetStaticArrayElement(string name, object value, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetStaticArrayElement(name, BindToEveryThing, value, indices); } @@ -216,7 +216,7 @@ public void SetStaticArrayElement(string name, object value, params int[] indice /// element at the specified location. public object GetStaticArrayElement(string name, BindingFlags bindingFlags, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); var arr = (Array)InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | bindingFlags, null, CultureInfo.InvariantCulture); return arr.GetValue(indices); } @@ -233,7 +233,7 @@ public object GetStaticArrayElement(string name, BindingFlags bindingFlags, para /// public void SetStaticArrayElement(string name, BindingFlags bindingFlags, object value, params int[] indices) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); var arr = (Array)InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); arr.SetValue(value, indices); } @@ -245,7 +245,7 @@ public void SetStaticArrayElement(string name, BindingFlags bindingFlags, object /// The static field. public object GetStaticField(string name) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetStaticField(name, BindToEveryThing); } @@ -256,7 +256,7 @@ public object GetStaticField(string name) /// Argument to the invocation. public void SetStaticField(string name, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetStaticField(name, BindToEveryThing, value); } @@ -268,7 +268,7 @@ public void SetStaticField(string name, object value) /// The static field. public object GetStaticField(string name, BindingFlags bindingFlags) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); } @@ -280,7 +280,7 @@ public object GetStaticField(string name, BindingFlags bindingFlags) /// Argument to the invocation. public void SetStaticField(string name, BindingFlags bindingFlags, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); InvokeHelperStatic(name, BindingFlags.SetField | bindingFlags | BindingFlags.Static, [value], CultureInfo.InvariantCulture); } @@ -291,7 +291,7 @@ public void SetStaticField(string name, BindingFlags bindingFlags, object value) /// The static field or property. public object GetStaticFieldOrProperty(string name) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return GetStaticFieldOrProperty(name, BindToEveryThing); } @@ -302,7 +302,7 @@ public object GetStaticFieldOrProperty(string name) /// Value to be set to field or property. public void SetStaticFieldOrProperty(string name, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); SetStaticFieldOrProperty(name, BindToEveryThing, value); } @@ -314,7 +314,7 @@ public void SetStaticFieldOrProperty(string name, object value) /// The static field or property. public object GetStaticFieldOrProperty(string name, BindingFlags bindingFlags) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); return InvokeHelperStatic(name, BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Static | bindingFlags, null, CultureInfo.InvariantCulture); } @@ -326,7 +326,7 @@ public object GetStaticFieldOrProperty(string name, BindingFlags bindingFlags) /// Value to be set to field or property. public void SetStaticFieldOrProperty(string name, BindingFlags bindingFlags, object value) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); InvokeHelperStatic(name, BindingFlags.SetField | BindingFlags.SetProperty | bindingFlags | BindingFlags.Static, [value], CultureInfo.InvariantCulture); } @@ -374,7 +374,7 @@ public void SetStaticFieldOrProperty(string name, BindingFlags bindingFlags, obj /// The static property. public object GetStaticProperty(string name, BindingFlags bindingFlags, Type[]? parameterTypes, object?[]? args) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes == null) { return InvokeHelperStatic(name, bindingFlags | BindingFlags.GetProperty, args, null); @@ -404,7 +404,7 @@ public object GetStaticProperty(string name, BindingFlags bindingFlags, Type[]? /// Arguments to pass to the member to invoke. public void SetStaticProperty(string name, BindingFlags bindingFlags, object value, Type[]? parameterTypes, object?[]? args) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); if (parameterTypes != null) { @@ -431,7 +431,7 @@ public void SetStaticProperty(string name, BindingFlags bindingFlags, object val /// Result of invocation. private object InvokeHelperStatic(string name, BindingFlags bindingFlags, object?[]? args, CultureInfo? culture) { - Ensure.NotNull(name); + _ = name ?? throw new ArgumentNullException(nameof(name)); try { return ReferencedType.InvokeMember(name, bindingFlags | BindToEveryThing | BindingFlags.Static, null, null, args, culture); diff --git a/src/TestFramework/TestFramework.Extensions/RuntimeTypeHelper.cs b/src/TestFramework/TestFramework.Extensions/RuntimeTypeHelper.cs index c375680d53..09faf84dc6 100644 --- a/src/TestFramework/TestFramework.Extensions/RuntimeTypeHelper.cs +++ b/src/TestFramework/TestFramework.Extensions/RuntimeTypeHelper.cs @@ -113,8 +113,6 @@ internal static int GetHierarchyDepth(Type t) [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")] internal static MethodBase? SelectMethod(MethodBase[] match, Type[] types) { - Ensure.NotNull(match); - int i; int j; diff --git a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj index 7bd66d65a3..8e956ba5ad 100644 --- a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj +++ b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj @@ -56,11 +56,13 @@ - - + + + + diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs index 5cd7fe9939..ba9fde5ab9 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs @@ -208,7 +208,7 @@ public AssertNonGenericAreEqualInterpolatedStringHandler(int literalLength, int public AssertNonGenericAreEqualInterpolatedStringHandler(int literalLength, int formattedCount, string? expected, string? actual, bool ignoreCase, CultureInfo culture, out bool shouldAppend) { - Ensure.NotNull(culture); + _ = culture ?? throw new ArgumentNullException(nameof(culture)); shouldAppend = AreEqualFailing(expected, actual, ignoreCase, culture); if (shouldAppend) { @@ -314,7 +314,7 @@ public AssertNonGenericAreNotEqualInterpolatedStringHandler(int literalLength, i public AssertNonGenericAreNotEqualInterpolatedStringHandler(int literalLength, int formattedCount, string? notExpected, string? actual, bool ignoreCase, CultureInfo culture, out bool shouldAppend) { - Ensure.NotNull(culture); + _ = culture ?? throw new ArgumentNullException(nameof(culture)); shouldAppend = AreNotEqualFailing(notExpected, actual, ignoreCase, culture); if (shouldAppend) { diff --git a/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs b/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs index b5478a28a6..417f12afd4 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs @@ -321,8 +321,8 @@ public static TException ThrowsExactly(Func action, [Interp private static TException ThrowsException(Action action, bool isStrictType, string? message, string actionExpression, [CallerMemberName] string assertMethodName = "") where TException : Exception { - Ensure.NotNull(action); - Ensure.NotNull(message); + _ = action ?? throw new ArgumentNullException(nameof(action)); + _ = message ?? throw new ArgumentNullException(nameof(message)); ThrowsExceptionState state = IsThrowsFailing(action, isStrictType, assertMethodName); if (state.FailAction is not null) @@ -341,8 +341,8 @@ private static TException ThrowsException(Action action, bool isStri private static TException ThrowsException(Action action, bool isStrictType, Func messageBuilder, string actionExpression, [CallerMemberName] string assertMethodName = "") where TException : Exception { - Ensure.NotNull(action); - Ensure.NotNull(messageBuilder); + _ = action ?? throw new ArgumentNullException(nameof(action)); + _ = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder)); ThrowsExceptionState state = IsThrowsFailing(action, isStrictType, assertMethodName); if (state.FailAction is not null) @@ -477,8 +477,8 @@ public static Task ThrowsExactlyAsync(Func action, private static async Task ThrowsExceptionAsync(Func action, bool isStrictType, string? message, string actionExpression, [CallerMemberName] string assertMethodName = "") where TException : Exception { - Ensure.NotNull(action); - Ensure.NotNull(message); + _ = action ?? throw new ArgumentNullException(nameof(action)); + _ = message ?? throw new ArgumentNullException(nameof(message)); ThrowsExceptionState state = await IsThrowsAsyncFailingAsync(action, isStrictType, assertMethodName).ConfigureAwait(false); if (state.FailAction is not null) @@ -497,8 +497,8 @@ private static async Task ThrowsExceptionAsync(Func ThrowsExceptionAsync(Func action, bool isStrictType, Func messageBuilder, string actionExpression, [CallerMemberName] string assertMethodName = "") where TException : Exception { - Ensure.NotNull(action); - Ensure.NotNull(messageBuilder); + _ = action ?? throw new ArgumentNullException(nameof(action)); + _ = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder)); ThrowsExceptionState state = await IsThrowsAsyncFailingAsync(action, isStrictType, assertMethodName).ConfigureAwait(false); if (state.FailAction is not null) diff --git a/src/TestFramework/TestFramework/Logger.cs b/src/TestFramework/TestFramework/Logger.cs index 8d2937baa5..9402932af6 100644 --- a/src/TestFramework/TestFramework/Logger.cs +++ b/src/TestFramework/TestFramework/Logger.cs @@ -33,8 +33,8 @@ public static void LogMessage(string format, params object?[] args) return; } - Ensure.NotNull(format); - Ensure.NotNull(args); + _ = format ?? throw new ArgumentNullException(nameof(format)); + _ = args ?? throw new ArgumentNullException(nameof(args)); string message = args.Length == 0 ? format diff --git a/src/TestFramework/TestFramework/TestFramework.csproj b/src/TestFramework/TestFramework/TestFramework.csproj index 3bb3e7339b..3d66b80c13 100644 --- a/src/TestFramework/TestFramework/TestFramework.csproj +++ b/src/TestFramework/TestFramework/TestFramework.csproj @@ -28,7 +28,7 @@ - + From 0f640a0873c35da98ef182c68d5c984b0d2a9700 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:12:06 +0200 Subject: [PATCH 11/24] Remove Polyfill from test projects --- Directory.Packages.props | 1 - .../MSTest.Acceptance.IntegrationTests.csproj | 5 ++++- .../MSTest.IntegrationTests/MSTest.IntegrationTests.csproj | 2 +- ...osoft.Testing.Platform.Acceptance.IntegrationTests.csproj | 5 ++++- .../MSTest.Analyzers.UnitTests.csproj | 4 +++- .../MSTestAdapter.PlatformServices.UnitTests.csproj | 5 ++++- .../MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj | 5 ++++- .../Microsoft.Testing.Extensions.UnitTests.csproj | 5 ++++- ...icrosoft.Testing.Extensions.VSTestBridge.UnitTests.csproj | 5 ++++- .../Microsoft.Testing.Platform.UnitTests.csproj | 5 ++++- .../TestFramework.UnitTests/TestFramework.UnitTests.csproj | 5 ++++- test/Utilities/Automation.CLI/Automation.CLI.csproj | 5 ++++- .../Microsoft.Testing.TestInfrastructure.csproj | 5 ++++- .../TestFramework.ForTestingMSTest.csproj | 2 +- 14 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 16c4bec7c0..179a218047 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -59,7 +59,6 @@ - diff --git a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj index add7413e38..6810147f4b 100644 --- a/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj +++ b/test/IntegrationTests/MSTest.Acceptance.IntegrationTests/MSTest.Acceptance.IntegrationTests.csproj @@ -25,10 +25,13 @@ - + + + + diff --git a/test/IntegrationTests/MSTest.IntegrationTests/MSTest.IntegrationTests.csproj b/test/IntegrationTests/MSTest.IntegrationTests/MSTest.IntegrationTests.csproj index 5347699911..270aaf88e2 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/MSTest.IntegrationTests.csproj +++ b/test/IntegrationTests/MSTest.IntegrationTests/MSTest.IntegrationTests.csproj @@ -28,7 +28,7 @@ - + diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj index a9b216db53..c6846768aa 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj @@ -18,10 +18,13 @@ - + + + + diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj index 62237a43c6..e5b7a70cb9 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/MSTest.Analyzers.UnitTests.csproj @@ -31,8 +31,10 @@ + - + + diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj index 5cf1911159..f2fa7a0cdd 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/MSTestAdapter.PlatformServices.UnitTests.csproj @@ -32,7 +32,10 @@ - + + + + diff --git a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj index 6ca3cab48e..4cd17ec7e7 100644 --- a/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj +++ b/test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj @@ -31,7 +31,10 @@ - + + + + diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj index 62289d70b7..3ed3a5e269 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj +++ b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj @@ -34,7 +34,10 @@ - + + + + diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests.csproj b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests.csproj index 65ac530917..26698caab5 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests.csproj +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests.csproj @@ -17,7 +17,10 @@ - + + + + diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj index 954782fee0..abb6845845 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Microsoft.Testing.Platform.UnitTests.csproj @@ -34,7 +34,10 @@ - + + + + diff --git a/test/UnitTests/TestFramework.UnitTests/TestFramework.UnitTests.csproj b/test/UnitTests/TestFramework.UnitTests/TestFramework.UnitTests.csproj index 6b9b4137dc..82d0d6a4f0 100644 --- a/test/UnitTests/TestFramework.UnitTests/TestFramework.UnitTests.csproj +++ b/test/UnitTests/TestFramework.UnitTests/TestFramework.UnitTests.csproj @@ -24,7 +24,10 @@ - + + + + diff --git a/test/Utilities/Automation.CLI/Automation.CLI.csproj b/test/Utilities/Automation.CLI/Automation.CLI.csproj index cbb4fe349f..5e45cc985f 100644 --- a/test/Utilities/Automation.CLI/Automation.CLI.csproj +++ b/test/Utilities/Automation.CLI/Automation.CLI.csproj @@ -7,7 +7,10 @@ - + + + + diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj b/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj index 73bb77b1b6..e83f1d866d 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/Microsoft.Testing.TestInfrastructure.csproj @@ -9,7 +9,10 @@ - + + + + diff --git a/test/Utilities/TestFramework.ForTestingMSTest/TestFramework.ForTestingMSTest.csproj b/test/Utilities/TestFramework.ForTestingMSTest/TestFramework.ForTestingMSTest.csproj index 23bdfc6d46..34e87a0e16 100644 --- a/test/Utilities/TestFramework.ForTestingMSTest/TestFramework.ForTestingMSTest.csproj +++ b/test/Utilities/TestFramework.ForTestingMSTest/TestFramework.ForTestingMSTest.csproj @@ -16,7 +16,7 @@ - + From 7fafea906cb6829398f161ad170f69c781dce688 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:13:38 +0200 Subject: [PATCH 12/24] Cleanup Directory.Build.props --- Directory.Build.props | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index c52560a235..fe3901dfe7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -75,17 +75,6 @@ Microsoft.Testing.Platform - - - true - - true - true - true - - From 12f72fbdf52b76fcbe46fd431adc66996a74ded7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:17:42 +0200 Subject: [PATCH 13/24] Progress --- src/Polyfills/Index.cs | 2 +- src/Polyfills/Range.cs | 2 +- src/TestFramework/TestFramework/TestFramework.csproj | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Polyfills/Index.cs b/src/Polyfills/Index.cs index 04a4f1e06c..510f94cc42 100644 --- a/src/Polyfills/Index.cs +++ b/src/Polyfills/Index.cs @@ -4,7 +4,7 @@ #nullable enable -#if !NETCOREAPP +#if !NETCOREAPP && !EXCLUDE_RANGE_INDEX_POLYFILL namespace System; /// Represent a type can be used to index a collection either from the start or the end. diff --git a/src/Polyfills/Range.cs b/src/Polyfills/Range.cs index 71d0c8fae5..60c9341d4f 100644 --- a/src/Polyfills/Range.cs +++ b/src/Polyfills/Range.cs @@ -2,7 +2,7 @@ #pragma warning disable -#if !NETCOREAPP +#if !NETCOREAPP && !EXCLUDE_RANGE_INDEX_POLYFILL using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; diff --git a/src/TestFramework/TestFramework/TestFramework.csproj b/src/TestFramework/TestFramework/TestFramework.csproj index 3d66b80c13..81b796220e 100644 --- a/src/TestFramework/TestFramework/TestFramework.csproj +++ b/src/TestFramework/TestFramework/TestFramework.csproj @@ -13,6 +13,7 @@ NotPublishable false + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL From 031dcdb8f3009ff605c6589107151d394ee19ef9 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:27:49 +0200 Subject: [PATCH 14/24] More fix --- src/Polyfills/Index.cs | 2 +- src/Polyfills/Range.cs | 2 +- src/Polyfills/StackTraceHiddenAttribute.cs | 13 +++++++++++++ .../Attributes/DataSource/DynamicDataOperations.cs | 5 +++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/Polyfills/StackTraceHiddenAttribute.cs diff --git a/src/Polyfills/Index.cs b/src/Polyfills/Index.cs index 510f94cc42..d70d03a106 100644 --- a/src/Polyfills/Index.cs +++ b/src/Polyfills/Index.cs @@ -148,6 +148,6 @@ private string ToStringFromEnd() return '^' + Value.ToString(); } } -#else +#elif NETCOREAPP [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Index))] #endif diff --git a/src/Polyfills/Range.cs b/src/Polyfills/Range.cs index 60c9341d4f..4770a327d1 100644 --- a/src/Polyfills/Range.cs +++ b/src/Polyfills/Range.cs @@ -94,6 +94,6 @@ private static void ThrowArgumentOutOfRangeException() throw new ArgumentOutOfRangeException("length"); } } -#else +#elif NETCOREAPP [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Range))] #endif diff --git a/src/Polyfills/StackTraceHiddenAttribute.cs b/src/Polyfills/StackTraceHiddenAttribute.cs new file mode 100644 index 0000000000..1885ea3a15 --- /dev/null +++ b/src/Polyfills/StackTraceHiddenAttribute.cs @@ -0,0 +1,13 @@ +// + +#if !NETCOREAPP +using Microsoft.CodeAnalysis; + +namespace System.Diagnostics; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Struct, Inherited = false)] +[Embedded] +internal sealed class StackTraceHiddenAttribute : Attribute; +#else +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.StackTraceHiddenAttribute))] +#endif diff --git a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs index 4ce72a026f..7521a1dd01 100644 --- a/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs +++ b/src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs @@ -97,9 +97,14 @@ public static IEnumerable GetData(Type? dynamicDataDeclaringType, Dyna ParameterInfo[] methodParameters = method.GetParameters(); ParameterInfo? lastParameter = methodParameters.Length > 0 ? methodParameters[methodParameters.Length - 1] : null; + +#if NET9_0_OR_GREATER if (lastParameter is not null && (lastParameter.GetCustomAttribute() is not null || lastParameter.GetCustomAttribute() is not null)) +#else + if (lastParameter is not null && lastParameter.GetCustomAttribute() is not null) +#endif { throw new NotSupportedException( string.Format( From 0388c64f6c9392233a0c2430f664ffde97de224a Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:42:51 +0200 Subject: [PATCH 15/24] Progress --- src/Polyfills/StackTraceHiddenAttribute.cs | 2 ++ .../TestFramework/Assertions/Assert.Contains.cs | 8 ++++++++ src/TestFramework/TestFramework/Assertions/Assert.That.cs | 8 ++++---- .../TestFramework/Assertions/CollectionAssert.cs | 4 ++-- .../TestMethod/SingleThreadedSTASynchronizationContext.cs | 2 +- src/TestFramework/TestFramework/TestFramework.csproj | 2 +- .../Helpers/CountDownEventTests.cs | 4 ++++ .../Microsoft.Testing.TestInfrastructure/ProjectSystem.cs | 8 ++++++-- 8 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/Polyfills/StackTraceHiddenAttribute.cs b/src/Polyfills/StackTraceHiddenAttribute.cs index 1885ea3a15..318aec7bb8 100644 --- a/src/Polyfills/StackTraceHiddenAttribute.cs +++ b/src/Polyfills/StackTraceHiddenAttribute.cs @@ -1,5 +1,7 @@ // +#pragma warning disable + #if !NETCOREAPP using Microsoft.CodeAnalysis; diff --git a/src/TestFramework/TestFramework/Assertions/Assert.Contains.cs b/src/TestFramework/TestFramework/Assertions/Assert.Contains.cs index 19fb68e0a6..846da0a78b 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.Contains.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.Contains.cs @@ -489,7 +489,11 @@ public static void Contains(string substring, string value, StringComparison com CheckParameterNotNull(value, "Assert.Contains", "value"); CheckParameterNotNull(substring, "Assert.Contains", "substring"); +#if NETCOREAPP if (!value.Contains(substring, comparisonType)) +#else + if (value.IndexOf(substring, comparisonType) < 0) +#endif { string userMessage = BuildUserMessageForSubstringExpressionAndValueExpression(message, substringExpression, valueExpression); string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.ContainsFail, value, substring, userMessage); @@ -728,7 +732,11 @@ public static void DoesNotContain(string substring, string value, StringComparis CheckParameterNotNull(value, "Assert.DoesNotContain", "value"); CheckParameterNotNull(substring, "Assert.DoesNotContain", "substring"); +#if NETCOREAPP if (value.Contains(substring, comparisonType)) +#else + if (value.IndexOf(substring, comparisonType) >= 0) +#endif { string userMessage = BuildUserMessageForSubstringExpressionAndValueExpression(message, substringExpression, valueExpression); string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.DoesNotContainFail, value, substring, userMessage); diff --git a/src/TestFramework/TestFramework/Assertions/Assert.That.cs b/src/TestFramework/TestFramework/Assertions/Assert.That.cs index e13c803967..cc1338f784 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.That.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.That.cs @@ -72,12 +72,12 @@ private static string ExtractDetails(Expression expr) IOrderedEnumerable> sortedDetails = details.OrderBy(kvp => kvp.Key, StringComparer.Ordinal); var sb = new StringBuilder(); - foreach ((string name, object? value) in sortedDetails) + foreach (KeyValuePair kvp in sortedDetails) { #if NET - sb.AppendLine(CultureInfo.InvariantCulture, $" {name} = {FormatValue(value)}"); + sb.AppendLine(CultureInfo.InvariantCulture, $" {kvp.Key} = {FormatValue(kvp.Value)}"); #else - sb.AppendLine($" {name} = {FormatValue(value)}"); + sb.AppendLine($" {kvp.Key} = {FormatValue(kvp.Value)}"); #endif } @@ -640,7 +640,7 @@ private static string CleanParentheses(string input) private static string RemoveOuterParentheses(string input) { - if (input.Length < 2 || !input.StartsWith('(') || !input.EndsWith(')')) + if (input.Length < 2 || !input.StartsWith("(", StringComparison.Ordinal) || !input.EndsWith(")", StringComparison.Ordinal)) { return input; } diff --git a/src/TestFramework/TestFramework/Assertions/CollectionAssert.cs b/src/TestFramework/TestFramework/Assertions/CollectionAssert.cs index c8f37e098b..603ed082e9 100644 --- a/src/TestFramework/TestFramework/Assertions/CollectionAssert.cs +++ b/src/TestFramework/TestFramework/Assertions/CollectionAssert.cs @@ -207,7 +207,7 @@ public static void AllItemsAreUnique([NotNull] ICollection? collection, string? message = Assert.ReplaceNulls(message); bool foundNull = false; - Dictionary table = []; + HashSet table = []; foreach (object? current in collection) { if (current == null) @@ -231,7 +231,7 @@ public static void AllItemsAreUnique([NotNull] ICollection? collection, string? } else { - if (!table.TryAdd(current, true)) + if (!table.Add(current)) { string userMessage = Assert.BuildUserMessage(message); string finalMessage = string.Format( diff --git a/src/TestFramework/TestFramework/Attributes/TestMethod/SingleThreadedSTASynchronizationContext.cs b/src/TestFramework/TestFramework/Attributes/TestMethod/SingleThreadedSTASynchronizationContext.cs index 691b72ebbd..8a37f1675e 100644 --- a/src/TestFramework/TestFramework/Attributes/TestMethod/SingleThreadedSTASynchronizationContext.cs +++ b/src/TestFramework/TestFramework/Attributes/TestMethod/SingleThreadedSTASynchronizationContext.cs @@ -12,7 +12,7 @@ internal sealed class SingleThreadedSTASynchronizationContext : SynchronizationC public SingleThreadedSTASynchronizationContext() { #if !NETFRAMEWORK - if (!OperatingSystem.IsWindows()) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { throw new NotSupportedException("SingleThreadedSTASynchronizationContext is only supported on Windows."); } diff --git a/src/TestFramework/TestFramework/TestFramework.csproj b/src/TestFramework/TestFramework/TestFramework.csproj index 81b796220e..63b8c344e7 100644 --- a/src/TestFramework/TestFramework/TestFramework.csproj +++ b/src/TestFramework/TestFramework/TestFramework.csproj @@ -13,7 +13,7 @@ NotPublishable false - $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL;EXCLUDE_OS_POLYFILL diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Helpers/CountDownEventTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Helpers/CountDownEventTests.cs index 49f81499da..fef796da19 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Helpers/CountDownEventTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Helpers/CountDownEventTests.cs @@ -41,7 +41,11 @@ public async Task CountDownEvent_WaitAsyncCanceled_Succeeded() CancellationTokenSource cts = new(); CancellationToken cancelToken = cts.Token; Task waiter = Task.Run(() => countdownEvent.WaitAsync(cancelToken), cancelToken); +#if NETCOREAPP await cts.CancelAsync(); +#else + cts.Cancel(); +#endif await Assert.ThrowsAsync(async () => await waiter); } diff --git a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProjectSystem.cs b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProjectSystem.cs index 8c1c12b0fb..104c42b4e8 100644 --- a/test/Utilities/Microsoft.Testing.TestInfrastructure/ProjectSystem.cs +++ b/test/Utilities/Microsoft.Testing.TestInfrastructure/ProjectSystem.cs @@ -79,12 +79,16 @@ public class CSharpProject : Project private readonly string _projectFileName; private XElement _projectContent = new("Project", new XAttribute("Sdk", "Microsoft.NET.Sdk"), new XElement("PropertyGroup"), new XElement("ItemGroup")); - public CSharpProject(string solutionFolder, string projectName, params string[]? tfms) + public CSharpProject(string solutionFolder, string projectName, params string[] tfms) : base(Path.Combine(solutionFolder, projectName)) { Ensure.NotNullOrWhiteSpace(solutionFolder); Ensure.NotNullOrWhiteSpace(projectName); - Ensure.NotNullOrEmpty(tfms); + + if (tfms is null || tfms.Length == 0) + { + throw new InvalidOperationException("tfms must have at least one element."); + } _projectFileName = $"{projectName}.csproj"; ProjectFile = Path.Combine(FolderPath, _projectFileName); From 9d98312c05d64f2b43e0712e6827ef7c0168dc5c Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 15:45:57 +0200 Subject: [PATCH 16/24] Progress --- .../TestFramework/Internal/TestDataSourceUtilities.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestFramework/TestFramework/Internal/TestDataSourceUtilities.cs b/src/TestFramework/TestFramework/Internal/TestDataSourceUtilities.cs index 7ebca153fd..60a1609a0b 100644 --- a/src/TestFramework/TestFramework/Internal/TestDataSourceUtilities.cs +++ b/src/TestFramework/TestFramework/Internal/TestDataSourceUtilities.cs @@ -29,7 +29,7 @@ internal static class TestDataSourceUtilities CultureInfo.CurrentCulture, FrameworkMessages.DataDrivenResultDisplayName, methodDisplayName, - string.Join(',', displayData.Select(GetHumanizedArguments))); + string.Join(",", displayData.Select(GetHumanizedArguments))); } /// @@ -57,6 +57,6 @@ internal static class TestDataSourceUtilities // We need to box the object here so that we can support value types IEnumerable boxedObjectEnumerable = ((IEnumerable)data).Cast(); IEnumerable elementStrings = boxedObjectEnumerable.Select(GetHumanizedArguments); - return $"[{string.Join(',', elementStrings)}]"; + return $"[{string.Join(",", elementStrings)}]"; } } From 219299ff5a48fe66872b36ff5cb47c65ed337883 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 16:02:49 +0200 Subject: [PATCH 17/24] Progress --- .../TestFramework.Extensions/TestFramework.Extensions.csproj | 2 ++ .../Configuration/ConfigurationExtensionsTests.cs | 3 ++- .../Assertions/AssertTests.AreEqualTests.cs | 2 +- test/Utilities/Automation.CLI/Automation.CLI.csproj | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj index 8e956ba5ad..afc6f45127 100644 --- a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj +++ b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj @@ -35,6 +35,8 @@ Microsoft.VisualStudio.TestTools.UnitTesting MSTest.TestFramework.Extensions $(DefineConstants);TRACE + + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationExtensionsTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationExtensionsTests.cs index 104bff4626..67bd0c012a 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationExtensionsTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/Configuration/ConfigurationExtensionsTests.cs @@ -42,6 +42,7 @@ public void ConfigurationExtensions_TestedMethod_ThrowsArgumentNullException(str .Setup(configuration => configuration[key]) .Returns(value: null); - Assert.ThrowsExactly(() => GetActualValueFromConfiguration(configuration.Object, key)); + // This should never happen in practice. We always have AggregatedConfiguration which will ensure non-null values. + Assert.ThrowsExactly(() => GetActualValueFromConfiguration(configuration.Object, key)); } } diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreEqualTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreEqualTests.cs index 1ba1daaad9..f7901cfdce 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreEqualTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreEqualTests.cs @@ -1649,7 +1649,7 @@ public void CreateStringPreviews_DiffNeverPointsAtEllipsis_Generated() { string p = FormatStringPreview(StringPreviewHelper.CreateStringPreviews(DigitString(e, d), DigitString(a, d), diffIndex: d, 11)); - string[] lines = p.Split("\n"); + string[] lines = p.Split('\n'); int diffIndicator = lines[2].IndexOf('^'); bool line0PointsOnEllipsis = lines[0].Length > diffIndicator && lines[0][diffIndicator] == '.'; bool line1PointsOnEllipsis = lines[1].Length > diffIndicator && lines[1][diffIndicator] == '.'; diff --git a/test/Utilities/Automation.CLI/Automation.CLI.csproj b/test/Utilities/Automation.CLI/Automation.CLI.csproj index 5e45cc985f..13762cbb3c 100644 --- a/test/Utilities/Automation.CLI/Automation.CLI.csproj +++ b/test/Utilities/Automation.CLI/Automation.CLI.csproj @@ -2,6 +2,7 @@ $(NetFrameworkMinimum) + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL From 037255e2060941c9ec554994d72856792e245b4b Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 16:04:46 +0200 Subject: [PATCH 18/24] Fix --- .../FlowTestContextCancellationTokenFixer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs b/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs index d0fb18a707..e6a977eaf4 100644 --- a/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs +++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/FlowTestContextCancellationTokenFixer.cs @@ -117,7 +117,7 @@ internal static Document ApplyFix( { if (testContextMemberName is null) { - throw new ArgumentNullException(testContextMemberName); + throw new ArgumentNullException(nameof(testContextMemberName)); } AddCancellationTokenArgument(editor, invocationExpression, testContextMemberName, cancellationTokenParameterName); From 639063ad179d1f5122299da3b5c0086bfb57ec23 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 16:05:37 +0200 Subject: [PATCH 19/24] Fix --- test/Utilities/Automation.CLI/Automation.CLI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Utilities/Automation.CLI/Automation.CLI.csproj b/test/Utilities/Automation.CLI/Automation.CLI.csproj index 13762cbb3c..5070065d87 100644 --- a/test/Utilities/Automation.CLI/Automation.CLI.csproj +++ b/test/Utilities/Automation.CLI/Automation.CLI.csproj @@ -2,7 +2,7 @@ $(NetFrameworkMinimum) - $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL;EXCLUDE_OS_POLYFILL From db5d5658de27d5fca77484eaffe786a1de08ffbb Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 16:27:08 +0200 Subject: [PATCH 20/24] Fix --- .../TestFramework.Extensions/TestFramework.Extensions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj index afc6f45127..4b194b57bd 100644 --- a/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj +++ b/src/TestFramework/TestFramework.Extensions/TestFramework.Extensions.csproj @@ -36,7 +36,7 @@ MSTest.TestFramework.Extensions $(DefineConstants);TRACE - $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL + $(DefineConstants);EXCLUDE_RANGE_INDEX_POLYFILL;EXCLUDE_OS_POLYFILL From 32236cb645179b60d8802a0c683ecf4eef83c9e7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 17:01:21 +0200 Subject: [PATCH 21/24] Progress --- .../Utilities/TestCaseFilterFactory.cs | 16 ++++++++++------ .../Verifiers/CSharpCodeFixVerifier`2.cs | 14 ++++++++++++++ .../Verifiers/VisualBasicCodeFixVerifier`2.cs | 14 ++++++++++++++ ...rTests.MockedMethodInfoWithExtraAttributes.cs | 8 ++------ .../ObjectModel/UnitTestElementTests.cs | 6 ++++-- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/TestCaseFilterFactory.cs b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/TestCaseFilterFactory.cs index 3bccf96779..f4600147b8 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Utilities/TestCaseFilterFactory.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Utilities/TestCaseFilterFactory.cs @@ -7,8 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; -using Polyfills; - namespace DiscoveryAndExecutionTests.Utilities; internal static class TestCaseFilterFactory @@ -19,7 +17,11 @@ internal static class TestCaseFilterFactory public static ITestCaseFilterExpression ParseTestFilter(string filterString) { - Ensure.NotNullOrEmpty(filterString); + if (string.IsNullOrEmpty(filterString)) + { + throw new ArgumentException("Filter string cannot be null or empty.", nameof(filterString)); + } + if (Regex.IsMatch(filterString, @"\(\s*\)")) { throw new FormatException($"Invalid filter, empty parenthesis: {filterString}"); @@ -119,7 +121,7 @@ public bool MatchTestCase(TestCase testCase, Func propertyValue private static void MergeExpression(Stack, bool>>> exp, Operator op) { - Ensure.NotNull(exp); + _ = exp ?? throw new ArgumentNullException(nameof(exp)); if (op is not Operator.And and not Operator.Or) { throw new ArgumentException($"Unexpected operator: {op}", nameof(op)); @@ -190,7 +192,6 @@ private static IEnumerable TokenizeFilter(string filterString) private static IEnumerable TokenizeCondition(string conditionString) { - Ensure.NotNullOrEmpty(conditionString); var token = new StringBuilder(conditionString.Length); for (int i = 0; i < conditionString.Length; i++) @@ -286,7 +287,10 @@ private static bool ContainsComparer(string[] values, string value) private static Expression, bool>> ConditionExpression(string conditionString) { - Ensure.NotNull(conditionString); + if (string.IsNullOrEmpty(conditionString)) + { + throw new ArgumentException("Condition string cannot be null or empty.", nameof(conditionString)); + } string[] condition = [.. TokenizeCondition(conditionString)]; diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs index 127d42d656..82497d76f3 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -31,7 +31,9 @@ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) /// public static async Task VerifyAnalyzerAsync( +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string source, params DiagnosticResult[] expected) { var test = new Test @@ -45,27 +47,39 @@ public static async Task VerifyAnalyzerAsync( /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string source, +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string fixedSource) => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string source, DiagnosticResult expected, +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string fixedSource) => await VerifyCodeFixAsync(source, [expected], fixedSource); /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string source, DiagnosticResult[] expected, +#if NETCOREAPP [StringSyntax("C#-test")] +#endif string fixedSource) { var test = new Test diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs index bd6fa42d6c..53f6309d60 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/Verifiers/VisualBasicCodeFixVerifier`2.cs @@ -31,7 +31,9 @@ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) /// public static async Task VerifyAnalyzerAsync( +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string source, params DiagnosticResult[] expected) { var test = new Test @@ -45,27 +47,39 @@ public static async Task VerifyAnalyzerAsync( /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string source, +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string fixedSource) => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string source, DiagnosticResult expected, +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string fixedSource) => await VerifyCodeFixAsync(source, [expected], fixedSource); /// public static async Task VerifyCodeFixAsync( +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string source, DiagnosticResult[] expected, +#if NETCOREAPP [StringSyntax("VB-test")] +#endif string fixedSource) { var test = new Test diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.MockedMethodInfoWithExtraAttributes.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.MockedMethodInfoWithExtraAttributes.cs index a351eada6d..a8c629987d 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.MockedMethodInfoWithExtraAttributes.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Discovery/TypeEnumeratorTests.MockedMethodInfoWithExtraAttributes.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if !NET6_0_OR_GREATER -using Polyfills; -#endif - namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests.Discovery; public partial class TypeEnumeratorTests @@ -68,7 +64,7 @@ public MockedMethodInfoWithExtraAttributes(MethodInfo original, params Attribute public override object[] GetCustomAttributes(bool inherit) => _original.GetCustomAttributes().Concat(_extraAttributes).ToArray(); - public override object[] GetCustomAttributes(Type attributeType, bool inherit) => _original.GetCustomAttributes().Concat(_extraAttributes.Where(a => a.GetType().IsAssignableTo(attributeType))).ToArray(); + public override object[] GetCustomAttributes(Type attributeType, bool inherit) => _original.GetCustomAttributes().Concat(_extraAttributes.Where(a => attributeType.IsAssignableFrom(a.GetType()))).ToArray(); public override IList GetCustomAttributesData() => _original.GetCustomAttributesData(); @@ -86,7 +82,7 @@ public MockedMethodInfoWithExtraAttributes(MethodInfo original, params Attribute => _original.Invoke(obj, invokeAttr, binder, parameters, culture); public override bool IsDefined(Type attributeType, bool inherit) - => _original.IsDefined(attributeType, inherit) || _extraAttributes.Any(a => a.GetType().IsAssignableTo(attributeType)); + => _original.IsDefined(attributeType, inherit) || _extraAttributes.Any(a => attributeType.IsAssignableFrom(a.GetType())); public override MethodInfo MakeGenericMethod(params Type[] typeArguments) => _original.MakeGenericMethod(typeArguments); diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs index 370fda5fa7..d7f4967493 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/ObjectModel/UnitTestElementTests.cs @@ -157,8 +157,11 @@ public void ToTestCaseShouldSetDeploymentItemPropertyIfPresent() public void ToTestCase_WhenStrategyIsData_DoesNotUseDefaultTestCaseId() { -#pragma warning disable CA2263 // Prefer generic overload when type is known +#if NETCOREAPP foreach (DynamicDataType dataType in Enum.GetValues()) +#else + foreach (DynamicDataType dataType in Enum.GetValues(typeof(DynamicDataType))) +#endif { var testCase = new UnitTestElement(new("MyMethod", "MyProduct.MyNamespace.MyClass", "MyAssembly", null) { @@ -172,7 +175,6 @@ public void ToTestCase_WhenStrategyIsData_DoesNotUseDefaultTestCaseId() Guid.TryParse(dataType == DynamicDataType.None ? "157ad7ac-90d2-8e05-a240-056ef4253f19" : "1834fb10-d2d5-8106-8620-918822cdc63a", out Guid expectedId2).Should().BeTrue(); expectedId.Should().Be(expectedId2); } -#pragma warning restore CA2263 // Prefer generic overload when type is known static Guid GuidFromString(string data) { From 2d2ccc8f2f3f9eca2620519d8efbe5ef9d1d4e0d Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 17:25:14 +0200 Subject: [PATCH 22/24] Update compat test --- .../ForwardCompatibilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs index c9edc53e61..e4956aee0e 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs @@ -26,7 +26,7 @@ public async Task NewerPlatform_WithPreviousExtensions_ShouldExecuteTests() public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.NuGetGlobalPackagesFolder) { - private const string PreviousExtensionVersion = "2.0.0"; + private const string PreviousExtensionVersion = "2.1.0"; private const string ForwardCompatibilityTestCode = """ #file ForwardCompatibilityTest.csproj From bf255cb84846f6adf2d576ec2c6faf3456a97f15 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 17:40:11 +0200 Subject: [PATCH 23/24] Update compat test --- .../ForwardCompatibilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs index e4956aee0e..0af7cf932d 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs @@ -26,7 +26,7 @@ public async Task NewerPlatform_WithPreviousExtensions_ShouldExecuteTests() public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.NuGetGlobalPackagesFolder) { - private const string PreviousExtensionVersion = "2.1.0"; + private const string PreviousExtensionVersion = "2.2.1"; private const string ForwardCompatibilityTestCode = """ #file ForwardCompatibilityTest.csproj From 528eaf49c7cc48e18443c3e8faf10b9ef0dcfca9 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Wed, 15 Apr 2026 17:57:01 +0200 Subject: [PATCH 24/24] Fix for TRX --- .../ForwardCompatibilityTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs index 0af7cf932d..fad778e23e 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ForwardCompatibilityTests.cs @@ -115,7 +115,7 @@ public Task CloseTestSessionAsync(CloseTestSessionContex public async Task ExecuteRequestAsync(ExecuteRequestContext context) { await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, - new TestNode() { Uid = "0", DisplayName = "ForwardCompatibilityTest", Properties = new(PassedTestNodeStateProperty.CachedInstance) })); + new TestNode() { Uid = "0", DisplayName = "ForwardCompatibilityTest", Properties = new(PassedTestNodeStateProperty.CachedInstance, new TrxFullyQualifiedTypeNameProperty("MyNS.MyTestClass")) })); context.Complete(); } }