1111<#@ import namespace="System.Globalization" #>
1212<#@ import namespace="System.Linq" #>
1313<#@ import namespace="System.Text" #>
14+ <#@ import namespace="System.Text.RegularExpressions" #>
1415<#@ import namespace="System.Threading.Tasks" #>
1516<#@ import namespace="Microsoft.CodeAnalysis" #>
1617<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
@@ -61,6 +62,11 @@ private class TestClassExplorer
6162 /// ><summary>
6263 private readonly string _testSkippingAttributeFullName;
6364
65+ /// <summary>
66+ /// The type full name of "test case" inidicator attribute type.
67+ /// ><summary>
68+ private readonly string _testCaseAttributeFullName;
69+
6470 /// <summary>
6571 /// The array of type full names of attributes which should mark significant methods including test method, setup method, etc.
6672 /// ><summary>
@@ -76,10 +82,11 @@ private class TestClassExplorer
7682 /// <param name="testSetupAttributeFullName">The type full name of "per test setip routine" inidicator attribute type.</param>
7783 /// <param name="testCleanupAttributeFullName">The type full name of "per test cleanup routine" inidicator attribute type.</param>
7884 /// <param name="testSkippingAttributeFullName">The type full name of "skipping specified test" inidicator attribute type.</param>
85+ /// <param name="testCaseAttributeFullName">The type full name of "test case" inidicator attribute type.</param>
7986 public TestClassExplorer( string testClassAttributeFullName, string testMethodAttributeFullName,
8087 string fixtureSetupAttributeFullName, string fixtureCleanupAttributeFullName,
8188 string testSetupAttributeFullName, string testCleanupAttributeFullName,
82- string testSkippingAttributeFullName
89+ string testSkippingAttributeFullName, string testCaseAttributeFullName
8390 )
8491 {
8592 this._testClassAttributeFullName = testClassAttributeFullName;
@@ -89,6 +96,7 @@ private class TestClassExplorer
8996 this._testSetupAttributeFullName = testSetupAttributeFullName;
9097 this._testCleanupAttributeFullName = testCleanupAttributeFullName;
9198 this._testSkippingAttributeFullName = testSkippingAttributeFullName;
99+ this._testCaseAttributeFullName = testCaseAttributeFullName;
92100 this._significantMethodAttributeFullNames =
93101 new [] { testMethodAttributeFullName, fixtureSetupAttributeFullName, fixtureCleanupAttributeFullName, testSetupAttributeFullName, testCleanupAttributeFullName };
94102 }
@@ -109,7 +117,8 @@ private class TestClassExplorer
109117 "NUnit.Framework.TestFixtureTearDownAttribute",
110118 "NUnit.Framework.SetUpAttribute",
111119 "NUnit.Framework.TearDownAttribute",
112- "NUnit.Framework.IgnoreAttribute"
120+ "NUnit.Framework.IgnoreAttribute",
121+ "NUnit.Framework.TestCaseAttribute"
113122 );
114123 }
115124
@@ -166,8 +175,13 @@ private class TestClassExplorer
166175 g.Where( x =>
167176 x.member.GetAttributes().Any( a => GetAttributeName( a ) == this._testMethodAttributeFullName ) // Only test methods
168177 && x.member.GetAttributes().All( a => GetAttributeName( a ) != this._testSkippingAttributeFullName ) // Excludes "Ignored" method
169- ).Select( x => new TestMethod( x.member.Name ) )
170- .OrderBy( x => x.Name )
178+ ).Select( x =>
179+ new TestMethod(
180+ x.member.Name,
181+ // only test data from attribute constructor arguments are supported.
182+ x.member.GetAttributes().Where( a => GetAttributeName( a ) == this._testCaseAttributeFullName ).Select( a => GetTestData( a ) ).ToArray()
183+ )
184+ ).OrderBy( x => x.Name )
171185 )
172186 {
173187 FixtureSetup = GetSpecialMethodName( g.Select( x => x.member ), this._fixtureSetupAttributeFullName ),
@@ -198,6 +212,115 @@ private class TestClassExplorer
198212 return attribute.AttributeClass.ToDisplayString( QualifiedNameOnlyFormat );
199213 }
200214
215+ /// <summary>
216+ /// Gets test data for specified "test case".
217+ /// </summary>
218+ /// <param name="attribute">The attribute.</param>
219+ /// <returns>
220+ /// The test data for the case. The element should be primitive value types, string or null.
221+ /// </returns>
222+ private static string[] GetTestData( AttributeData attribute )
223+ {
224+ TypedConstant[] constructorArguments = attribute.ConstructorArguments.ToArray();
225+ switch ( constructorArguments.Length )
226+ {
227+ case 1:
228+ {
229+ if ( constructorArguments[ 0 ].Kind == TypedConstantKind.Array )
230+ {
231+ // [Attr(new object[]{ a, b, c })]
232+ // Returns array's content (Values is IEnumerable<TypedConstant>)
233+ return constructorArguments[ 0 ].Values.Select( ToCSharpLiteral ).ToArray();
234+ }
235+ else
236+ {
237+ goto default;
238+ }
239+ }
240+ default:
241+ {
242+ // [Attr( a, b, c )]
243+ return constructorArguments.Select( ToCSharpLiteral ).ToArray();
244+ }
245+ }
246+ }
247+
248+ /// <summary>
249+ /// Converts a Roslyn <see cref="TypedConstant" /> to a C# literal representation.
250+ /// </summary>
251+ /// <param name="constant">A Roslyn <see cref="TypedConstant" />.</param>
252+ /// <returns>A C# literal representation.</returns>
253+ private static string ToCSharpLiteral( TypedConstant constant )
254+ {
255+ if ( constant.IsNull )
256+ {
257+ return "null";
258+ }
259+
260+ switch( constant.Kind )
261+ {
262+ case TypedConstantKind.Enum:
263+ {
264+ var value = constant.Value.ToString();
265+ if ( Regex.IsMatch( value, @"^\d+$" ) )
266+ {
267+ return "( " + constant.Type.Name + " )" + constant.Value;
268+ }
269+ else
270+ {
271+ return constant.Type.Name + "." + constant.Value;
272+ }
273+ }
274+ case TypedConstantKind.Type:
275+ {
276+ // TODO: generic type
277+ return "typeof( " + constant.Value + " )";
278+ }
279+ case TypedConstantKind.Primitive:
280+ {
281+ switch( Type.GetTypeCode( constant.Value.GetType() ) )
282+ {
283+ case TypeCode.String:
284+ {
285+ return "@\"" + constant.Value.ToString().Replace( "\"", "\"\"" ) + "\"";
286+ }
287+ case TypeCode.Char:
288+ {
289+ return "'" + constant.Value + "'";
290+ }
291+ case TypeCode.Int64:
292+ {
293+ return constant.Value + "L";
294+ }
295+ case TypeCode.UInt32:
296+ {
297+ return constant.Value + "U";
298+ }
299+ case TypeCode.UInt64:
300+ {
301+ return constant.Value + "UL";
302+ }
303+ case TypeCode.Single:
304+ {
305+ return constant.Value + "F";
306+ }
307+ case TypeCode.Decimal:
308+ {
309+ return constant.Value + "M";
310+ }
311+ default:
312+ {
313+ return constant.Value.ToString();
314+ }
315+ }
316+ }
317+ default:
318+ {
319+ return "__ERROR(" + constant.Kind + ")__";
320+ }
321+ }
322+ }
323+
201324 /// <summary>
202325 /// Gets the name of the special method which marked with specified attribute.
203326 /// </summary>
0 commit comments