Skip to content

Fixed serialization issues with ITestDataSource. #847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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