Skip to content

Commit

Permalink
Add support for RegisterXunitSerializer to xUnit1045/1047
Browse files Browse the repository at this point in the history
  • Loading branch information
bradwilson committed Oct 27, 2024
1 parent 2b07658 commit 2dead6c
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,54 @@ public void Serialize(IXunitSerializationInfo info) {{ }}
await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source);
}

[Theory]
[InlineData("CustomSerialized")]
[InlineData("CustomSerializedDerived")]
public async Task IXunitSerializerValue_DoesNotTrigger(string type)
{
var source = string.Format(/* lang=c#-test */ """
#nullable enable
using System;
using System.Collections.Generic;
using Xunit;
using Xunit.Sdk;
[assembly: RegisterXunitSerializer(typeof(CustomSerializer), typeof(ICustomSerialized))]
public class MyClass {{
public IEnumerable<TheoryDataRow> MyMethod() {{
var value = new {0}();
var defaultValue = default({0});
var nullValue = default({0}?);
var arrayValue = new {0}[0];
yield return new TheoryDataRow(value, defaultValue, nullValue, arrayValue);
yield return new TheoryDataRow<{0}, {0}?, {0}?, {0}[]>(new {0}(), default({0}), default({0}?), new {0}[0]);
}}
}}
public interface ICustomSerialized {{ }}
public class CustomSerialized : ICustomSerialized {{ }}
public class CustomSerializedDerived : CustomSerialized {{ }}
public class CustomSerializer : IXunitSerializer {{
public object Deserialize(Type type, string serializedValue) =>
throw new NotImplementedException();
public bool IsSerializable(Type type, object? value) =>
true;
public string Serialize(object value) =>
throw new NotImplementedException();
}}
""", type);

await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source);
}

