diff --git a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs index c7c980e9fd..49058b9125 100644 --- a/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs +++ b/src/Adapter/MSTestAdapter.PlatformServices/Helpers/DataSerializationHelper.cs @@ -21,7 +21,7 @@ internal static class DataSerializationHelper #if NETFRAMEWORK DataContractSurrogate = SerializationSurrogateProvider.Instance, #endif - KnownTypes = [typeof(SurrogatedDateOnly), typeof(SurrogatedTimeOnly)], + KnownTypes = [typeof(SurrogatedDateOnly), typeof(SurrogatedTimeOnly), typeof(SurrogatedSystemType)], }; /// @@ -50,7 +50,22 @@ internal static class DataSerializationHelper continue; } - Type type = data[i]!.GetType(); + object valueToSerialize = data[i]!; + Type type = valueToSerialize.GetType(); + + if (valueToSerialize is Type serializableType) + { + string assemblyQualifiedName = serializableType.AssemblyQualifiedName + ?? throw new SerializationException($"Cannot serialize '{serializableType}' because it does not have an assembly-qualified name."); + + valueToSerialize = new SurrogatedSystemType + { + AssemblyQualifiedName = assemblyQualifiedName, + }; + + type = typeof(SurrogatedSystemType); + } + string? typeName = type.AssemblyQualifiedName; serializedData[typeIndex] = typeName; @@ -66,7 +81,7 @@ internal static class DataSerializationHelper // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. #pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT #pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming - serializer.WriteObject(memoryStream, data[i]); + serializer.WriteObject(memoryStream, valueToSerialize); #pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT #pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming byte[] serializerData = memoryStream.ToArray(); @@ -135,7 +150,7 @@ private static DataContractJsonSerializer GetSerializer(string assemblyQualified // Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other. #pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT #pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming - _ => new DataContractJsonSerializer(PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings)); + _ => new DataContractJsonSerializer(GetSerializationType(assemblyQualifiedName), SerializerSettings)); #pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT #pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming @@ -149,6 +164,19 @@ private static DataContractJsonSerializer GetSerializer(Type type) #pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming _ => new DataContractJsonSerializer(type, SerializerSettings)); + private static Type GetSerializationType(string assemblyQualifiedName) + { + Type? serializedType = PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName); + return serializedType + ?? assemblyQualifiedName switch + { + var name when name.StartsWith(typeof(SurrogatedSystemType).FullName + ",", StringComparison.Ordinal) => typeof(SurrogatedSystemType), + var name when name.StartsWith(typeof(SurrogatedDateOnly).FullName + ",", StringComparison.Ordinal) => typeof(SurrogatedDateOnly), + var name when name.StartsWith(typeof(SurrogatedTimeOnly).FullName + ",", StringComparison.Ordinal) => typeof(SurrogatedTimeOnly), + _ => typeof(object), + }; + } + [DataContract] private sealed class SurrogatedDateOnly { @@ -163,6 +191,13 @@ private sealed class SurrogatedTimeOnly public long Ticks { get; set; } } + [DataContract] + private sealed class SurrogatedSystemType + { + [DataMember] + public string AssemblyQualifiedName { get; set; } = null!; + } + private sealed class SerializationSurrogateProvider #if NETFRAMEWORK : IDataContractSurrogate @@ -201,8 +236,10 @@ internal static object GetDeserializedObject(object obj) return new TimeOnly(surrogatedTimeOnly.Ticks); } #endif - - return obj; + return obj is SurrogatedSystemType surrogatedSystemType + ? PlatformServiceProvider.Instance.ReflectionOperations.GetType(surrogatedSystemType.AssemblyQualifiedName) + ?? throw new SerializationException($"Cannot deserialize type '{surrogatedSystemType.AssemblyQualifiedName}'.") + : obj; } public object GetObjectToSerialize(object obj, Type targetType) diff --git a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DataRowTests.cs b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DataRowTests.cs index 3eec60fd53..81e6cdbaa0 100644 --- a/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DataRowTests.cs +++ b/test/IntegrationTests/MSTest.IntegrationTests/Parameterized tests/DataRowTests.cs @@ -167,7 +167,9 @@ public async Task DataRowsShouldHandleNonSerializableValues() // Assert VerifyE2E.TestsDiscovered( testCases, - "DataRowNonSerializable"); + "DataRowNonSerializable (System.String)", + "DataRowNonSerializable (System.Int32)", + "DataRowNonSerializable (DataRowTestProject.DataRowTests_DerivedClass)"); VerifyE2E.TestsPassed( testResults, diff --git a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/DataSerializationHelperTests.cs b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/DataSerializationHelperTests.cs index b4753e5538..250161b628 100644 --- a/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/DataSerializationHelperTests.cs +++ b/test/UnitTests/MSTestAdapter.PlatformServices.UnitTests/Helpers/DataSerializationHelperTests.cs @@ -81,4 +81,32 @@ public void DataSerializerShouldRoundTripTimeOnly() actual[0]!.Equals(source).Should().BeTrue(); } #endif + + public void DataSerializerShouldRoundTripSystemType() + { + Type[] source = [typeof(Console), typeof(DataSerializationHelperTests), typeof(DataSerializationHelper)]; + + foreach (Type type in source) + { + object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize([type])); + + actual!.Length.Should().Be(1); + actual[0].Should().Be(type); + } + } + + public void DataSerializerShouldRoundTripMixedPayloadIncludingSystemType() + { + object?[] source = [typeof(string), 42, "hello", null, new DateTime(638450000000000000)]; + + object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize(source)); + + actual.Should().NotBeNull(); + actual!.Length.Should().Be(source.Length); + actual[0].Should().Be(typeof(string)); + actual[1].Should().Be(42); + actual[2].Should().Be("hello"); + actual[3].Should().BeNull(); + actual[4].Should().Be(source[4]); + } }