Skip to content

Commit

Permalink
BinaryFormat test updates (#11321)
Browse files Browse the repository at this point in the history
The most expensive thing we're doing in the binary format tests is getting the validation test method for types. I've added a cache for this, which cuts a third of the testing time for the project.

Also adding some theory data helper classes for tuples for clarity and minor perf.

Finally, no need to call the slow method for array index sets for multidimensional arrays.
  • Loading branch information
JeremyKuhne authored May 7, 2024
1 parent bb8c1c9 commit 8d62cf5
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 61 deletions.
43 changes: 43 additions & 0 deletions src/Common/tests/TestUtilities/XUnit/EnumerableTupleTheoryData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;

namespace Xunit;

/// <summary>
/// Theory data for tuple enumeration.
/// </summary>
public class EnumerableTupleTheoryData<T1, T2> : IReadOnlyCollection<object[]>
where T1 : notnull
where T2 : notnull
{
private readonly IEnumerable<(T1, T2)> _data;

public int Count => _data.Count();

public EnumerableTupleTheoryData(IEnumerable<(T1, T2)> data) => _data = data;

public IEnumerator<object[]> GetEnumerator() =>
_data.Select(i => new object[] { i.Item1, i.Item2 }).GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

/// <inheritdoc cref="EnumerableTupleTheoryData{T1, T2}"/>
public class EnumerableTupleTheoryData<T1, T2, T3> : IReadOnlyCollection<object[]>
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
private readonly IEnumerable<(T1, T2, T3)> _data;

public int Count => _data.Count();

public EnumerableTupleTheoryData(IEnumerable<(T1, T2, T3)> data) => _data = data;

public IEnumerator<object[]> GetEnumerator() =>
_data.Select(i => new object[] { i.Item1, i.Item2, i.Item3 }).GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal override Id Continue()
continue;
}

if (_arrayType is not BinaryArrayType.Rectangular || _elementType.IsValueType)
if (_elementType.IsValueType)
{
_array.SetArrayValueByFlattenedIndex(memberValue, _index);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Serialization.Formatters;
using BinaryFormatTests;
using BinaryFormatTests.FormatterTests;
using System.Runtime.Serialization.Formatters;

namespace FormatTests.Common;

Expand Down Expand Up @@ -59,55 +59,24 @@ public void BasicObjectsRoundtrip(
EqualityExtensions.CheckEquals(value, deserialized, isSamePlatform: true);
}

public static TheoryData<object, TypeSerializableValue[]> SerializableObjects()
public static EnumerableTupleTheoryData<object, TypeSerializableValue[]> SerializableObjects()
{
List<(object Object, TypeSerializableValue[] Serialized)> data = [];

foreach (var value in BinaryFormatterTests.RawSerializableObjects())
{
// Can add a .Skip() to get to the failing scenario easier when debugging.
return new((
// Explicitly not supporting offset arrays
if (value.Item1 is Array array && (array.GetLowerBound(0) != 0))
{
continue;
}

data.Add(value);
}

// Doing two steps for debugging purposes. Can add a .Skip() to get to the failing scenario.

TheoryData<object, TypeSerializableValue[]> theoryData = [];

foreach (var item in data.Skip(0))
{
theoryData.Add(item.Object, item.Serialized);
}

return theoryData;
from value in BinaryFormatterTests.RawSerializableObjects()
where value.Item1 is not Array array || array.GetLowerBound(0) == 0
select value).ToArray());
}

public static TheoryData<object, FormatterAssemblyStyle, FormatterTypeStyle> BasicObjectsRoundtrip_MemberData()
public static EnumerableTupleTheoryData<object, FormatterAssemblyStyle, FormatterTypeStyle> BasicObjectsRoundtrip_MemberData()
{
List<(object Object, FormatterAssemblyStyle AssemblyStyle, FormatterTypeStyle TypeStyle)> data = new(7000);

foreach (object[]? record in SerializableObjects())
{
foreach (FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple })
{
foreach (FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString })
{
data.Add((record[0], assemblyFormat, typeFormat));
}
}
}

TheoryData<object, FormatterAssemblyStyle, FormatterTypeStyle> theoryData = [];

foreach (var item in data.Skip(0))
{
theoryData.Add(item.Object, item.AssemblyStyle, item.TypeStyle);
}

return theoryData;
return new((
// Explicitly not supporting offset arrays
from value in BinaryFormatterTests.RawSerializableObjects()
from FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple }
from FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString }
where value.Item1 is not Array array || array.GetLowerBound(0) == 0
select (value.Item1, assemblyFormat, typeFormat)).ToArray());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,51 @@
using System.Runtime.Serialization;
using FormatTests.Common.TestTypes;
using System.Text.Json;
using System.Collections.Concurrent;

namespace BinaryFormatTests.FormatterTests;

public static class EqualityExtensions
{
private static readonly ConcurrentDictionary<Type, MethodInfo?> s_extensionMethods = new();

private static readonly (MethodInfo Method, string FirstParameterName)[] s_equalityMethods =
(
from method in typeof(EqualityExtensions).GetMethods()!
where method.Name == "IsEqual" && method.IsGenericMethodDefinition
let parameters = method.GetParameters()
where parameters.Length == 3
select (method, parameters[0].ParameterType.Name)
).ToArray();

private static MethodInfo? GetExtensionMethod(Type extendedType)
{
if (s_extensionMethods.TryGetValue(extendedType, out MethodInfo? existing))
{
return existing;
}

if (extendedType.IsGenericType)
{
IEnumerable<MethodInfo>? x = typeof(EqualityExtensions).GetMethods()
?.Where(m =>
m.Name == "IsEqual" &&
m.GetParameters().Length == 3 &&
m.IsGenericMethodDefinition);

MethodInfo? method = typeof(EqualityExtensions).GetMethods()
?.SingleOrDefault(m =>
m.Name == "IsEqual" &&
m.GetParameters().Length == 3 &&
m.GetParameters()[0].ParameterType.Name == extendedType.Name &&
m.IsGenericMethodDefinition);
MethodInfo? method =
(
from m in s_equalityMethods
where m.FirstParameterName == extendedType.Name
select m.Method
).SingleOrDefault();

// If extension method found, make it generic and return
if (method is not null)
return method.MakeGenericMethod(extendedType.GenericTypeArguments[0]);
{
return s_extensionMethods.GetOrAdd(
extendedType,
method.MakeGenericMethod(extendedType.GenericTypeArguments[0]));
}
}

return typeof(EqualityExtensions).GetMethod("IsEqual", [extendedType, extendedType, typeof(bool)]);
return s_extensionMethods.GetOrAdd(
extendedType,
typeof(EqualityExtensions).GetMethod("IsEqual", [extendedType, extendedType, typeof(bool)]));
}

public static void CheckEquals(object? objA, object? objB, bool isSamePlatform = true)
Expand Down

0 comments on commit 8d62cf5

Please sign in to comment.