Skip to content

Commit

Permalink
#2770: Make SerializationHelper public
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Sep 9, 2023
1 parent f49dafc commit 86b0eef
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 46 deletions.
54 changes: 19 additions & 35 deletions src/common/SerializationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Serialization;

#if XUNIT_FRAMEWORK
namespace Xunit.Sdk
#else
namespace Xunit
#endif
{
/// <summary>
/// Serializes and de-serializes objects. Serialization of objects is typically limited to supporting
/// the VSTest-based test runners (Visual Studio Test Explorer, Visual Studio Code, dotnet test, etc.).
/// Serializing values with this is not guaranteed to be cross-compatible and should not be used for
/// anything durable.
/// </summary>
static class SerializationHelper
public static class SerializationHelper
{
static readonly ConcurrentDictionary<Type, string> typeToTypeNameMap = new ConcurrentDictionary<Type, string>();

Expand All @@ -37,15 +40,7 @@ public static T Deserialize<T>(string serializedValue)
if (deserializedType == null)
throw new ArgumentException($"Could not load type '{pieces[0]}' from serialization value '{serializedValue}'", nameof(serializedValue));

if (!typeof(IXunitSerializable).IsAssignableFrom(deserializedType))
throw new ArgumentException("Cannot de-serialize an object that does not implement " + typeof(IXunitSerializable).FullName, nameof(serializedValue));

var obj = XunitSerializationInfo.Deserialize(deserializedType, pieces[1]);
var arraySerializer = obj as XunitSerializationInfo.ArraySerializer;
if (arraySerializer != null)
obj = arraySerializer.ArrayData;

return (T)obj;
return (T)XunitSerializationInfo.Deserialize(deserializedType, pieces[1]);
}

/// <summary>
Expand Down Expand Up @@ -162,10 +157,16 @@ public static Type GetType(string assemblyName, string typeName)
return assembly.GetType(typeName);
}

#if XUNIT_FRAMEWORK
/// <summary>
/// Gets an assembly qualified type name for serialization, with special handling for types which
/// live in assembly decorated by <see cref="PlatformSpecificAssemblyAttribute"/>.
/// </summary>
#else
/// <summary>
/// Gets an assembly qualified type name for serialization, with special dispensation for types which
/// originate in the execution assembly.
/// Gets an assembly qualified type name for serialization.
/// </summary>
#endif
public static string GetTypeNameForSerialization(Type type)
{
// Use the abstract Type instead of concretes like RuntimeType
Expand Down Expand Up @@ -220,9 +221,11 @@ string GetTypeNameAsString(Type typeToMap)
}
}

/// <summary>Gets whether the specified <paramref name="value"/> is serializable with <see cref="Serialize"/>.</summary>
/// <summary>
/// Determines whether the given <paramref name="value"/> is serializable with <see cref="Serialize"/>.
/// </summary>
/// <param name="value">The object to test for serializability.</param>
/// <returns>true if the object can be serialized; otherwise, false.</returns>
/// <returns>Returns <c>true</c> if the object can be serialized; <c>false</c>, otherwise.</returns>
public static bool IsSerializable(object value)
{
return XunitSerializationInfo.CanSerializeObject(value);
Expand All @@ -238,16 +241,7 @@ public static string Serialize(object value)
if (value == null)
throw new ArgumentNullException(nameof(value));

var array = value as object[];
if (array != null)
value = new XunitSerializationInfo.ArraySerializer(array);

var serializable = value as IXunitSerializable;
if (serializable == null)
throw new ArgumentException("Cannot serialize an object that does not implement " + typeof(IXunitSerializable).FullName, nameof(value));

var serializationInfo = new XunitSerializationInfo(serializable);
return $"{GetTypeNameForSerialization(value.GetType())}:{serializationInfo.ToSerializedString()}";
return $"{GetTypeNameForSerialization(value.GetType())}:{XunitSerializationInfo.Serialize(value)}";
}

static IList<string> SplitAtOuterCommas(string value, bool trimWhitespace = false)
Expand Down Expand Up @@ -290,16 +284,6 @@ static IList<string> SplitAtOuterCommas(string value, bool trimWhitespace = fals
return results;
}

