Skip to content

Commit

Permalink
Add analyzers to check values passed to the TheoryDataRow constructor…
Browse files Browse the repository at this point in the history
… in v3
  • Loading branch information
bradwilson committed Apr 8, 2024
1 parent cdb4317 commit 68525f8
Show file tree
Hide file tree
Showing 7 changed files with 621 additions and 258 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Verify = CSharpVerifier<Xunit.Analyzers.TheoryDataRowArgumentsShouldBeSerializable>;

public sealed class TheoryDataRowArgumentsShouldBeSerializableTests
{
[Theory]
[InlineData("\"String value\"", "string")]
[InlineData("'a'", "char")]
[InlineData("(byte)42", "byte")]
[InlineData("(sbyte)42", "sbyte")]
[InlineData("(short)42", "short")]
[InlineData("(ushort)42", "ushort")]
[InlineData("42", "int")]
[InlineData("42U", "uint")]
[InlineData("42L", "long")]
[InlineData("42UL", "ulong")]
[InlineData("21.12F", "float")]
[InlineData("21.12D", "double")]
[InlineData("21.12M", "decimal")]
[InlineData("true", "bool")]
[InlineData("DateTime.Now", "DateTime")]
[InlineData("DateTimeOffset.Now", "DateTimeOffset")]
[InlineData("TimeSpan.Zero", "TimeSpan")]
[InlineData("BigInteger.One", "BigInteger")]
[InlineData("typeof(TheoryDataRow)", "Type")]
[InlineData("ConsoleColor.Red", "ConsoleColor")]
[InlineData("new Dictionary<string, List<string>>()", "Dictionary<string, List<string>>")]
#if NET6_0_OR_GREATER && ROSLYN_4_4_OR_GREATER
[InlineData("DateOnly.MinValue", "DateOnly")]
[InlineData("TimeOnly.MinValue", "TimeOnly")]
#endif
public async void IntrinsicallySerializableValue_DoesNotTrigger(
string value,
string type)
{
var source = $@"
#nullable enable
using System;
using System.Collections.Generic;
using System.Numerics;
using Xunit.Sdk;
public class MyClass {{
public IEnumerable<TheoryDataRow> MyMethod() {{
var value = {value};
var defaultValue = default({type});
var nullValue = default({type}?);
var arrayValue = new {type}[0];
yield return new TheoryDataRow(value, defaultValue, nullValue, arrayValue);
yield return new TheoryDataRow({value}, default({type}), default({type}?), new {type}[0]);
}}
}}";

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

[Theory]
[InlineData("SerializableClass")]
[InlineData("SerializableStruct")]
public async void IXunitSerializableValue_DoesNotTrigger(string type)
{
var source = $@"
#nullable enable
using System.Collections.Generic;
using Xunit.Sdk;
public class MyClass {{
public IEnumerable<TheoryDataRow> MyMethod() {{
var value = new {type}();
var defaultValue = default({type});
var nullValue = default({type}?);
var arrayValue = new {type}[0];
yield return new TheoryDataRow(value, defaultValue, nullValue, arrayValue);
yield return new TheoryDataRow(new {type}(), default({type}), default({type}?), new {type}[0]);
}}
}}
public interface ISerializableInterface : IXunitSerializable {{ }}
public class SerializableClass : ISerializableInterface {{
public void Deserialize(IXunitSerializationInfo info) {{ }}
public void Serialize(IXunitSerializationInfo info) {{ }}
}}
public struct SerializableStruct : ISerializableInterface {{
public void Deserialize(IXunitSerializationInfo info) {{ }}
public void Serialize(IXunitSerializationInfo info) {{ }}
}}";

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

[Theory]
[InlineData("Delegate", "Delegate?", "Delegate?")]
[InlineData("Func<int>", "Func<int>?", "Func<int>?")]
[InlineData("NonSerializableSealedClass", "NonSerializableSealedClass?", "NonSerializableSealedClass?")]
[InlineData("NonSerializableStruct", "NonSerializableStruct", "NonSerializableStruct?")]
public async void KnownNonSerializableValue_Triggers1046(
string type,
string defaultValueType,
string nullValueType)
{
var source = $@"
#nullable enable
using System;
using System.Collections.Generic;
using Xunit.Sdk;
public class MyClass {{
public IEnumerable<TheoryDataRow> MyMethod() {{
var defaultValue = default({type});
var nullValue = default({type}?);
var arrayValue = new {type}[0];
yield return new TheoryDataRow(defaultValue, nullValue, arrayValue);
yield return new TheoryDataRow(default({type}), default({type}?), new {type}[0]);
}}
}}
public sealed class NonSerializableSealedClass {{ }}
public struct NonSerializableStruct {{ }}";

var expected = new[] {
Verify
.Diagnostic("xUnit1046")
.WithSpan(14, 40, 14, 52)
.WithArguments("defaultValue", defaultValueType),
Verify
.Diagnostic("xUnit1046")
.WithSpan(14, 54, 14, 63)
.WithArguments("nullValue", nullValueType),
Verify
.Diagnostic("xUnit1046")
.WithSpan(14, 65, 14, 75)
.WithArguments("arrayValue", $"{type}[]"),
Verify
.Diagnostic("xUnit1046")
.WithSpan(15, 40, 15, 49 + type.Length)
.WithArguments($"default({type})", defaultValueType),
Verify
.Diagnostic("xUnit1046")
.WithSpan(15, 51 + type.Length, 15, 61 + type.Length * 2)
.WithArguments($"default({type}?)", nullValueType),
Verify
.Diagnostic("xUnit1046")
.WithSpan(15, 63 + type.Length * 2, 15, 70 + type.Length * 3)
.WithArguments($"new {type}[0]", $"{type}[]"),
};

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

[Theory]
[InlineData("object")]
[InlineData("Array")]
[InlineData("ValueType")]
[InlineData("IEnumerable")]
[InlineData("IEnumerable<int>")]
[InlineData("Dictionary<int, string>")]
[InlineData("IPossiblySerializableInterface")]
[InlineData("PossiblySerializableUnsealedClass")]
public async void MaybeNonSerializableValue_Triggers1047(string type)
{
var source = $@"
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using Xunit.Sdk;
public class MyClass {{
public IEnumerable<TheoryDataRow> MyMethod() {{
var defaultValue = default({type});
var nullValue = default({type}?);
var arrayValue = new {type}[0];
yield return new TheoryDataRow(defaultValue, nullValue, arrayValue);
yield return new TheoryDataRow(default({type}), default({type}?), new {type}[0]);
}}
}}
public interface IPossiblySerializableInterface {{ }}
public class PossiblySerializableUnsealedClass {{ }}";

var expected = new[] {
Verify
.Diagnostic("xUnit1047")
.WithSpan(15, 40, 15, 52)
.WithArguments("defaultValue", $"{type}?"),
Verify
.Diagnostic("xUnit1047")
.WithSpan(15, 54, 15, 63)
.WithArguments("nullValue", $"{type}?"),
Verify
.Diagnostic("xUnit1047")
.WithSpan(15, 65, 15, 75)
.WithArguments("arrayValue", $"{type}[]"),
Verify
.Diagnostic("xUnit1047")
.WithSpan(16, 40, 16, 49 + type.Length)
.WithArguments($"default({type})", $"{type}?"),
Verify
.Diagnostic("xUnit1047")
.WithSpan(16, 51 + type.Length, 16, 61 + type.Length * 2)
.WithArguments($"default({type}?)", $"{type}?"),
Verify
.Diagnostic("xUnit1047")
.WithSpan(16, 63 + type.Length * 2, 16, 70 + type.Length * 3)
.WithArguments($"new {type}[0]", $"{type}[]"),
};

await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source, expected);
}
}
18 changes: 16 additions & 2 deletions src/xunit.analyzers/Utility/Descriptors.xUnit1xxx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,23 @@ public static partial class Descriptors
"The type argument {0} might not be serializable, which may cause Test Explorer to not enumerate individual data rows. Consider using a type that is known to be serializable."
);

// Placeholder for rule X1046
public static DiagnosticDescriptor X1046_AvoidUsingTheoryDataRowArgumentsThatAreNotSerializable { get; } =
Diagnostic(
"xUnit1046",
"Avoid using TheoryDataRow arguments that are not serializable",
Usage,
Info,
"The argument '{0}' of type '{1}' is not serializable, which will cause Test Explorer to not enumerate individual data rows. Consider using a value that is known to be serializable."
);

// Placeholder for rule X1047
public static DiagnosticDescriptor X1047_AvoidUsingTheoryDataRowArgumentsThatMightNotBeSerializable { get; } =
Diagnostic(
"xUnit1047",
"Avoid using TheoryDataRow arguments that might not be serializable",
Usage,
Info,
"The argument '{0}' of type '{1}' might not be serializable, which may cause Test Explorer to not enumerate individual data rows. Consider using a value that is known to be serializable."
);

// Placeholder for rule X1048

Expand Down
8 changes: 8 additions & 0 deletions src/xunit.analyzers/Utility/Serializability.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Xunit.Analyzers;

public enum Serializability
{
NeverSerializable,
PossiblySerializable,
AlwaysSerializable
}
Loading

0 comments on commit 68525f8

Please sign in to comment.