1 // ***********************************************************************
2 // Copyright (c) 2008-2015 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections.Generic;
31 using System.Reflection;
32 using NUnit.Compatibility;
33 using NUnit.Framework.Interfaces;
34 using NUnit.Framework.Internal;
35 using NUnit.Framework.Internal.Builders;
37 namespace NUnit.Framework
40 /// TestCaseAttribute is used to mark parameterized test cases
41 /// and provide them with their arguments.
43 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited=false)]
44 public class TestCaseAttribute : NUnitAttribute, ITestBuilder, ITestCaseData, IImplyFixture
49 /// Construct a TestCaseAttribute with a list of arguments.
50 /// This constructor is not CLS-Compliant
52 /// <param name="arguments"></param>
53 public TestCaseAttribute(params object[] arguments)
55 RunState = RunState.Runnable;
57 if (arguments == null)
58 Arguments = new object[] { null };
60 Arguments = arguments;
62 Properties = new PropertyBag();
66 /// Construct a TestCaseAttribute with a single argument
68 /// <param name="arg"></param>
69 public TestCaseAttribute(object arg)
71 RunState = RunState.Runnable;
72 Arguments = new object[] { arg };
73 Properties = new PropertyBag();
77 /// Construct a TestCaseAttribute with a two arguments
79 /// <param name="arg1"></param>
80 /// <param name="arg2"></param>
81 public TestCaseAttribute(object arg1, object arg2)
83 RunState = RunState.Runnable;
84 Arguments = new object[] { arg1, arg2 };
85 Properties = new PropertyBag();
89 /// Construct a TestCaseAttribute with a three arguments
91 /// <param name="arg1"></param>
92 /// <param name="arg2"></param>
93 /// <param name="arg3"></param>
94 public TestCaseAttribute(object arg1, object arg2, object arg3)
96 RunState = RunState.Runnable;
97 Arguments = new object[] { arg1, arg2, arg3 };
98 Properties = new PropertyBag();
103 #region ITestData Members
106 /// Gets or sets the name of the test.
108 /// <value>The name of the test.</value>
109 public string TestName { get; set; }
112 /// Gets or sets the RunState of this test case.
114 public RunState RunState { get; private set; }
117 /// Gets the list of arguments to a test case
119 public object[] Arguments { get; private set; }
122 /// Gets the properties of the test case
124 public IPropertyBag Properties { get; private set; }
128 #region ITestCaseData Members
131 /// Gets or sets the expected result.
133 /// <value>The result.</value>
134 public object ExpectedResult
136 get { return _expectedResult; }
139 _expectedResult = value;
140 HasExpectedResult = true;
143 private object _expectedResult;
146 /// Returns true if the expected result has been set
148 public bool HasExpectedResult { get; private set; }
152 #region Other Properties
155 /// Gets or sets the description.
157 /// <value>The description.</value>
158 public string Description
160 get { return Properties.Get(PropertyNames.Description) as string; }
161 set { Properties.Set(PropertyNames.Description, value); }
165 /// The author of this test
169 get { return Properties.Get(PropertyNames.Author) as string; }
170 set { Properties.Set(PropertyNames.Author, value); }
174 /// The type that this test is testing
178 get { return _testOf; }
182 Properties.Set(PropertyNames.TestOf, value.FullName);
185 private Type _testOf;
188 /// Gets or sets the reason for ignoring the test
192 get { return IgnoreReason; }
193 set { IgnoreReason = value; }
197 /// Gets or sets a value indicating whether this <see cref="NUnit.Framework.TestCaseAttribute"/> is explicit.
200 /// <c>true</c> if explicit; otherwise, <c>false</c>.
204 get { return RunState == RunState.Explicit; }
205 set { RunState = value ? RunState.Explicit : RunState.Runnable; }
209 /// Gets or sets the reason for not running the test.
211 /// <value>The reason.</value>
214 get { return Properties.Get(PropertyNames.SkipReason) as string; }
215 set { Properties.Set(PropertyNames.SkipReason, value); }
219 /// Gets or sets the ignore reason. When set to a non-null
220 /// non-empty value, the test is marked as ignored.
222 /// <value>The ignore reason.</value>
223 public string IgnoreReason
225 get { return Reason; }
228 RunState = RunState.Ignored;
235 /// Comma-delimited list of platforms to run the test for
237 public string IncludePlatform { get; set; }
240 /// Comma-delimited list of platforms to not run the test for
242 public string ExcludePlatform { get; set; }
246 /// Gets and sets the category for this test case.
247 /// May be a comma-separated list of categories.
249 public string Category
251 get { return Properties.Get(PropertyNames.Category) as string; }
254 foreach (string cat in value.Split(new char[] { ',' }) )
255 Properties.Add(PropertyNames.Category, cat);
261 #region Helper Methods
263 private TestCaseParameters GetParametersForTestCase(IMethodInfo method)
265 TestCaseParameters parms;
270 var tmethod = method.MakeGenericMethodEx(Arguments);
272 throw new NotSupportedException("Cannot determine generic types from probing");
276 IParameterInfo[] parameters = method.GetParameters();
277 int argsNeeded = parameters.Length;
278 int argsProvided = Arguments.Length;
280 parms = new TestCaseParameters(this);
282 // Special handling for params arguments
283 if (argsNeeded > 0 && argsProvided >= argsNeeded - 1)
285 IParameterInfo lastParameter = parameters[argsNeeded - 1];
286 Type lastParameterType = lastParameter.ParameterType;
287 Type elementType = lastParameterType.GetElementType();
289 if (lastParameterType.IsArray && lastParameter.IsDefined<ParamArrayAttribute>(false))
291 if (argsProvided == argsNeeded)
293 Type lastArgumentType = parms.Arguments[argsProvided - 1].GetType();
294 if (!lastParameterType.GetTypeInfo().IsAssignableFrom(lastArgumentType.GetTypeInfo()))
296 Array array = Array.CreateInstance(elementType, 1);
297 array.SetValue(parms.Arguments[argsProvided - 1], 0);
298 parms.Arguments[argsProvided - 1] = array;
303 object[] newArglist = new object[argsNeeded];
304 for (int i = 0; i < argsNeeded && i < argsProvided; i++)
305 newArglist[i] = parms.Arguments[i];
307 int length = argsProvided - argsNeeded + 1;
308 Array array = Array.CreateInstance(elementType, length);
309 for (int i = 0; i < length; i++)
310 array.SetValue(parms.Arguments[argsNeeded + i - 1], i);
312 newArglist[argsNeeded - 1] = array;
313 parms.Arguments = newArglist;
314 argsProvided = argsNeeded;
320 //Special handling for optional parameters
321 if (parms.Arguments.Length < argsNeeded)
323 object[] newArgList = new object[parameters.Length];
324 Array.Copy(parms.Arguments, newArgList, parms.Arguments.Length);
326 for (var i = 0; i < parameters.Length; i++)
328 if (parameters[i].IsOptional)
329 newArgList[i] = Type.Missing;
332 if (i < parms.Arguments.Length)
333 newArgList[i] = parms.Arguments[i];
335 throw new TargetParameterCountException("Incorrect number of parameters specified for TestCase");
338 parms.Arguments = newArgList;
342 //if (method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(object[]))
343 // parms.Arguments = new object[]{parms.Arguments};
345 // Special handling when sole argument is an object[]
346 if (argsNeeded == 1 && method.GetParameters()[0].ParameterType == typeof(object[]))
348 if (argsProvided > 1 ||
349 argsProvided == 1 && parms.Arguments[0].GetType() != typeof(object[]))
351 parms.Arguments = new object[] { parms.Arguments };
355 if (argsProvided == argsNeeded)
356 PerformSpecialConversions(parms.Arguments, parameters);
360 parms = new TestCaseParameters(ex);
367 /// Performs several special conversions allowed by NUnit in order to
368 /// permit arguments with types that cannot be used in the constructor
369 /// of an Attribute such as TestCaseAttribute or to simplify their use.
371 /// <param name="arglist">The arguments to be converted</param>
372 /// <param name="parameters">The ParameterInfo array for the method</param>
373 private static void PerformSpecialConversions(object[] arglist, IParameterInfo[] parameters)
375 for (int i = 0; i < arglist.Length; i++)
377 object arg = arglist[i];
378 Type targetType = parameters[i].ParameterType;
383 if (arg is SpecialValue && (SpecialValue)arg == SpecialValue.Null)
389 if (targetType.IsAssignableFrom(arg.GetType()))
398 bool convert = false;
400 if (targetType == typeof(short) || targetType == typeof(byte) || targetType == typeof(sbyte) ||
401 targetType == typeof(short?) || targetType == typeof(byte?) || targetType == typeof(sbyte?) || targetType == typeof(double?))
403 convert = arg is int;
405 else if (targetType == typeof(decimal) || targetType == typeof(decimal?))
407 convert = arg is double || arg is string || arg is int;
409 else if (targetType == typeof(DateTime) || targetType == typeof(DateTime?))
411 convert = arg is string;
416 Type convertTo = targetType.GetTypeInfo().IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>) ?
417 targetType.GetGenericArguments()[0] : targetType;
418 arglist[i] = Convert.ChangeType(arg, convertTo, System.Globalization.CultureInfo.InvariantCulture);
421 // Convert.ChangeType doesn't work for TimeSpan from string
422 if ((targetType == typeof(TimeSpan) || targetType == typeof(TimeSpan?)) && arg is string)
424 arglist[i] = TimeSpan.Parse((string)arg);
430 #region ITestBuilder Members
433 /// Construct one or more TestMethods from a given MethodInfo,
434 /// using available parameter data.
436 /// <param name="method">The MethodInfo for which tests are to be constructed.</param>
437 /// <param name="suite">The suite to which the tests will be added.</param>
438 /// <returns>One or more TestMethods</returns>
439 public IEnumerable<TestMethod> BuildFrom(IMethodInfo method, Test suite)
441 TestMethod test = new NUnitTestCaseBuilder().BuildTestMethod(method, suite, GetParametersForTestCase(method));
444 if (test.RunState != RunState.NotRunnable &&
445 test.RunState != RunState.Ignored)
447 PlatformHelper platformHelper = new PlatformHelper();
449 if (!platformHelper.IsPlatformSupported(this))
451 test.RunState = RunState.Skipped;
452 test.Properties.Add(PropertyNames.SkipReason, platformHelper.Reason);