Skip to content

Commit 4cd8080

Browse files
[rel/3.7] Fix Tuple/ValueTuple handling of TRest (#4653)
Co-authored-by: Youssef1313 <[email protected]>
1 parent 67ecd81 commit 4cd8080

File tree

2 files changed

+223
-13
lines changed

2 files changed

+223
-13
lines changed

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

+80-13
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,46 @@ private static bool TryGetData(object dataSource, [NotNullWhen(true)] out IEnume
149149

150150
objects.Add(array);
151151
#else
152-
Type type = entry.GetType();
153-
if (!IsTupleOrValueTuple(entry.GetType(), out int tupleSize)
152+
if (!IsTupleOrValueTuple(entry, out int tupleSize)
154153
|| (objects.Count > 0 && objects[objects.Count - 1].Length != tupleSize))
155154
{
156155
data = null;
157156
return false;
158157
}
159158

160159
object[] array = new object[tupleSize];
161-
for (int i = 0; i < tupleSize; i++)
162-
{
163-
array[i] = type.GetField($"Item{i + 1}")?.GetValue(entry)!;
164-
}
160+
ProcessTuple(entry, array, 0);
165161

166162
objects.Add(array);
163+
164+
static void ProcessTuple(object data, object[] array, int startingIndex)
165+
{
166+
Type type = data.GetType();
167+
int tupleSize = type.GenericTypeArguments.Length;
168+
for (int i = 0; i < tupleSize; i++)
169+
{
170+
if (i != 7)
171+
{
172+
// Note: ItemN are properties on Tuple, but are fields on ValueTuple
173+
array[startingIndex + i] = type.GetField($"Item{i + 1}")?.GetValue(data)
174+
?? type.GetProperty($"Item{i + 1}").GetValue(data);
175+
continue;
176+
}
177+
178+
object rest = type.GetProperty("Rest")?.GetValue(data) ??
179+
type.GetField("Rest").GetValue(data)!;
180+
if (IsTupleOrValueTuple(rest, out _))
181+
{
182+
ProcessTuple(rest, array, startingIndex + 7);
183+
}
184+
else
185+
{
186+
array[startingIndex + i] = rest;
187+
}
188+
189+
return;
190+
}
191+
}
167192
#endif
168193
}
169194

@@ -176,9 +201,16 @@ private static bool TryGetData(object dataSource, [NotNullWhen(true)] out IEnume
176201
}
177202

178203
#if !NET471_OR_GREATER && !NETCOREAPP
179-
private static bool IsTupleOrValueTuple(Type type, out int tupleSize)
204+
private static bool IsTupleOrValueTuple(object? data, out int tupleSize)
180205
{
181206
tupleSize = 0;
207+
208+
if (data is null)
209+
{
210+
return false;
211+
}
212+
213+
Type type = data.GetType();
182214
if (!type.IsGenericType)
183215
{
184216
return false;
@@ -192,34 +224,69 @@ private static bool IsTupleOrValueTuple(Type type, out int tupleSize)
192224
genericTypeDefinition == typeof(Tuple<,,,>) ||
193225
genericTypeDefinition == typeof(Tuple<,,,,>) ||
194226
genericTypeDefinition == typeof(Tuple<,,,,,>) ||
195-
genericTypeDefinition == typeof(Tuple<,,,,,,>) ||
196-
genericTypeDefinition == typeof(Tuple<,,,,,,,>))
227+
genericTypeDefinition == typeof(Tuple<,,,,,,>))
197228
{
198229
tupleSize = type.GetGenericArguments().Length;
199230
return true;
200231
}
201232

233+
if (genericTypeDefinition == typeof(Tuple<,,,,,,,>))
234+
{
235+
object? last = type.GetProperty("Rest").GetValue(data);
236+
if (IsTupleOrValueTuple(last, out int restSize))
237+
{
238+
tupleSize = 7 + restSize;
239+
return true;
240+
}
241+
else
242+
{
243+
tupleSize = 8;
244+
return true;
245+
}
246+
}
247+
202248
#if NET462
203-
// TODO: https://github.com/microsoft/testfx/issues/4624
204249
if (genericTypeDefinition.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal))
205250
{
206251
tupleSize = type.GetGenericArguments().Length;
252+
if (tupleSize == 8)
253+
{
254+
object? last = type.GetField("Rest").GetValue(data);
255+
if (IsTupleOrValueTuple(last, out int restSize))
256+
{
257+
tupleSize = 7 + restSize;
258+
}
259+
}
260+
207261
return true;
208262
}
209263
#else
210-
// TODO: https://github.com/microsoft/testfx/issues/4624
211264
if (genericTypeDefinition == typeof(ValueTuple<>) ||
212265
genericTypeDefinition == typeof(ValueTuple<,>) ||
213266
genericTypeDefinition == typeof(ValueTuple<,,>) ||
214267
genericTypeDefinition == typeof(ValueTuple<,,,>) ||
215268
genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
216269
genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
217-
genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
218-
genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
270+
genericTypeDefinition == typeof(ValueTuple<,,,,,,>))
219271
{
220272
tupleSize = type.GetGenericArguments().Length;
221273
return true;
222274
}
275+
276+
if (genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
277+
{
278+
object? last = type.GetField("Rest").GetValue(data);
279+
if (IsTupleOrValueTuple(last, out int restSize))
280+
{
281+
tupleSize = 7 + restSize;
282+
return true;
283+
}
284+
else
285+
{
286+
tupleSize = 8;
287+
return true;
288+
}
289+
}
223290
#endif
224291

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

0 commit comments

Comments
 (0)