Skip to content

Commit 02c8fa5

Browse files
authored
Fix Tuple/ValueTuple handling of TRest (#4639)
1 parent 1af0198 commit 02c8fa5

File tree

2 files changed

+219
-13
lines changed

2 files changed

+219
-13
lines changed

src/TestFramework/TestFramework/Attributes/DataSource/DynamicDataOperations.cs

+80-13
Original file line numberDiff line numberDiff line change
@@ -192,28 +192,60 @@ private static bool TryHandleTupleDataSource(object data, List<object[]> objects
192192
return true;
193193
}
194194
#else
195-
Type type = data.GetType();
196-
if (IsTupleOrValueTuple(data.GetType(), out int tupleSize)
195+
if (IsTupleOrValueTuple(data, out int tupleSize)
197196
&& (objects.Count == 0 || objects[objects.Count - 1].Length == tupleSize))
198197
{
199198
object[] array = new object[tupleSize];
200-
for (int i = 0; i < tupleSize; i++)
201-
{
202-
array[i] = type.GetField($"Item{i + 1}")?.GetValue(data)!;
203-
}
199+
ProcessTuple(data, array, 0);
204200

205201
objects.Add(array);
206202
return true;
207203
}
204+
205+
static void ProcessTuple(object data, object[] array, int startingIndex)
206+
{
207+
Type type = data.GetType();
208+
int tupleSize = type.GenericTypeArguments.Length;
209+
for (int i = 0; i < tupleSize; i++)
210+
{
211+
if (i != 7)
212+
{
213+
// Note: ItemN are properties on Tuple, but are fields on ValueTuple
214+
array[startingIndex + i] = type.GetField($"Item{i + 1}")?.GetValue(data)
215+
?? type.GetProperty($"Item{i + 1}").GetValue(data);
216+
continue;
217+
}
218+
219+
object rest = type.GetProperty("Rest")?.GetValue(data) ??
220+
type.GetField("Rest").GetValue(data)!;
221+
if (IsTupleOrValueTuple(rest, out _))
222+
{
223+
ProcessTuple(rest, array, startingIndex + 7);
224+
}
225+
else
226+
{
227+
array[startingIndex + i] = rest;
228+
}
229+
230+
return;
231+
}
232+
}
208233
#endif
209234

210235
return false;
211236
}
212237

213238
#if !NET471_OR_GREATER && !NETCOREAPP
214-
private static bool IsTupleOrValueTuple(Type type, out int tupleSize)
239+
private static bool IsTupleOrValueTuple(object? data, out int tupleSize)
215240
{
216241
tupleSize = 0;
242+
243+
if (data is null)
244+
{
245+
return false;
246+
}
247+
248+
Type type = data.GetType();
217249
if (!type.IsGenericType)
218250
{
219251
return false;
@@ -227,34 +259,69 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize)
227259
genericTypeDefinition == typeof(Tuple<,,,>) ||
228260
genericTypeDefinition == typeof(Tuple<,,,,>) ||
229261
genericTypeDefinition == typeof(Tuple<,,,,,>) ||
230-
genericTypeDefinition == typeof(Tuple<,,,,,,>) ||
231-
genericTypeDefinition == typeof(Tuple<,,,,,,,>))
262+
genericTypeDefinition == typeof(Tuple<,,,,,,>))
232263
{
233264
tupleSize = type.GetGenericArguments().Length;
234265
return true;
235266
}
236267