[Theory]
[InlineData("Delegate", "Delegate?", "Delegate?")]
[InlineData("Func<int>", "Func<int>?", "Func<int>?")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Verify = CSharpVerifier<Xunit.Analyzers.TheoryDataTypeArgumentsShouldBeSerializable>;

Expand Down Expand Up @@ -253,6 +254,52 @@ public void Serialize(IXunitSerializationInfo info) {{ }}
""", member, attribute, type, ns);
}

[Theory]
[MemberData(nameof(TheoryDataMembers), "ICustomSerialized")]
[MemberData(nameof(TheoryDataMembers), "CustomSerialized")]
[MemberData(nameof(TheoryDataMembers), "CustomSerializedDerived")]
public async Task GivenTheory_WithIXunitSerializerTheoryDataMember_DoesNotTrigger(
string member,
string attribute,
string type)
{
var source = string.Format(/* lang=c#-test */ """
using System;
using Xunit;
using Xunit.Sdk;
[assembly: RegisterXunitSerializer(typeof(CustomSerializer), typeof(ICustomSerialized))]
public class TestClass {{
{0}
[Theory]
[{1}]
public void TestMethod({2} parameter) {{ }}
}}
public interface ICustomSerialized {{ }}
public class CustomSerialized : ICustomSerialized {{ }}
public class CustomSerializedDerived : CustomSerialized {{ }}
public class CustomSerializer : IXunitSerializer {{
public object Deserialize(Type type, string serializedValue) =>
throw new NotImplementedException();
public bool IsSerializable(Type type, object? value) =>
true;
public string Serialize(object value) =>
throw new NotImplementedException();
}}
""", member, attribute, type
);

await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source);
}

[Theory]
[MemberData(nameof(TheoryDataMembers), "Delegate")]
[MemberData(nameof(TheoryDataMembers), "Delegate[]")]
Expand Down
1 change: 1 addition & 0 deletions src/xunit.analyzers/Utility/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public static class Xunit
public const string LongLivedMarshalByRefObject_Execution_V2 = "Xunit.LongLivedMarshalByRefObject";
public const string LongLivedMarshalByRefObject_RunnerUtility = "Xunit.Sdk.LongLivedMarshalByRefObject";
public const string MemberDataAttribute = "Xunit.MemberDataAttribute";
public const string RegisterXunitSerializerAttribute_V3 = "Xunit.Sdk.RegisterXunitSerializerAttribute";
public const string TestContext_V3 = "Xunit.TestContext";
public const string TheoryAttribute = "Xunit.TheoryAttribute";
public const string TheoryData = "Xunit.TheoryData";
Expand Down
3 changes: 3 additions & 0 deletions src/xunit.analyzers/Utility/SerializabilityAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public Serializability AnalayzeSerializability(ITypeSymbol type)
|| type.Equals(typeSymbols.TimeOnly, SymbolEqualityComparer.Default))
return Serializability.AlwaysSerializable;

if (typeSymbols.TypesWithCustomSerializers.Any(t => t.IsAssignableFrom(type)))
return Serializability.AlwaysSerializable;

if (type.TypeKind == TypeKind.Class && !type.IsSealed)
return Serializability.PossiblySerializable;

Expand Down
19 changes: 19 additions & 0 deletions src/xunit.analyzers/Utility/SerializableTypeSymbols.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Xunit.Analyzers;
Expand All @@ -16,6 +18,7 @@ public sealed class SerializableTypeSymbols
readonly Lazy<INamedTypeSymbol?> timeSpan;
readonly Lazy<INamedTypeSymbol?> traitDictionary;
readonly Lazy<INamedTypeSymbol?> type;
readonly Lazy<ImmutableArray<INamedTypeSymbol>> typesWithCustomSerializers;

SerializableTypeSymbols(
Compilation compilation,
Expand All @@ -40,6 +43,21 @@ public sealed class SerializableTypeSymbols
timeSpan = new(() => TypeSymbolFactory.TimeSpan(compilation));
traitDictionary = new(() => GetTraitDictionary(compilation));
type = new(() => TypeSymbolFactory.Type(compilation));
typesWithCustomSerializers = new(() =>
{
var registerXunitSerializer = TypeSymbolFactory.RegisterXunitSerializerAttribute_V3(compilation);
if (registerXunitSerializer is null)
return ImmutableArray<INamedTypeSymbol>.Empty;

return
compilation
.Assembly
.GetAttributes()
.Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, registerXunitSerializer) && a.ConstructorArguments.Length > 1 && a.ConstructorArguments[1].Kind == TypedConstantKind.Array)
.SelectMany(a => a.ConstructorArguments[1].Values.Select(v => v.Value as INamedTypeSymbol))
.WhereNotNull()
.ToImmutableArray();
});

ClassDataAttribute = classDataAttribute;
DataAttribute = dataAttribute;
Expand All @@ -60,6 +78,7 @@ public sealed class SerializableTypeSymbols
public INamedTypeSymbol? TimeSpan => timeSpan.Value;
public INamedTypeSymbol? TraitDictionary => traitDictionary.Value;
public INamedTypeSymbol? Type => type.Value;
public ImmutableArray<INamedTypeSymbol> TypesWithCustomSerializers => typesWithCustomSerializers.Value;

public static SerializableTypeSymbols? Create(
Compilation compilation,
Expand Down
3 changes: 3 additions & 0 deletions src/xunit.analyzers/Utility/TypeSymbolFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ public static IArrayTypeSymbol ObjectArray(Compilation compilation) =>
public static INamedTypeSymbol? OptionalAttribute(Compilation compilation) =>
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Runtime.InteropServices.OptionalAttribute");

public static INamedTypeSymbol? RegisterXunitSerializerAttribute_V3(Compilation compilation) =>
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName(Constants.Types.Xunit.RegisterXunitSerializerAttribute_V3);

public static INamedTypeSymbol? SortedSetOfT(Compilation compilation) =>
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Collections.Generic.SortedSet`1");

Expand Down

0 comments on commit 2dead6c

Please sign in to comment.