Skip to content

Commit bb3c852

Browse files
authored
Allow serialization of DateOnly/TimeOnly for parameterized tests (#5676)
1 parent 39aa076 commit bb3c852

File tree

4 files changed

+131
-8
lines changed

4 files changed

+131
-8
lines changed

src/Adapter/MSTest.TestAdapter/Helpers/DataSerializationHelper.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
#if NETFRAMEWORK
5+
using System.CodeDom;
6+
using System.Collections.ObjectModel;
7+
#endif
8+
using System.Runtime.Serialization;
49
using System.Runtime.Serialization.Json;
510

611
namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
@@ -13,6 +18,10 @@ internal static class DataSerializationHelper
1318
UseSimpleDictionaryFormat = true,
1419
EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Always,
1520
DateTimeFormat = new System.Runtime.Serialization.DateTimeFormat("O", CultureInfo.InvariantCulture),
21+
#if NETFRAMEWORK
22+
DataContractSurrogate = SerializationSurrogateProvider.Instance,
23+
#endif
24+
KnownTypes = [typeof(SurrogatedDateOnly), typeof(SurrogatedTimeOnly)],
1625
};
1726

1827
/// <summary>
@@ -47,6 +56,9 @@ internal static class DataSerializationHelper
4756
serializedData[typeIndex] = typeName;
4857

4958
DataContractJsonSerializer serializer = GetSerializer(type);
59+
#if NET7_0_OR_GREATER
60+
serializer.SetSerializationSurrogateProvider(SerializationSurrogateProvider.Instance);
61+
#endif
5062

5163
using var memoryStream = new MemoryStream();
5264
// This should be safe as long as our generator mentions
@@ -93,6 +105,9 @@ internal static class DataSerializationHelper
93105
}
94106

95107
DataContractJsonSerializer serializer = GetSerializer(assemblyQualifiedName);
108+
#if NET7_0_OR_GREATER
109+
serializer.SetSerializationSurrogateProvider(SerializationSurrogateProvider.Instance);
110+
#endif
96111

97112
byte[] serializedDataBytes = Encoding.UTF8.GetBytes(serializedValue);
98113
using var memoryStream = new MemoryStream(serializedDataBytes);
@@ -104,6 +119,9 @@ internal static class DataSerializationHelper
104119
data[i] = serializer.ReadObject(memoryStream);
105120
#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
106121
#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
122+
// For some reason, we don't get SerializationSurrogateProvider.GetDeserializedObject to be called by .NET runtime.
123+
// So we manually call it.
124+
data[i] = SerializationSurrogateProvider.GetDeserializedObject(data[i]!);
107125
}
108126

