Skip to content
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
34 changes: 34 additions & 0 deletions TUnit.Analyzers.Tests/EnumerableMethodDataTupleTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,40 @@ public void DataSource_TupleMethod(int value, string value2, bool value3, int va
);
}

[Test]
public async Task Test_Method_With_Tuple_Parameter_Should_Raise_Error()
{
// This tests the new functionality - tuple parameters with tuple data sources should error
await Verifier
.VerifyAnalyzerAsync(
"""
using System.Collections.Generic;
using System;
using TUnit.Core;

public class EnumerableTupleDataSourceDrivenTests
{
[Test]
[{|#0:MethodDataSource(nameof(TupleMethod))|}]
public void DataSource_TupleMethod((int value, string value2) tupleParam)
{
}

public static IEnumerable<Func<(int, string)>> TupleMethod()
{
yield return () => (1, "String");
yield return () => (2, "String2");
}
}
""",

Verifier
.Diagnostic(Rules.WrongArgumentTypeTestData)
.WithArguments("int, string", "(int value, string value2)")
.WithLocation(0)
);
}

[Test]
public async Task Valid_Enumerable_Tuple_Raises_No_Error()
{
Expand Down
72 changes: 72 additions & 0 deletions TUnit.Analyzers.Tests/TupleParameterMismatchTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<TUnit.Analyzers.TestDataAnalyzer>;

namespace TUnit.Analyzers.Tests
{
public class TupleParameterMismatchTests
{
[Test]
public async Task Test_Method_With_Tuple_Parameter_And_Tuple_DataSource_Should_Raise_Error()
{
// This is the problematic case from the issue
// Data source returns Func<(int, int)> which gets unpacked to 2 arguments
// But test method expects single tuple parameter - this should error
await Verifier
.VerifyAnalyzerAsync(
"""
using System.Collections.Generic;
using System;
using TUnit.Core;

public class TupleParameterTests
{
[Test]
[{|#0:MethodDataSource(nameof(IncreasingLoad))|}]
public void CanHandleManyRequests_With_Changing_Subscribers((int consumers, int requests) state)
{
}

public static IEnumerable<Func<(int consumers, int messages)>> IncreasingLoad()
{
yield return () => (1, 10);
yield return () => (5, 50);
}
}
""",

Verifier
.Diagnostic(Rules.WrongArgumentTypeTestData)
.WithArguments("int, int", "(int consumers, int requests)")
.WithLocation(0)
);
}

[Test]
public async Task Test_Method_With_Separate_Parameters_And_Tuple_DataSource_Should_Not_Raise_Error()
{
// This is the correct way - data source returns tuples, method accepts separate parameters
await Verifier
.VerifyAnalyzerAsync(
"""
using System.Collections.Generic;
using System;
using TUnit.Core;

public class TupleParameterTests
{
[Test]
[MethodDataSource(nameof(IncreasingLoad))]
public void CanHandleManyRequests_With_Separate_Parameters(int consumers, int requests)
{
}

public static IEnumerable<Func<(int consumers, int messages)>> IncreasingLoad()
{
yield return () => (1, 10);
yield return () => (5, 50);
}
}
"""
);
}
}
}
27 changes: 21 additions & 6 deletions TUnit.Analyzers/TestDataAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,20 @@ private void CheckMethodDataSource(SymbolAnalysisContext context,

if (isTuples)
{
// Check if any test method parameters are tuple types when data source returns tuples
// This causes a runtime mismatch: data source provides separate arguments, but method expects tuple parameter
var tupleParameters = testParameterTypes.Where(p => p is INamedTypeSymbol { IsTupleType: true }).ToArray();
if (tupleParameters.Any())
{
context.ReportDiagnostic(Diagnostic.Create(
Rules.WrongArgumentTypeTestData,
attribute.GetLocation(),
string.Join(", ", unwrappedTypes),
string.Join(", ", testParameterTypes))
);
return;
}

if (unwrappedTypes.Length != testParameterTypes.Length)
{
context.ReportDiagnostic(Diagnostic.Create(
Expand Down Expand Up @@ -744,18 +758,19 @@ private ImmutableArray<ITypeSymbol> UnwrapTypes(SymbolAnalysisContext context,
type = genericType.TypeArguments[0];
}

if (testParameterTypes.Length == 1
&& context.Compilation.HasImplicitConversionOrGenericParameter(type, testParameterTypes[0]))
{
return ImmutableArray.Create(type);
}

// Check for tuple types first before doing conversion checks
if (type is INamedTypeSymbol { IsTupleType: true } tupleType)
{
isTuples = true;
return ImmutableArray.CreateRange(tupleType.TupleElements.Select(x => x.Type));
}

if (testParameterTypes.Length == 1
&& context.Compilation.HasImplicitConversionOrGenericParameter(type, testParameterTypes[0]))
{
return ImmutableArray.Create(type);
}

// Handle array cases - when a data source returns IEnumerable<T[]> or IAsyncEnumerable<T[]>,
// each array contains the arguments for one test invocation
if (type is IArrayTypeSymbol arrayType)
Expand Down
Loading