Skip to content

Commit

Permalink
Merge pull request #637 from manfred-brands/issue634_IAsyncEnumerable
Browse files Browse the repository at this point in the history
Allow IAsyncEnumerable for ValueSource/TestCaseSource
  • Loading branch information
mikkelbu committed Nov 18, 2023
2 parents 9d1b382 + 8a4ee92 commit 2214c07
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 52 deletions.
20 changes: 10 additions & 10 deletions documentation/NUnit1015.md
@@ -1,6 +1,6 @@
# NUnit1015

## The source type does not implement IEnumerable
## The source type does not implement I(Async)Enumerable

| Topic | Value
| :-- | :--
Expand All @@ -12,7 +12,7 @@

## Description

The source type must implement IEnumerable in order to provide test cases.
The source type must implement I(Async)Enumerable in order to provide test cases.

## Motivation

Expand Down Expand Up @@ -45,13 +45,13 @@ class DivideCases

### Explanation

In the sample above, the class `DivideCases` does not implement `IEnumerable`.
In the sample above, the class `DivideCases` does not implement `IEnumerable` nor `IAsyncEnumerable`

However, source types specified by `TestCaseSource` [must implement `IEnumerable`](https://github.com/nunit/docs/wiki/TestCaseSource-Attribute).
However, source types specified by `TestCaseSource` [must implement `IEnumerable` or `IAsyncEnumerable`](https://github.com/nunit/docs/wiki/TestCaseSource-Attribute).

### Fix

Make the source type implement `IEnumerable`:
Make the source type implement `IEnumerable` or `IAsyncEnumerable`

```csharp
public class MyTestClass
Expand Down Expand Up @@ -84,7 +84,7 @@ Configure the severity per project, for more info see [MSDN](https://learn.micro
### Via .editorconfig file

```ini
# NUnit1015: The source type does not implement IEnumerable
# NUnit1015: The source type does not implement I(Async)Enumerable
dotnet_diagnostic.NUnit1015.severity = chosenSeverity
```

Expand All @@ -93,22 +93,22 @@ where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`,
### Via #pragma directive

```csharp
#pragma warning disable NUnit1015 // The source type does not implement IEnumerable
#pragma warning disable NUnit1015 // The source type does not implement I(Async)Enumerable
Code violating the rule here
#pragma warning restore NUnit1015 // The source type does not implement IEnumerable
#pragma warning restore NUnit1015 // The source type does not implement I(Async)Enumerable
```

Or put this at the top of the file to disable all instances.

```csharp
#pragma warning disable NUnit1015 // The source type does not implement IEnumerable
#pragma warning disable NUnit1015 // The source type does not implement I(Async)Enumerable
```

### Via attribute `[SuppressMessage]`

```csharp
[System.Diagnostics.CodeAnalysis.SuppressMessage("Structure",
"NUnit1015:The source type does not implement IEnumerable",
"NUnit1015:The source type does not implement I(Async)Enumerable",
Justification = "Reason...")]
```
<!-- end generated config severity -->
20 changes: 10 additions & 10 deletions documentation/NUnit1019.md
@@ -1,6 +1,6 @@
# NUnit1019

## The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable
## The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable

| Topic | Value
| :-- | :--
Expand All @@ -12,7 +12,7 @@

## Description

The source specified by the TestCaseSource must return an IEnumerable or a type that implements IEnumerable.
The source specified by the TestCaseSource must return an I(Async)Enumerable or a type that implements I(Async)Enumerable.

## Motivation

Expand All @@ -36,14 +36,14 @@ public class AnalyzeWhenSourceDoesProvideIEnumerable

### Explanation

In the sample above, the source specified by `TestCaseSource` - the field `testCases` - does not return an `IEnumerable` or a type that implements `IEnumerable`,
In the sample above, the source specified by `TestCaseSource` - the field `testCases` - does not return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`,
instead it returns an `int`.

However, sources specified by `TestCaseSource` [must return an `IEnumerable` or a type that implements `IEnumerable`.](https://github.com/nunit/docs/wiki/TestCaseSource-Attribute).
However, sources specified by `TestCaseSource` [must return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`.](https://github.com/nunit/docs/wiki/TestCaseSource-Attribute).

### Fix

Change `testCases` to return an `IEnumerable` or a type that implements `IEnumerable`:
Change `testCases` to return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`:

```csharp
public class AnalyzeWhenSourceDoesProvideIEnumerable
Expand All @@ -67,7 +67,7 @@ Configure the severity per project, for more info see [MSDN](https://learn.micro
### Via .editorconfig file

```ini
# NUnit1019: The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable
# NUnit1019: The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
dotnet_diagnostic.NUnit1019.severity = chosenSeverity
```

Expand All @@ -76,22 +76,22 @@ where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`,
### Via #pragma directive

```csharp
#pragma warning disable NUnit1019 // The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning disable NUnit1019 // The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
Code violating the rule here
#pragma warning restore NUnit1019 // The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning restore NUnit1019 // The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
```

Or put this at the top of the file to disable all instances.

```csharp
#pragma warning disable NUnit1019 // The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning disable NUnit1019 // The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
```

### Via attribute `[SuppressMessage]`

```csharp
[System.Diagnostics.CodeAnalysis.SuppressMessage("Structure",
"NUnit1019:The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable",
"NUnit1019:The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable",
Justification = "Reason...")]
```
<!-- end generated config severity -->
20 changes: 10 additions & 10 deletions documentation/NUnit1024.md
@@ -1,6 +1,6 @@
# NUnit1024

## The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable
## The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable

| Topic | Value
| :-- | :--
Expand All @@ -12,7 +12,7 @@

## Description

The source specified by the ValueSource must return an IEnumerable or a type that implements IEnumerable.
The source specified by the ValueSource must return an I(Async)Enumerable or a type that implements I(Async)Enumerable.

## Motivation

Expand All @@ -36,14 +36,14 @@ public class AnalyzeWhenSourceDoesProvideIEnumerable

### Explanation

In the sample above, the source specified by `ValueSource` - the field `testCases` - does not return an `IEnumerable` or a type that implements `IEnumerable`,
In the sample above, the source specified by `ValueSource` - the field `testCases` - does not return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`,
instead it returns an `int`.

However, sources specified by `ValueSource` [must return an `IEnumerable` or a type that implements `IEnumerable`.](https://github.com/nunit/docs/wiki/ValueSource-Attribute).
However, sources specified by `ValueSource` [must return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`.](https://github.com/nunit/docs/wiki/ValueSource-Attribute).

### Fix

Change `testCases` to return an `IEnumerable` or a type that implements `IEnumerable`:
Change `testCases` to return an `I(Async)Enumerable` or a type that implements `I(Async)Enumerable`:

```csharp
public class AnalyzeWhenSourceDoesProvideIEnumerable
Expand All @@ -67,7 +67,7 @@ Configure the severity per project, for more info see [MSDN](https://learn.micro
### Via .editorconfig file

```ini
# NUnit1024: The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable
# NUnit1024: The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
dotnet_diagnostic.NUnit1024.severity = chosenSeverity
```

Expand All @@ -76,22 +76,22 @@ where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`,
### Via #pragma directive

```csharp
#pragma warning disable NUnit1024 // The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning disable NUnit1024 // The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
Code violating the rule here
#pragma warning restore NUnit1024 // The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning restore NUnit1024 // The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
```

Or put this at the top of the file to disable all instances.

```csharp
#pragma warning disable NUnit1024 // The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable
#pragma warning disable NUnit1024 // The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable
```

### Via attribute `[SuppressMessage]`

```csharp
[System.Diagnostics.CodeAnalysis.SuppressMessage("Structure",
"NUnit1024:The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable",
"NUnit1024:The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable",
Justification = "Reason...")]
```
<!-- end generated config severity -->
6 changes: 3 additions & 3 deletions documentation/index.md
Expand Up @@ -33,16 +33,16 @@ Rules which enforce structural requirements on the test code.
| [NUnit1012](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1012.md) | The async test method must have a non-void return type | :white_check_mark: | :exclamation: | :x: |
| [NUnit1013](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1013.md) | The async test method must have a non-generic Task return type when no result is expected | :white_check_mark: | :exclamation: | :x: |
| [NUnit1014](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1014.md) | The async test method must have a Task\<T> return type when a result is expected | :white_check_mark: | :exclamation: | :x: |
| [NUnit1015](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1015.md) | The source type does not implement IEnumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1015](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1015.md) | The source type does not implement I(Async)Enumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1016](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1016.md) | The source type does not have a default constructor | :white_check_mark: | :exclamation: | :x: |
| [NUnit1017](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1017.md) | The specified source is not static | :white_check_mark: | :exclamation: | :x: |
| [NUnit1018](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1018.md) | The number of parameters provided by the TestCaseSource does not match the number of parameters in the target method | :white_check_mark: | :exclamation: | :x: |
| [NUnit1019](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1019.md) | The source specified by the TestCaseSource does not return an IEnumerable or a type that implements IEnumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1019](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1019.md) | The source specified by the TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1020](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1020.md) | The TestCaseSource provides parameters to a source - field or property - that expects no parameters | :white_check_mark: | :exclamation: | :x: |
| [NUnit1021](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1021.md) | The ValueSource should use nameof operator to specify target | :white_check_mark: | :warning: | :white_check_mark: |
| [NUnit1022](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1022.md) | The specified source is not static | :white_check_mark: | :exclamation: | :x: |
| [NUnit1023](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1023.md) | The target method expects parameters which cannot be supplied by the ValueSource | :white_check_mark: | :exclamation: | :x: |
| [NUnit1024](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1024.md) | The source specified by the ValueSource does not return an IEnumerable or a type that implements IEnumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1024](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1024.md) | The source specified by the ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable | :white_check_mark: | :exclamation: | :x: |
| [NUnit1025](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1025.md) | The ValueSource argument does not specify an existing member | :white_check_mark: | :exclamation: | :x: |
| [NUnit1026](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1026.md) | The test or setup/teardown method is not public | :white_check_mark: | :exclamation: | :white_check_mark: |
| [NUnit1027](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit1027.md) | The test method has parameters, but no arguments are supplied by attributes | :white_check_mark: | :exclamation: | :x: |
Expand Down
@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Analyzers.Constants;
using NUnit.Analyzers.TestCaseSourceUsage;
Expand Down Expand Up @@ -51,10 +54,37 @@ class MyTests

var expectedDiagnostic = ExpectedDiagnostic
.Create(AnalyzerIdentifiers.TestCaseSourceSourceTypeNotIEnumerable)
.WithMessage("The source type 'MyTests' does not implement IEnumerable");
.WithMessage("The source type 'MyTests' does not implement I(Async)Enumerable");
RoslynAssert.Diagnostics(analyzer, expectedDiagnostic, testCode);
}

[Test]
public void AnalyzeWhenTypeOfSourceImplementsIAsyncEnumerable()
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
public class AnalyzeWhenTypeOfSourceImplementsIAsyncEnumerable
{
[TestCaseSource(typeof(MyTests))]
public void Test(int i)
{
}
}
public sealed class MyTests : IAsyncEnumerable<int>
{
public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
{
throw new NotImplementedException();
}
}",
additionalUsings: "using System.Collections.Generic;using System.Threading;");

IEnumerable<MetadataReference> asyncEnumerableMetadata = MetadataReferences.Transitive(typeof(IAsyncEnumerable<>));
IEnumerable<MetadataReference> metadataReferences = (Settings.Default.MetadataReferences ?? Enumerable.Empty<MetadataReference>()).Concat(asyncEnumerableMetadata);

RoslynAssert.Valid(analyzer, testCode, Settings.Default.WithMetadataReferences(metadataReferences));
}

[Test]
public void AnalyzeWhenTypeOfSourceNoDefaultConstructor()
{
Expand Down
Expand Up @@ -322,7 +322,7 @@ public void Test()

var expectedDiagnostic = ExpectedDiagnostic
.Create(AnalyzerIdentifiers.TestCaseSourceDoesNotReturnIEnumerable)
.WithMessage($"The TestCaseSource does not return an IEnumerable or a type that implements IEnumerable. Instead it returns a '{returnType}'.");
.WithMessage($"The TestCaseSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable. Instead it returns a '{returnType}'.");
RoslynAssert.Diagnostics(analyzer, expectedDiagnostic, testCode);
}

Expand Down
@@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Gu.Roslyn.Asserts;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
Expand Down Expand Up @@ -199,7 +201,8 @@ public static IEnumerable<int> TestData(string dummy, int anotherDummy)
[TestCase("private static TestCaseData[] TestCases => new TestCaseData[0];")]
[TestCase("private static TestCaseData[] TestCases() => new TestCaseData[0];")]
[TestCase("private static Task<TestCaseData[]> TestCases() => Task.FromResult(new TestCaseData[0]);")]
public void AnalyzeWhenSourceDoesProvideIEnumerable(string testCaseMember)
[TestCase("private static async IAsyncEnumerable<int> TestCases() { foreach (var value in new[] { 0 }) { yield return value; await Task.Yield(); } }", "using System.Collections.Generic;")]
public void AnalyzeWhenSourceDoesProvideIEnumerable(string testCaseMember, string? additionalUsings = null)
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing($@"
public class AnalyzeWhenSourceDoesProvideIEnumerable
Expand All @@ -210,9 +213,12 @@ public class AnalyzeWhenSourceDoesProvideIEnumerable
public void Test([ValueSource(nameof(TestCases))] int number)
{{
}}
}}");
}}", additionalUsings);

RoslynAssert.Valid(analyzer, testCode);
IEnumerable<MetadataReference> asyncEnumerableMetadata = MetadataReferences.Transitive(typeof(IAsyncEnumerable<>));
IEnumerable<MetadataReference> metadataReferences = (Settings.Default.MetadataReferences ?? Enumerable.Empty<MetadataReference>()).Concat(asyncEnumerableMetadata);

RoslynAssert.Valid(analyzer, testCode, Settings.Default.WithMetadataReferences(metadataReferences));
}

[TestCase("private static readonly object? TestCases = null;", "object?")]
Expand All @@ -230,7 +236,7 @@ public void Test([ValueSource(nameof(TestCases))] int number)
public void AnalyzeWhenSourceDoesNotProvideIEnumerable(string testCaseMember, string returnType)
{
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing($@"
public class AnalyzeWhenSourceDoesProvideIEnumerable
public class AnalyzeWhenSourceDoesNotProvideIEnumerable
{{
#pragma warning disable CS0414 // Consider changing the field to a 'const'
{testCaseMember}
Expand All @@ -244,7 +250,7 @@ public void Test([ValueSource(nameof(TestCases))] int number)

var expectedDiagnostic = ExpectedDiagnostic
.Create(AnalyzerIdentifiers.ValueSourceDoesNotReturnIEnumerable)
.WithMessage($"The ValueSource does not return an IEnumerable or a type that implements IEnumerable. Instead it returns a '{returnType}'.");
.WithMessage($"The ValueSource does not return an I(Async)Enumerable or a type that implements I(Async)Enumerable. Instead it returns a '{returnType}'.");
RoslynAssert.Diagnostics(analyzer, expectedDiagnostic, testCode);
}

Expand Down

0 comments on commit 2214c07

Please sign in to comment.