109127
return data;
@@ -130,6 +148,93 @@ private static DataContractJsonSerializer GetSerializer(Type type)
130148
#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
131149
#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
132150
_ => new DataContractJsonSerializer(type, SerializerSettings));
151+
152+
[DataContract]
153+
private sealed class SurrogatedDateOnly
154+
{
155+
[DataMember]
156+
public int DayNumber { get; set; }
157+
}
158+
159+
[DataContract]
160+
private sealed class SurrogatedTimeOnly
161+
{
162+
[DataMember]
163+
public long Ticks { get; set; }
164+
}
165+
166+
private sealed class SerializationSurrogateProvider
167+
#if NETFRAMEWORK
168+
: IDataContractSurrogate
169+
#else
170+
: ISerializationSurrogateProvider
171+
#endif
172+
{
173+
public static SerializationSurrogateProvider Instance { get; } = new();
174+
175+
#if NETFRAMEWORK
176+
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType) => null!;
177+
178+
public object GetCustomDataToExport(Type clrType, Type dataContractType) => null!;
179+
180+
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
181+
{
182+
}
183+
184+
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) => null!;
185+
186+
public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit) => typeDeclaration;
187+
#endif
188+
189+
public object GetDeserializedObject(object obj, Type targetType)
190+
=> GetDeserializedObject(obj);
191+
192+
internal static object GetDeserializedObject(object obj)
193+
{
194+
#if NET6_0_OR_GREATER
195+
if (obj is SurrogatedDateOnly surrogatedDateOnly)
196+
{
197+
return DateOnly.FromDayNumber(surrogatedDateOnly.DayNumber);
198+
}
199+
else if (obj is SurrogatedTimeOnly surrogatedTimeOnly)
200+
{
201+
return new TimeOnly(surrogatedTimeOnly.Ticks);
202+
}
203+
#endif
204+
205+
return obj;
206+
}
207+
208+
public object GetObjectToSerialize(object obj, Type targetType)
209+
=> obj switch
210+
{
211+
#if NET6_0_OR_GREATER
212+
DateOnly dateOnly => new SurrogatedDateOnly() { DayNumber = dateOnly.DayNumber },
213+
TimeOnly timeOnly => new SurrogatedTimeOnly() { Ticks = timeOnly.Ticks },
214+
#endif
215+
_ => obj,
216+
};
217+
218+
#if NETFRAMEWORK
219+
public Type GetDataContractType(Type type)
220+
#else
221+
public Type GetSurrogateType(Type type)
222+
#endif
223+
{
224+
#if NET6_0_OR_GREATER
225+
if (type == typeof(DateOnly))
226+
{
227+
return typeof(SurrogatedDateOnly);
228+
}
229+
else if (type == typeof(TimeOnly))
230+
{
231+
return typeof(SurrogatedTimeOnly);
232+
}
233+
#endif
234+
235+
return type;
236+
}
237+
}
133238
#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
134239
#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
135240
}

src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,7 @@ private static bool TryGetData(object dataSource, [NotNullWhen(true)] out IEnume
118118
List<object[]> objects = [];
119119
foreach (object? entry in enumerable)
120120
{
121-
if (entry is null)
122-
{
123-
data = null;
124-
return false;
125-
}
126-
127-
objects.Add([entry]);
121+
objects.Add([entry!]);
128122
}
129123

130124
data = objects;

test/UnitTests/MSTestAdapter.UnitTests/Helpers/DataSerializationHelperTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,28 @@ public void DataSerializerShouldRoundTripDateTimeOfKindUtc()
5555
Verify(actual[0]!.Equals(source));
5656
Verify(((DateTime)actual[0]!).Kind.Equals(source.Kind));
5757
}
58+
59+
#if NET7_0_OR_GREATER
60+
public void DataSerializerShouldRoundTripDateOnly()
61+
{
62+
var source = new DateOnly(1999, 11, 3);
63+
64+
object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize([source]));
65+
66+
Verify(actual!.Length == 1);
67+
Verify(actual[0]!.GetType() == typeof(DateOnly));
68+
Verify(actual[0]!.Equals(source));
69+
}
70+
71+
public void DataSerializerShouldRoundTripTimeOnly()
72+
{
73+
var source = new TimeOnly(hour: 14, minute: 50, second: 13, millisecond: 15);
74+
75+
object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize([source]));
76+
77+
Verify(actual!.Length == 1);
78+
Verify(actual[0]!.GetType() == typeof(TimeOnly));
79+
Verify(actual[0]!.Equals(source));
80+
}
81+
#endif
5882
}

test/UnitTests/MSTestAdapter.UnitTests/MSTestAdapter.UnitTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<NetStandardNetFrameworkHolder>net48</NetStandardNetFrameworkHolder>
5-
<TargetFrameworks>net6.0;net462;$(NetStandardNetFrameworkHolder);netcoreapp3.1</TargetFrameworks>
5+
<TargetFrameworks>net6.0;net7.0;net462;$(NetStandardNetFrameworkHolder);netcoreapp3.1</TargetFrameworks>
66
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);$(WinUiMinimum)</TargetFrameworks>
77
<IsNetCoreApp Condition=" '$(TargetFramework)' == 'netcoreapp3.1' OR '$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == '$(WinUiMinimum)' ">true</IsNetCoreApp>
88
<RootNamespace>Microsoft.VisualStudio.TestPlatform.MSTestAdapter.UnitTests</RootNamespace>

0 commit comments

Comments
 (0)