Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for checking TestCase<> parameters #773

Merged
merged 4 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions documentation/NUnit1001.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public void SampleTest(int numberValue)
{
Assert.That(numberValue, Is.EqualTo(1));
}

[TestCase<double>(42)]
public void SampleTest(int numberValue)
{
Assert.That(numberValue, Is.EqualTo(1));
}
```

### Problem
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<LangVersion>preview</LangVersion>
<!-- Counter intuitive, but this only enabled the pre-shipped analyzer, we configure them below -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
Expand Down
3 changes: 2 additions & 1 deletion src/nunit.analyzers.tests/SetUpFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis.CSharp;
using NUnit.Framework;

#if NUNIT4
Expand All @@ -16,7 +17,7 @@ public void SetDefaults()
Settings.Default = Settings.Default
#if NUNIT4
.WithMetadataReferences(MetadataReferences.Transitive(typeof(Assert), typeof(ClassicAssert)))
.WithParseOption(Settings.Default.ParseOptions.WithPreprocessorSymbols("NUNIT4"));
.WithParseOption(new CSharpParseOptions(LanguageVersion.Preview).WithPreprocessorSymbols("NUNIT4"));
#else
.WithMetadataReferences(MetadataReferences.Transitive(typeof(Assert)));
#endif
Expand Down
120 changes: 120 additions & 0 deletions src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,57 @@ namespace NUnit.Analyzers.Tests.TestCaseUsage
[TestFixture]
public sealed class TestCaseUsageAnalyzerTests
{
#if NUNIT4
#if NET6_0_OR_GREATER
// This can go once NUnit 4.2.0 is released and we update our reference.
private const string GenericTestCaseAttributeSource = """
using System;

namespace NUnit.Framework
{
#pragma warning disable CA1019 // Define accessors for attribute arguments

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
/// <typeparam name="T">The type of the argument for the test case.</typeparam>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class TestCaseAttribute<T> : TestCaseAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TestCaseAttribute{T}"/> class.
/// </summary>
/// <param name="argument">The argument for the test case.</param>
public TestCaseAttribute(T argument)
: base(new object?[] { argument })
{
this.TypeArgs = new[] { typeof(T) };
}
}

/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
/// <typeparam name="T">The type of the argument for the test case.</typeparam>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class TestCaseAttribute<T1, T2> : TestCaseAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="TestCaseAttribute{T1,T2}"/> class.
/// </summary>
/// <param name="argument">The argument for the test case.</param>
public TestCaseAttribute(T1 argument1, T2 argument2)
: base(new object?[] { argument1, argument2 })
{
this.TypeArgs = new[] { typeof(T1), typeof(T2) };
}
}
}

""";
#endif
#endif

private readonly DiagnosticAnalyzer analyzer = new TestCaseUsageAnalyzer();

private static IEnumerable<TestCaseData> SpecialConversions
Expand Down Expand Up @@ -757,6 +808,75 @@ public void TestWithGenericParameter<T>(T arg1) { }
}

#if NUNIT4
#if NET6_0_OR_GREATER
[Test]
public void AnalyzeWhenArgumentIsCorrectGenericTypeParameter()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
class AnalyzeWhenArgumentIsGenericTypeParameter
{
[TestCase<byte>(2)]
public void Test(byte a) { }
}");
RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode);
}

[Test]
public void AnalyzeWhenArgumentsAreCorrectGenericTypeParameter()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
class AnalyzeWhenArgumentIsGenericTypeParameter
{
[TestCase<byte, uint>(2, 3)]
public void Test(byte a, uint b) { }
}");
RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode);
}

[Test]
public void AnalyzeWhenArgumentIsWrongGenericTypeParameter()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
class AnalyzeWhenArgumentIsGenericTypeParameter
{
[TestCase<double>(↓2)]
public void Test(int a) { }
}");
RoslynAssert.Diagnostics(this.analyzer,
ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestCaseParameterTypeMismatchUsage),
GenericTestCaseAttributeSource, testCode);
}

[Test]
public void AnalyzeWhenArgumentsAreWrongGenericTypeParameter()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
class AnalyzeWhenArgumentIsGenericTypeParameter
{
[TestCase<double, int>(↓2, ↓3)]
public void Test(int a, uint b) { }
}");
RoslynAssert.Diagnostics(this.analyzer,
ExpectedDiagnostic.Create(AnalyzerIdentifiers.TestCaseParameterTypeMismatchUsage),
GenericTestCaseAttributeSource, testCode);
}

[Test]
public void AnalyzeWhenTestMethodHasTypeParameterArgumentTypeAndGenericTestCase()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
public sealed class AnalyzeWhenTestMethodHasTypeParameterArgumentType
{
[TestCase<int>(1)]
[TestCase<uint>(1)]
[TestCase<float>(1)]
[TestCase<double>(1)]
public void TestWithGenericParameter<T>(T arg1) { }
}");
RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode);
}
#endif

[Test]
public void AnalyzeWhenTestMethodHasImplicitlySuppliedCancellationTokenParameterDueToCancelAfterOnMethod()
{
Expand Down
4 changes: 3 additions & 1 deletion src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ private static void AnalyzeMethod(

var testCaseAttributes = methodAttributes
.Where(a => a.ApplicationSyntaxReference is not null
&& SymbolEqualityComparer.Default.Equals(a.AttributeClass, testCaseType));
&& (SymbolEqualityComparer.Default.Equals(a.AttributeClass, testCaseType) ||
(a.AttributeClass is not null && a.AttributeClass.IsGenericType &&
SymbolEqualityComparer.Default.Equals(a.AttributeClass.BaseType, testCaseType))));

foreach (var attribute in testCaseAttributes)
{
Expand Down