From cb4e82d3a6007134f0f12561e62df0233dcec7f3 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Fri, 26 Jul 2024 16:33:26 +0800 Subject: [PATCH 1/4] Add support for checking TestCase<> parameters --- src/Directory.Build.props | 2 +- src/nunit.analyzers.tests/SetUpFixture.cs | 3 +- .../TestCaseUsageAnalyzerTests.cs | 118 ++++++++++++++++++ .../TestCaseUsage/TestCaseUsageAnalyzer.cs | 4 +- 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1b252254..8f98c700 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,7 +11,7 @@ true enable - 10.0 + 11.0 false false diff --git a/src/nunit.analyzers.tests/SetUpFixture.cs b/src/nunit.analyzers.tests/SetUpFixture.cs index 9a248c2d..8b7b02a5 100644 --- a/src/nunit.analyzers.tests/SetUpFixture.cs +++ b/src/nunit.analyzers.tests/SetUpFixture.cs @@ -1,4 +1,5 @@ using Gu.Roslyn.Asserts; +using Microsoft.CodeAnalysis.CSharp; using NUnit.Framework; #if NUNIT4 @@ -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 diff --git a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs index c8dc554a..177ebbd0 100644 --- a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs +++ b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs @@ -15,6 +15,55 @@ namespace NUnit.Analyzers.Tests.TestCaseUsage [TestFixture] public sealed class TestCaseUsageAnalyzerTests { +#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 + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + /// The type of the argument for the test case. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public sealed class TestCaseAttribute : TestCaseAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The argument for the test case. + public TestCaseAttribute(T argument) + : base(new object?[] { argument }) + { + this.TypeArgs = new[] { typeof(T) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + /// The type of the argument for the test case. + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public sealed class TestCaseAttribute : TestCaseAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The argument for the test case. + public TestCaseAttribute(T1 argument1, T2 argument2) + : base(new object?[] { argument1, argument2 }) + { + this.TypeArgs = new[] { typeof(T1), typeof(T2) }; + } + } + } + + """; +#endif + private readonly DiagnosticAnalyzer analyzer = new TestCaseUsageAnalyzer(); private static IEnumerable SpecialConversions @@ -757,6 +806,75 @@ public void TestWithGenericParameter(T arg1) { } } #if NUNIT4 +#if NET6_0_OR_GREATER + [Test] + public void AnalyzeWhenArgumentIsCorrectGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(2)] + public void Test(byte a) { } + }"); + RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode); + } + + [Test] + public void AnalyzeWhenArgumentsAreCorrectGenericTypeParameter() + { + var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@" + class AnalyzeWhenArgumentIsGenericTypeParameter + { + [TestCase(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(↓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(↓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(1)] + [TestCase(1)] + [TestCase(1)] + [TestCase(1)] + public void TestWithGenericParameter(T arg1) { } + }"); + RoslynAssert.Valid(this.analyzer, GenericTestCaseAttributeSource, testCode); + } +#endif + [Test] public void AnalyzeWhenTestMethodHasImplicitlySuppliedCancellationTokenParameterDueToCancelAfterOnMethod() { diff --git a/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs b/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs index 4d8d690b..b0c1d42f 100644 --- a/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs +++ b/src/nunit.analyzers/TestCaseUsage/TestCaseUsageAnalyzer.cs @@ -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) { From 093c90ad04fda5f9d77af9a0bec2c17178b500a1 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Fri, 26 Jul 2024 16:49:42 +0800 Subject: [PATCH 2/4] Add generic example to documentation --- documentation/NUnit1001.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation/NUnit1001.md b/documentation/NUnit1001.md index 7c56b5c1..356903e0 100644 --- a/documentation/NUnit1001.md +++ b/documentation/NUnit1001.md @@ -29,6 +29,12 @@ public void SampleTest(int numberValue) { Assert.That(numberValue, Is.EqualTo(1)); } + +[TestCase(42)] +public void SampleTest(int numberValue) +{ + Assert.That(numberValue, Is.EqualTo(1)); +} ``` ### Problem From f017e6dae59e7a24ffb13e1a8c78088c63211775 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Fri, 26 Jul 2024 17:33:29 +0800 Subject: [PATCH 3/4] C#11 is not available for .NET6.0 This can be reverted when NUnit 4.2 is referenced. --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8f98c700..6f276f81 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,7 +11,7 @@ true enable - 11.0 + preview false false From 7c962001c492e26feb6ecf4047818eeda11e74b0 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Fri, 26 Jul 2024 17:43:29 +0800 Subject: [PATCH 4/4] Field is only needed for NUNIT4 tests --- .../TestCaseUsage/TestCaseUsageAnalyzerTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs index 177ebbd0..bc02ca53 100644 --- a/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs +++ b/src/nunit.analyzers.tests/TestCaseUsage/TestCaseUsageAnalyzerTests.cs @@ -15,6 +15,7 @@ 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 = """ @@ -62,6 +63,7 @@ public TestCaseAttribute(T1 argument1, T2 argument2) } """; +#endif #endif private readonly DiagnosticAnalyzer analyzer = new TestCaseUsageAnalyzer();