268+
if (genericTypeDefinition == typeof(Tuple<,,,,,,,>))
269+
{
270+
object? last = type.GetProperty("Rest").GetValue(data);
271+
if (IsTupleOrValueTuple(last, out int restSize))
272+
{
273+
tupleSize = 7 + restSize;
274+
return true;
275+
}
276+
else
277+
{
278+
tupleSize = 8;
279+
return true;
280+
}
281+
}
282+
237283
#if NET462
238-
// TODO: https://github.com/microsoft/testfx/issues/4624
239284
if (genericTypeDefinition.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal))
240285
{
241286
tupleSize = type.GetGenericArguments().Length;
287+
if (tupleSize == 8)
288+
{
289+
object? last = type.GetField("Rest").GetValue(data);
290+
if (IsTupleOrValueTuple(last, out int restSize))
291+
{
292+
tupleSize = 7 + restSize;
293+
}
294+
}
295+
242296
return true;
243297
}
244298
#else
245-
// TODO: https://github.com/microsoft/testfx/issues/4624
246299
if (genericTypeDefinition == typeof(ValueTuple<>) ||
247300
genericTypeDefinition == typeof(ValueTuple<,>) ||
248301
genericTypeDefinition == typeof(ValueTuple<,,>) ||
249302
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
250303
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
251304
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
252-
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
253-
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
305+
genericTypeDefinition == typeof(ValueTuple<,,,,,,>))
254306
{
255307
tupleSize = type.GetGenericArguments().Length;
256308
return true;
257309
}
310+
311+
if (genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
312+
{
313+
object? last = type.GetField("Rest").GetValue(data);
314+
if (IsTupleOrValueTuple(last, out int restSize))
315+
{
316+
tupleSize = 7 + restSize;
317+
return true;
318+
}
319+
else
320+
{
321+
tupleSize = 8;
322+
return true;
323+
}
324+
}
258325
#endif
259326

260327
return false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Testing.Platform.Acceptance.IntegrationTests;
5+
using Microsoft.Testing.Platform.Acceptance.IntegrationTests.Helpers;
6+
using Microsoft.Testing.Platform.Helpers;
7+
8+
namespace MSTest.Acceptance.IntegrationTests;
9+
10+
[TestClass]
11+
public sealed class TupleDynamicDataTests : AcceptanceTestBase<TupleDynamicDataTests.TestAssetFixture>
12+
{
13+
[TestMethod]
14+
[DynamicData(nameof(TargetFrameworks.AllForDynamicData), typeof(TargetFrameworks))]
15+
public async Task CanUseLongTuplesAndValueTuplesForAllFrameworks(string tfm)
16+
{
17+
var testHost = TestHost.LocateFrom(AssetFixture.ProjectPath, TestAssetFixture.ProjectName, tfm);
18+
TestHostResult testHostResult = await testHost.ExecuteAsync("--settings my.runsettings");
19+
20+
// Assert
21+
testHostResult.AssertExitCodeIs(ExitCodes.Success);
22+
testHostResult.AssertOutputContains("""
23+
1, 2, 3, 4, 5, 6, 7, 8
24+
9, 10, 11, 12, 13, 14, 15, 16
25+
1, 2, 3, 4, 5, 6, 7, 8
26+
9, 10, 11, 12, 13, 14, 15, 16
27+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
28+
11, 12, 13, 14, 15, 16, 17, 18, 19, 20
29+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
30+
11, 12, 13, 14, 15, 16, 17, 18, 19, 20
31+
""");
32+
testHostResult.AssertOutputContainsSummary(failed: 0, passed: 8, skipped: 0);
33+
}
34+
35+
public sealed class TestAssetFixture() : TestAssetFixtureBase(AcceptanceFixture.NuGetGlobalPackagesFolder)
36+
{
37+
public const string ProjectName = "TupleDynamicDataTests";
38+
39+
public string ProjectPath => GetAssetPath(ProjectName);
40+
41+
public override IEnumerable<(string ID, string Name, string Code)> GetAssetsToGenerate()
42+
{
43+
yield return (ProjectName, ProjectName,
44+
SourceCode
45+
.PatchTargetFrameworks(TargetFrameworks.All)
46+
.PatchCodeWithReplace("$MSTestVersion$", MSTestVersion));
47+
}
48+
49+
private const string SourceCode = """
50+
#file TupleDynamicDataTests.csproj
51+
<Project Sdk="Microsoft.NET.Sdk">
52+
53+
<PropertyGroup>
54+
<OutputType>Exe</OutputType>
55+
<EnableMSTestRunner>true</EnableMSTestRunner>
56+
<TargetFrameworks>$TargetFrameworks$</TargetFrameworks>
57+
<LangVersion>preview</LangVersion>
58+
</PropertyGroup>
59+
60+
<ItemGroup>
61+
<PackageReference Include="MSTest.TestAdapter" Version="$MSTestVersion$" />
62+
<PackageReference Include="MSTest.TestFramework" Version="$MSTestVersion$" />
63+
</ItemGroup>
64+
65+
<ItemGroup>
66+
<None Update="*.runsettings">
67+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
68+
</None>
69+
</ItemGroup>
70+
</Project>
71+
72+
#file UnitTest1.cs
73+
using System;
74+
using System.Collections.Generic;
75+
using System.Text;
76+
using Microsoft.VisualStudio.TestTools.UnitTesting;
77+
78+
[TestClass]
79+
public class UnitTest1
80+
{
81+
private static readonly StringBuilder s_builder = new();
82+
83+
[ClassCleanup]
84+
public static void ClassCleanup()
85+
{
86+
Console.WriteLine(s_builder.ToString());
87+
}
88+
89+
[DynamicData(nameof(DataTuple8))]
90+
[DynamicData(nameof(DataValueTuple8))]
91+
[TestMethod]
92+
public void TestMethod1(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8)
93+
{
94+
s_builder.AppendLine($"{p1}, {p2}, {p3}, {p4}, {p5}, {p6}, {p7}, {p8}");
95+
}
96+
97+
[DynamicData(nameof(DataTuple10))]
98+
[DynamicData(nameof(DataValueTuple10))]
99+
[TestMethod]
100+
public void TestMethod1(int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8, int p9, int p10)
101+
{
102+
s_builder.AppendLine($"{p1}, {p2}, {p3}, {p4}, {p5}, {p6}, {p7}, {p8}, {p9}, {p10}");
103+
}
104+
105+
public static IEnumerable<Tuple<int, int, int, int, int, int, int, Tuple<int>>> DataTuple8 =>
106+
[
107+
(1, 2, 3, 4, 5, 6, 7, 8).ToTuple(),
108+
(9, 10, 11, 12, 13, 14, 15, 16).ToTuple(),
109+
];
110+
111+
public static IEnumerable<ValueTuple<int, int, int, int, int, int, int, ValueTuple<int>>> DataValueTuple8 =>
112+
[
113+
(1, 2, 3, 4, 5, 6, 7, 8),
114+
(9, 10, 11, 12, 13, 14, 15, 16),
115+
];
116+
117+
public static IEnumerable<Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>>> DataTuple10 =>
118+
[
119+
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).ToTuple(),
120+
(11, 12, 13, 14, 15, 16, 17, 18, 19, 20).ToTuple(),
121+
];
122+
123+
public static IEnumerable<ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>> DataValueTuple10 =>
124+
[
125+
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
126+
(11, 12, 13, 14, 15, 16, 17, 18, 19, 20),
127+
];
128+
}
129+
130+
131+
#file my.runsettings
132+
<RunSettings>
133+
<MSTest>
134+
<CaptureTraceOutput>false</CaptureTraceOutput>
135+
</MSTest>
136+
</RunSettings>
137+
""";
138+
}
139+
}

0 commit comments

Comments
 (0)