/// <summary>
/// Retrieves a substring from the string, with whitespace trimmed on both ends.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The starting index.</param>
/// <param name="length">The length.</param>
/// <returns>
/// A substring starting no earlier than startIndex and ending no later
/// than startIndex + length.
/// </returns>
static string SubstringTrim(string str, int startIndex, int length)
{
int endIndex = startIndex + length;
Expand Down
8 changes: 7 additions & 1 deletion src/common/XunitSerializationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
using System.Linq;
using System.Text;
using Xunit.Abstractions;

#if XUNIT_FRAMEWORK
using Xunit.Sdk;
#endif

#if !NET35
#if !NETFRAMEWORK
using System.Numerics;
#endif

#if !NET35
using System.Reflection;
#endif

Expand Down
98 changes: 91 additions & 7 deletions test/test.xunit.execution/Common/SerializationHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using System;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using SerializationHelper = Xunit.Sdk.SerializationHelper;
using TestMethodDisplay = Xunit.Sdk.TestMethodDisplay;

#if NET6_0
using System.Numerics;
#endif

public class SerializationHelperTests
{
Expand Down Expand Up @@ -49,16 +56,93 @@ public static void CannotRoundTripTypesFromTheGAC()
#endif
}

public class IsSerializable
public class Serialization
{
[Fact]
public void TypeSerialization()
public static TheoryData<object, string> ValidValueData = new TheoryData<object, string>
{
Assert.True(SerializationHelper.IsSerializable(typeof(string))); // Can serialization types from mscorlib
Assert.True(SerializationHelper.IsSerializable(typeof(SerializationHelper))); // Can serialize types from local libraries
#if NETFRAMEWORK
Assert.False(SerializationHelper.IsSerializable(typeof(Uri))); // Can't serialize types from the GAC
{ 'a', "System.Char:97" },
{ "a", "System.String:YQ==" },
{ (byte)42, "System.Byte:42" },
{ (sbyte)-42, "System.SByte:-42" },
{ (ushort)42, "System.UInt16:42" },
{ (short)-42, "System.Int16:-42" },
{ 42U, "System.UInt32:42" },
{ -42, "System.Int32:-42" },
{ 42UL, "System.UInt64:42" },
{ -42L, "System.Int64:-42" },
{ 42.2112F, "System.Single:RWxlbWVudFR5cGU6U3lzdGVtLlN0cmluZzpVM2x6ZEdWdExrSjVkR1U9ClJhbms6U3lzdGVtLkludDMyOjEKVG90YWxMZW5ndGg6U3lzdGVtLkludDMyOjQKTGVuZ3RoMDpTeXN0ZW0uSW50MzI6NApMb3dlckJvdW5kMDpTeXN0ZW0uSW50MzI6MApJdGVtMDpTeXN0ZW0uQnl0ZTo2OQpJdGVtMTpTeXN0ZW0uQnl0ZToyMTYKSXRlbTI6U3lzdGVtLkJ5dGU6NDAKSXRlbTM6U3lzdGVtLkJ5dGU6NjY=" },
{ 42.2112D, "System.Double:RWxlbWVudFR5cGU6U3lzdGVtLlN0cmluZzpVM2x6ZEdWdExrSjVkR1U9ClJhbms6U3lzdGVtLkludDMyOjEKVG90YWxMZW5ndGg6U3lzdGVtLkludDMyOjgKTGVuZ3RoMDpTeXN0ZW0uSW50MzI6OApMb3dlckJvdW5kMDpTeXN0ZW0uSW50MzI6MApJdGVtMDpTeXN0ZW0uQnl0ZTozNwpJdGVtMTpTeXN0ZW0uQnl0ZToxMTcKSXRlbTI6U3lzdGVtLkJ5dGU6MgpJdGVtMzpTeXN0ZW0uQnl0ZToxNTQKSXRlbTQ6U3lzdGVtLkJ5dGU6OApJdGVtNTpTeXN0ZW0uQnl0ZToyNwpJdGVtNjpTeXN0ZW0uQnl0ZTo2OQpJdGVtNzpTeXN0ZW0uQnl0ZTo2NA==" },
{ 42.2112M, "System.Decimal:42.2112" },
{ true, "System.Boolean:True" },
{ new DateTime(2112L), "System.DateTime:0001-01-01T00:00:00.0002112" },
{ new DateTimeOffset(2112L, TimeSpan.Zero), "System.DateTimeOffset:0001-01-01T00:00:00.0002112+00:00" },
{ new TimeSpan(3, 4, 5), "System.TimeSpan:03:04:05" },
#if NET6_0
{ new DateOnly(2023, 8, 9), "System.DateOnly:738740" },
{ new TimeOnly(21, 12, 42, 567), "System.TimeOnly:763625670000" },
{ new BigInteger(42), "System.Numerics.BigInteger, System.Runtime.Numerics:42" },
#endif
{ typeof(string), "System.Type:System.String" },
{ TestMethodDisplay.Method, "Xunit.Sdk.TestMethodDisplay, xunit.core:Method" },
{ new[] { 1, 2, 3 }, "System.Int32[]:RWxlbWVudFR5cGU6U3lzdGVtLlN0cmluZzpVM2x6ZEdWdExrbHVkRE15ClJhbms6U3lzdGVtLkludDMyOjEKVG90YWxMZW5ndGg6U3lzdGVtLkludDMyOjMKTGVuZ3RoMDpTeXN0ZW0uSW50MzI6MwpMb3dlckJvdW5kMDpTeXN0ZW0uSW50MzI6MApJdGVtMDpTeXN0ZW0uSW50MzI6MQpJdGVtMTpTeXN0ZW0uSW50MzI6MgpJdGVtMjpTeXN0ZW0uSW50MzI6Mw==" },
{ 42, "System.Int32:42" },
{ new MySerializable(42), "SerializationHelperTests+Serialization+MySerializable, test.xunit.execution:dmFsdWU6U3lzdGVtLkludDMyOjQy" },
};

[Theory]
[MemberData(nameof(ValidValueData))]
public void ValidValues<T>(T value, string serializedValue)
{
// Serialization
var serialized = SerializationHelper.Serialize(value);

Assert.Equal(serializedValue, serialized);

// Deserialization
var deserialized = SerializationHelper.Deserialize<T>(serializedValue);

Assert.Equal(value, deserialized);
}

[Fact]
public void InvalidValue()
{
var obj = new object();

Assert.False(SerializationHelper.IsSerializable(obj));
var ex = Record.Exception(() => SerializationHelper.Serialize(obj));
var argEx = Assert.IsType<ArgumentException>(ex);
Assert.Equal("value", argEx.ParamName);
Assert.StartsWith("We don't know how to serialize type System.Object", argEx.Message);
}

class MySerializable : IXunitSerializable, IEquatable<MySerializable>
{
int value;

[Obsolete("For deserialization purposes only")]
public MySerializable()
{ }

public MySerializable(int value)
{
this.value = value;
}

public void Deserialize(IXunitSerializationInfo info)
{
value = info.GetValue<int>(nameof(value));
}

public bool Equals(MySerializable other)
{
return other != null && value == other.value;
}

public void Serialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(value), value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using SerializationHelper = Xunit.Sdk.SerializationHelper;

public class TheoryDiscovererTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Xunit.Abstractions;
using Xunit.Sdk;
using IAttributeInfo = Xunit.Abstractions.IAttributeInfo;
using SerializationHelper = Xunit.Sdk.SerializationHelper;
using TestMethodDisplay = Xunit.Sdk.TestMethodDisplay;
using TestMethodDisplayOptions = Xunit.Sdk.TestMethodDisplayOptions;

Expand Down
1 change: 1 addition & 0 deletions test/test.xunit.execution/Sdk/TestCaseSerializerTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Xunit;
using Xunit.Sdk;
using SerializationHelper = Xunit.Sdk.SerializationHelper;

public class TestCaseSerializerTests
{
Expand Down
1 change: 1 addition & 0 deletions test/test.xunit.execution/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using SerializationHelper = Xunit.Sdk.SerializationHelper;

public class SerializationTests
{
Expand Down
1 change: 0 additions & 1 deletion test/test.xunit.execution/test.xunit.execution.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<Compile Include="..\..\src\common\ExecutionHelper.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\Guard.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\NewReflectionExtensions.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\SerializationHelper.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\TestOptionsNames.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\XunitSerializationInfo.cs" LinkBase="Common" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
<Compile Include="..\..\src\common\Guard.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\Json.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\NewReflectionExtensions.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\SerializationHelper.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\TestOptionsNames.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\XunitSerializationInfo.cs" LinkBase="Common" />
<Compile Include="..\..\src\common\AssemblyResolution\**\*.cs" LinkBase="Common\AssemblyResolution" />
<Compile Include="..\..\src\xunit.runner.utility\Frameworks\v1\Xunit1ExceptionUtility.cs" LinkBase="Common\RunnerUtility" />
</ItemGroup>
Expand Down

0 comments on commit 86b0eef

Please sign in to comment.