Update xUnit2018 to vary its message based on whether assertion library supports exactMatch

This commit is contained in:
Brad Wilson 2024-11-03 10:57:56 -08:00
Родитель 350ab935b5
Коммит 6b8347c422
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 0B7BD15AD1EC5FDE
3 изменённых файлов: 87 добавлений и 26 удалений

Просмотреть файл

@ -1,20 +1,29 @@
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Xunit;
using Xunit.Analyzers;
using Verify = CSharpVerifier<Xunit.Analyzers.AssertIsTypeShouldNotBeUsedForAbstractType>;
using Verify_v2_Pre2_9_3 = CSharpVerifier<AssertIsTypeShouldNotBeUsedForAbstractTypeTests.Analyzer_v2_Pre2_9_3>;
using Verify_v3_Pre0_6_0 = CSharpVerifier<AssertIsTypeShouldNotBeUsedForAbstractTypeTests.Analyzer_v3_Pre0_6_0>;
public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
{
public static TheoryData<string, string> Methods = new()
public static TheoryData<string> Methods = ["IsType", "IsNotType"];
public static TheoryData<string, string, bool> MethodsWithReplacements = new()
{
{ "IsType", "IsAssignableFrom" },
{ "IsNotType", "IsNotAssignableFrom" },
{ "IsType", "Assert.IsAssignableFrom", false },
{ "IsType", "exactMatch: false", true },
{ "IsNotType", "Assert.IsNotAssignableFrom", false },
{ "IsNotType", "exactMatch: false", true },
};
[Theory]
[MemberData(nameof(Methods))]
[MemberData(nameof(MethodsWithReplacements))]
public async Task Interface_Triggers(
string method,
string replacement)
string replacement,
bool supportsInexactTypeAssertions)
{
var source = string.Format(/* lang=c#-test */ """
using System;
@ -28,14 +37,18 @@ public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
""", method);
var expected = Verify.Diagnostic().WithLocation(0).WithArguments("interface", "System.IDisposable", replacement);
await Verify.VerifyAnalyzer(source, expected);
if (supportsInexactTypeAssertions)
await Verify.VerifyAnalyzer(source, expected);
else
{
await Verify_v2_Pre2_9_3.VerifyAnalyzer(source, expected);
await Verify_v3_Pre0_6_0.VerifyAnalyzer(source, expected);
}
}
[Theory]
[MemberData(nameof(Methods))]
public async Task Interface_WithExactMatchFlag_TriggersForTrue(
string method,
string replacement)
public async Task Interface_WithExactMatchFlag_TriggersForTrue(string method)
{
// We can only trigger when we know the literal true is being used; anything else,
// we let the runtime figure it out.
@ -48,20 +61,25 @@ public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
var flag = true;
{{|#0:Assert.{0}<IDisposable>(new object(), true)|}};
{{|#1:Assert.{0}<IDisposable>(new object(), exactMatch: true)|}};
Assert.{0}<IDisposable>(new object(), flag);
}}
}}
""", method);
var expected = Verify.Diagnostic().WithLocation(0).WithArguments("interface", "System.IDisposable", replacement);
var expected = new[] {
Verify.Diagnostic().WithLocation(0).WithArguments("interface", "System.IDisposable", "exactMatch: false"),
Verify.Diagnostic().WithLocation(1).WithArguments("interface", "System.IDisposable", "exactMatch: false"),
};
await Verify.VerifyAnalyzer(source, expected);
}
[Theory]
[MemberData(nameof(Methods))]
[MemberData(nameof(MethodsWithReplacements))]
public async Task AbstractClass_Triggers(
string method,
string replacement)
string replacement,
bool supportsInexactTypeAssertions)
{
var source = string.Format(/* lang=c#-test */ """
using System.IO;
@ -75,15 +93,21 @@ public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
""", method);
var expected = Verify.Diagnostic().WithLocation(0).WithArguments("abstract class", "System.IO.Stream", replacement);
await Verify.VerifyAnalyzer(source, expected);
if (supportsInexactTypeAssertions)
await Verify.VerifyAnalyzer(source, expected);
else
{
await Verify_v2_Pre2_9_3.VerifyAnalyzer(source, expected);
await Verify_v3_Pre0_6_0.VerifyAnalyzer(source, expected);
}
}
[Theory]
[MemberData(nameof(Methods))]
public async Task AbstractClass_WithExactMatchFlag_TriggersForTrue(
string method,
string replacement)
public async Task AbstractClass_WithExactMatchFlag_TriggersForTrue(string method)
{
// We can only trigger when we know the literal true is being used; anything else,
// we let the runtime figure it out.
var source = string.Format(/* lang=c#-test */ """
using System.IO;
using Xunit;
@ -93,20 +117,25 @@ public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
var flag = true;
{{|#0:Assert.{0}<Stream>(new object(), true)|}};
{{|#1:Assert.{0}<Stream>(new object(), exactMatch: true)|}};
Assert.{0}<Stream>(new object(), flag);
}}
}}
""", method);
var expected = Verify.Diagnostic().WithLocation(0).WithArguments("abstract class", "System.IO.Stream", replacement);
var expected = new[] {
Verify.Diagnostic().WithLocation(0).WithArguments("abstract class", "System.IO.Stream", "exactMatch: false"),
Verify.Diagnostic().WithLocation(1).WithArguments("abstract class", "System.IO.Stream", "exactMatch: false"),
};
await Verify.VerifyAnalyzer(source, expected);
}
[Theory]
[MemberData(nameof(Methods))]
[MemberData(nameof(MethodsWithReplacements))]
public async Task UsingStatic_Triggers(
string method,
string replacement)
string replacement,
bool supportsInexactTypeAssertions)
{
var source = string.Format(/* lang=c#-test */ """
using System;
@ -120,25 +149,49 @@ public class AssertIsTypeShouldNotBeUsedForAbstractTypeTests
""", method);
var expected = Verify.Diagnostic().WithLocation(0).WithArguments("interface", "System.IDisposable", replacement);
await Verify.VerifyAnalyzer(source, expected);
if (supportsInexactTypeAssertions)
await Verify.VerifyAnalyzer(source, expected);
else
{
await Verify_v2_Pre2_9_3.VerifyAnalyzer(source, expected);
await Verify_v3_Pre0_6_0.VerifyAnalyzer(source, expected);
}
}
[Theory]
[MemberData(nameof(Methods))]
public async Task NonAbstractClass_DoesNotTrigger(
string method,
string _)
public async Task NonAbstractClass_DoesNotTrigger(string method)
{
var source = string.Format(/* lang=c#-test */ """
using Xunit;
class TestClass {{
void TestMethod() {{
var flag = true;
Assert.{0}<string>(new object());
Assert.{0}<string>(new object(), flag);
Assert.{0}<string>(new object(), exactMatch: flag);
Assert.{0}<string>(new object(), true);
Assert.{0}<string>(new object(), exactMatch: true);
Assert.{0}<string>(new object(), false);
Assert.{0}<string>(new object(), exactMatch: false);
}}
}}
""", method);
await Verify.VerifyAnalyzer(source);
}
internal class Analyzer_v2_Pre2_9_3 : AssertIsTypeShouldNotBeUsedForAbstractType
{
protected override XunitContext CreateXunitContext(Compilation compilation) =>
XunitContext.ForV2(compilation, new Version(2, 9, 2));
}
internal class Analyzer_v3_Pre0_6_0 : AssertIsTypeShouldNotBeUsedForAbstractType
{
protected override XunitContext CreateXunitContext(Compilation compilation) =>
XunitContext.ForV3(compilation, new Version(0, 5, 999));
}
}

Просмотреть файл

@ -174,7 +174,7 @@ public static partial class Descriptors
"Do not compare an object's exact type to an abstract class or interface",
Assertions,
Warning,
"Do not compare an object's exact type to the {0} '{1}'. Use Assert.{2} instead."
"Do not compare an object's exact type to the {0} '{1}'. Use {2} instead."
);
// Note: X2019 was already covered by X2014, and should not be reused

Просмотреть файл

@ -59,8 +59,16 @@ public class AssertIsTypeShouldNotBeUsedForAbstractType : AssertUsageAnalyzerBas
var typeName = SymbolDisplay.ToDisplayString(type);
if (!ReplacementMethods.TryGetValue(invocationOperation.TargetMethod.Name, out var replacement))
return;
string? replacement;
if (xunitContext.Assert.SupportsInexactTypeAssertions)
replacement = "exactMatch: false";
else
{
if (!ReplacementMethods.TryGetValue(invocationOperation.TargetMethod.Name, out replacement))
return;
replacement = "Assert." + replacement;
}
context.ReportDiagnostic(
Diagnostic.Create(