Skip to content

Commit

Permalink
Fixed a serialization issue with DataRows. (microsoft#847)
Browse files Browse the repository at this point in the history
  • Loading branch information
Haplois authored May 31, 2021
1 parent 1c62432 commit 57d2d00
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 109 deletions.
32 changes: 27 additions & 5 deletions src/Adapter/MSTest.CoreAdapter/Discovery/AssemblyEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Discovery
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security;
using System.Text;

Expand Down Expand Up @@ -249,7 +250,8 @@ private IEnumerable<UnitTestElement> DiscoverTestsInType(string assemblyFileName

private bool DynamicDataAttached(IDictionary<string, object> sourceLevelParameters, Assembly assembly, UnitTestElement test, List<UnitTestElement> tests)
{
// It should always be `true`, but any part of the chain is obsolete; it might not contain those. Since we depend on those properties, if they don't exist, we bail out early.
// It should always be `true`, but if any part of the chain is obsolete; it might not contain those.
// Since we depend on those properties, if they don't exist, we bail out early.
if (!test.TestMethod.HasManagedMethodAndTypeProperties)
{
return false;
Expand Down Expand Up @@ -320,7 +322,7 @@ private bool ProcessDataSourceTests(UnitTestElement test, TestMethodInfo testMet
var discoveredTest = test.Clone();
discoveredTest.DisplayName = displayName;
discoveredTest.TestMethod.DataType = DynamicDataType.DataSourceAttribute;
discoveredTest.TestMethod.Data = new[] { (object)rowIndex };
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(new[] { (object)rowIndex });
tests.Add(discoveredTest);
}

Expand Down Expand Up @@ -359,17 +361,37 @@ private bool ProcessTestDataSourceTests(UnitTestElement test, MethodInfo methodI
foreach (var dataSource in testDataSources)
{
var data = dataSource.GetData(methodInfo);
var discoveredTests = new List<UnitTestElement>();
var serializationFailed = false;

foreach (var d in data)
{
var discoveredTest = test.Clone();
discoveredTest.DisplayName = dataSource.GetDisplayName(methodInfo, d);

discoveredTest.TestMethod.DataType = DynamicDataType.ITestDataSource;
discoveredTest.TestMethod.Data = d;
try
{
discoveredTest.TestMethod.SerializedData = DataSerializationHelper.Serialize(d);
discoveredTest.TestMethod.DataType = DynamicDataType.ITestDataSource;
}
catch (SerializationException)
{
serializationFailed = true;
break;
}

tests.Add(discoveredTest);
discoveredTests.Add(discoveredTest);
}

// Serialization failed for the type, bail out.
if (serializationFailed)
{
tests.Add(test);

break;
}

tests.AddRange(discoveredTests);
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Execution
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Extensions;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
Expand Down Expand Up @@ -375,18 +376,14 @@ private void ExecuteTestsWithTestRunner(

var startTime = DateTimeOffset.Now;

PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
"Executing test {0}",
unitTestElement.TestMethod.Name);
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executing test {0}", unitTestElement.TestMethod.Name);

// Run single test passing test context properties to it.
var tcmProperties = TcmTestPropertiesProvider.GetTcmProperties(currentTest);
var testContextProperties = this.GetTestContextProperties(tcmProperties, sourceLevelParameters);
var unitTestResult = testRunner.RunSingleTest(unitTestElement.TestMethod, testContextProperties);

PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo(
"Executed test {0}",
unitTestElement.TestMethod.Name);
PlatformServiceProvider.Instance.AdapterTraceLogger.LogInfo("Executed test {0}", unitTestElement.TestMethod.Name);

var endTime = DateTimeOffset.Now;

Expand Down
52 changes: 22 additions & 30 deletions src/Adapter/MSTest.CoreAdapter/Execution/TestMethodRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,9 @@ internal class TestMethodRunner
/// <param name="captureDebugTraces">
/// The capture debug traces.
/// </param>
public TestMethodRunner(
TestMethodInfo testMethodInfo,
TestMethod testMethod,
ITestContext testContext,
bool captureDebugTraces)
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces)
: this(testMethodInfo, testMethod, testContext, captureDebugTraces, ReflectHelper.Instance)
{
Debug.Assert(testMethodInfo != null, "testMethodInfo should not be null");
Debug.Assert(testMethod != null, "testMethod should not be null");
Debug.Assert(testContext != null, "testContext should not be null");

this.testMethodInfo = testMethodInfo;
this.test = testMethod;
this.testContext = testContext;
this.captureDebugTraces = captureDebugTraces;
}

/// <summary>
Expand All @@ -99,12 +87,7 @@ public TestMethodRunner(
/// <param name="reflectHelper">
/// The reflect Helper object.
/// </param>
public TestMethodRunner(
TestMethodInfo testMethodInfo,
TestMethod testMethod,
ITestContext testContext,
bool captureDebugTraces,
ReflectHelper reflectHelper)
public TestMethodRunner(TestMethodInfo testMethodInfo, TestMethod testMethod, ITestContext testContext, bool captureDebugTraces, ReflectHelper reflectHelper)
{
Debug.Assert(testMethodInfo != null, "testMethodInfo should not be null");
Debug.Assert(testMethod != null, "testMethod should not be null");
Expand Down Expand Up @@ -223,7 +206,8 @@ internal UnitTestResult[] RunTestMethod()
{
if (this.test.DataType == DynamicDataType.ITestDataSource)
{
var testResults = this.ExecuteTestWithDataSource(null, this.test.Data);
var data = DataSerializationHelper.Deserialize(this.test.SerializedData, this.testMethodInfo.Parent.Parent.Assembly);
var testResults = this.ExecuteTestWithDataSource(null, data);
results.AddRange(testResults);
}
else if (this.ExecuteDataSourceBasedTests(results))
Expand All @@ -232,7 +216,7 @@ internal UnitTestResult[] RunTestMethod()
}
else
{
var testResults = this.ExecuteTest();
var testResults = this.ExecuteTest(this.testMethodInfo);

foreach (var testResult in testResults)
{
Expand Down Expand Up @@ -358,7 +342,7 @@ private UTF.TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource testDataS
var stopwatch = Stopwatch.StartNew();

this.testMethodInfo.SetArguments(data);
var testResults = this.ExecuteTest();
var testResults = this.ExecuteTest(this.testMethodInfo);
stopwatch.Stop();

var hasDisplayName = !string.IsNullOrWhiteSpace(this.test.DisplayName);
Expand Down Expand Up @@ -387,13 +371,21 @@ private UTF.TestResult[] ExecuteTestWithDataSource(UTF.ITestDataSource testDataS

private UTF.TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
{
var stopwatch = Stopwatch.StartNew();

this.testContext.SetDataRow(dataRow);
var testResults = this.ExecuteTest();
stopwatch.Stop();

var displayName = string.Format(CultureInfo.CurrentCulture, Resource.DataDrivenResultDisplayName, this.test.DisplayName, rowIndex);
Stopwatch stopwatch = null;

UTF.TestResult[] testResults = null;
try
{
stopwatch = Stopwatch.StartNew();
this.testContext.SetDataRow(dataRow);
testResults = this.ExecuteTest(this.testMethodInfo);
}
finally
{
stopwatch?.Stop();
this.testContext.SetDataRow(null);
}

foreach (var testResult in testResults)
{
Expand All @@ -405,11 +397,11 @@ private UTF.TestResult[] ExecuteTestWithDataRow(object dataRow, int rowIndex)
return testResults;
}

private UTF.TestResult[] ExecuteTest()
private UTF.TestResult[] ExecuteTest(TestMethodInfo testMethodInfo)
{
try
{
return this.testMethodInfo.TestMethodOptions.Executor.Execute(this.testMethodInfo);
return this.testMethodInfo.TestMethodOptions.Executor.Execute(testMethodInfo);
}
catch (Exception ex)
{
Expand Down
3 changes: 2 additions & 1 deletion src/Adapter/MSTest.CoreAdapter/Execution/UnitTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ internal UnitTestResult[] RunSingleTest(TestMethod testMethod, IDictionary<strin
return new UnitTestResult[] { new UnitTestResult(UnitTestOutcome.NotRunnable, testMethodInfo.NotRunnableReason) };
}

return new TestMethodRunner(testMethodInfo, testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces).Execute();
var testMethodRunner = new TestMethodRunner(testMethodInfo, testMethod, testContext, MSTestSettings.CurrentSettings.CaptureDebugTraces);
return testMethodRunner.Execute();
}
}
catch (TypeInspectionException ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal static UnitTestElement ToUnitTestElement(this TestCase testCase, string
var data = testCase.GetPropertyValue<string[]>(Constants.TestDynamicDataProperty, null);

testMethod.DataType = dataType;
testMethod.Data = Helpers.DataSerializationHelper.Deserialize(data);
testMethod.SerializedData = data;
}

testMethod.DisplayName = testCase.DisplayName;
Expand Down
88 changes: 71 additions & 17 deletions src/Adapter/MSTest.CoreAdapter/Helpers/DataSerializationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@

namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.Helpers
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Text;

internal static class DataSerializationHelper
{
private static readonly Dictionary<Type, DataContractJsonSerializer> SerializerCache = new Dictionary<Type, DataContractJsonSerializer>();
private static readonly DataContractJsonSerializerSettings SerializerSettings = new DataContractJsonSerializerSettings()
{
UseSimpleDictionaryFormat = true,
EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Always
};

/// <summary>
/// Serializes the date in such a way that won't throw exceptions during deserialization in Test Platform.
/// The result can be deserialized using <see cref="Deserialize(string[])"/> method.
Expand All @@ -22,23 +33,33 @@ public static string[] Serialize(object[] data)
return null;
}

var serializer = GetSerializer();
var serializedData = new string[data.Length];
var serializedData = new string[data.Length * 2];

for (int i = 0; i < data.Length; i++)
{
var typeIndex = i * 2;
var dataIndex = typeIndex + 1;

if (data[i] == null)
{
serializedData[i] = null;
serializedData[typeIndex] = null;
serializedData[dataIndex] = null;
continue;
}

var type = data[i].GetType();
var typeName = type.FullName;

serializedData[typeIndex] = typeName;

var serializer = GetSerializer(type);

using (var memoryStream = new MemoryStream())
{
serializer.WriteObject(memoryStream, data[i]);
var serializerData = memoryStream.ToArray();

serializedData[i] = Encoding.UTF8.GetString(serializerData, 0, serializerData.Length);
serializedData[dataIndex] = Encoding.UTF8.GetString(serializerData, 0, serializerData.Length);
}
}

Expand All @@ -49,26 +70,33 @@ public static string[] Serialize(object[] data)
/// Deserialzes the data serialzed by <see cref="Serialize(object[])" /> method.
/// </summary>
/// <param name="serializedData">Serialized data array to deserialize.</param>
/// <param name="assemblies">Assemblies that serialized types defined in.</param>
/// <returns>Deserialized array.</returns>
public static object[] Deserialize(string[] serializedData)
public static object[] Deserialize(string[] serializedData, params Assembly[] assemblies)
{
if (serializedData == null)
if (serializedData == null || serializedData.Length % 2 != 0)
{
return null;
}

var serializer = GetSerializer();
var data = new object[serializedData.Length];
var length = serializedData.Length / 2;
var data = new object[length];

for (int i = 0; i < serializedData.Length; i++)
for (int i = 0; i < length; i++)
{
var typeIndex = i * 2;
var dataIndex = typeIndex + 1;

if (serializedData[i] == null)
{
data[i] = null;
continue;
}

var serialzedDataBytes = Encoding.UTF8.GetBytes(serializedData[i]);
var typeName = serializedData[typeIndex];
var serializer = GetSerializer(typeName, assemblies);

var serialzedDataBytes = Encoding.UTF8.GetBytes(serializedData[dataIndex]);
using (var memoryStream = new MemoryStream(serialzedDataBytes))
{
data[i] = serializer.ReadObject(memoryStream);
Expand All @@ -78,17 +106,43 @@ public static object[] Deserialize(string[] serializedData)
return data;
}

private static DataContractJsonSerializer GetSerializer()
private static DataContractJsonSerializer GetSerializer(string typeName, Assembly[] assemblies)
{
var settings = new DataContractJsonSerializerSettings()
var serializer = SerializerCache.SingleOrDefault(i => i.Key.FullName == typeName);
if (serializer.Value != null)
{
return serializer.Value;
}

var type = Type.GetType(typeName);
if (type != null)
{
UseSimpleDictionaryFormat = true,
EmitTypeInformation = System.Runtime.Serialization.EmitTypeInformation.Always
};
return GetSerializer(type);
}

if (assemblies != null)
{
foreach (var assembly in assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
{
return GetSerializer(type);
}
}
}

var serializer = new DataContractJsonSerializer(typeof(object), settings);
return GetSerializer(typeof(object));
}

private static DataContractJsonSerializer GetSerializer(Type type)
{
if (SerializerCache.ContainsKey(type))
{
return SerializerCache[type];
}

return serializer;
return SerializerCache[type] = new DataContractJsonSerializer(type, SerializerSettings);
}
}
}
4 changes: 2 additions & 2 deletions src/Adapter/MSTest.CoreAdapter/ObjectModel/TestMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ public string DeclaringClassFullName
internal DynamicDataType DataType { get; set; }

/// <summary>
/// Gets or sets indices of dynamic data
/// Gets or sets the serialized data
/// </summary>
internal object[] Data { get; set; }
internal string[] SerializedData { get; set; }

/// <summary>
/// Gets or sets the test group set during discovery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ internal TestCase ToTestCase()
// Store resolved data if any
if (this.TestMethod.DataType != DynamicDataType.None)
{
var data = Helpers.DataSerializationHelper.Serialize(this.TestMethod.Data);
var data = this.TestMethod.SerializedData;

testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataTypeProperty, (int)this.TestMethod.DataType);
testCase.SetPropertyValue(TestAdapter.Constants.TestDynamicDataProperty, data);
Expand Down
Loading

0 comments on commit 57d2d00

Please sign in to comment.