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;
31 using System.Collections.Generic;
32 using System.Reflection;
33 using NUnit.Compatibility;
34 using NUnit.Framework.Interfaces;
35 using NUnit.Framework.Internal;
36 using NUnit.Framework.Internal.Builders;
38 namespace NUnit.Framework
41 /// TestCaseSourceAttribute indicates the source to be used to
42 /// provide test cases for a test method.
44 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
45 public class TestCaseSourceAttribute : NUnitAttribute, ITestBuilder, IImplyFixture
48 private NUnitTestCaseBuilder _builder = new NUnitTestCaseBuilder();
53 /// Construct with the name of the method, property or field that will provide data
55 /// <param name="sourceName">The name of a static method, property or field that will provide data.</param>
56 public TestCaseSourceAttribute(string sourceName)
58 this.SourceName = sourceName;
62 /// Construct with a Type and name
64 /// <param name="sourceType">The Type that will provide data</param>
65 /// <param name="sourceName">The name of a static method, property or field that will provide data.</param>
66 /// <param name="methodParams">A set of parameters passed to the method, works only if the Source Name is a method.
67 /// If the source name is a field or property has no effect.</param>
68 public TestCaseSourceAttribute(Type sourceType, string sourceName, object[] methodParams)
70 this.MethodParams = methodParams;
71 this.SourceType = sourceType;
72 this.SourceName = sourceName;
75 /// Construct with a Type and name
77 /// <param name="sourceType">The Type that will provide data</param>
78 /// <param name="sourceName">The name of a static method, property or field that will provide data.</param>
79 public TestCaseSourceAttribute(Type sourceType, string sourceName)
81 this.SourceType = sourceType;
82 this.SourceName = sourceName;
86 /// Construct with a Type
88 /// <param name="sourceType">The type that will provide data</param>
89 public TestCaseSourceAttribute(Type sourceType)
91 this.SourceType = sourceType;
98 /// A set of parameters passed to the method, works only if the Source Name is a method.
99 /// If the source name is a field or property has no effect.
101 public object[] MethodParams { get; private set; }
103 /// The name of a the method, property or fiend to be used as a source
105 public string SourceName { get; private set; }
108 /// A Type to be used as a source
110 public Type SourceType { get; private set; }
113 /// Gets or sets the category associated with every fixture created from
114 /// this attribute. May be a single category or a comma-separated list.
116 public string Category { get; set; }
120 #region ITestBuilder Members
123 /// Construct one or more TestMethods from a given MethodInfo,
124 /// using available parameter data.
126 /// <param name="method">The IMethod for which tests are to be constructed.</param>
127 /// <param name="suite">The suite to which the tests will be added.</param>
128 /// <returns>One or more TestMethods</returns>
129 public IEnumerable<TestMethod> BuildFrom(IMethodInfo method, Test suite)
131 foreach (TestCaseParameters parms in GetTestCasesFor(method))
132 yield return _builder.BuildTestMethod(method, suite, parms);
137 #region Helper Methods
140 /// Returns a set of ITestCaseDataItems for use as arguments
141 /// to a parameterized test method.
143 /// <param name="method">The method for which data is needed.</param>
144 /// <returns></returns>
145 private IEnumerable<ITestCaseData> GetTestCasesFor(IMethodInfo method)
147 List<ITestCaseData> data = new List<ITestCaseData>();
151 IEnumerable source = GetTestCaseSource(method);
155 foreach (object item in source)
157 // First handle two easy cases:
158 // 1. Source is null. This is really an error but if we
159 // throw an exception we simply get an invalid fixture
160 // without good info as to what caused it. Passing a
161 // single null argument will cause an error to be
162 // reported at the test level, in most cases.
163 // 2. User provided an ITestCaseData and we just use it.
164 ITestCaseData parms = item == null
165 ? new TestCaseParameters(new object[] { null })
166 : item as ITestCaseData;
170 // 3. An array was passed, it may be an object[]
171 // or possibly some other kind of array, which
172 // TestCaseSource can accept.
173 var args = item as object[];
174 if (args == null && item is Array)
176 Array array = item as Array;
178 bool netcfOpenType = method.IsGenericMethodDefinition;
180 bool netcfOpenType = false;
182 int numParameters = netcfOpenType ? array.Length : method.GetParameters().Length;
183 if (array != null && array.Rank == 1 && array.Length == numParameters)
185 // Array is something like int[] - convert it to
186 // an object[] for use as the argument array.
187 args = new object[array.Length];
188 for (int i = 0; i < array.Length; i++)
189 args[i] = array.GetValue(i);
193 // Check again if we have an object[]
197 if (method.IsGenericMethodDefinition)
199 var mi = method.MakeGenericMethodEx(args);
201 throw new NotSupportedException("Cannot determine generic Type");
206 var parameters = method.GetParameters();
207 var argsNeeded = parameters.Length;
208 var argsProvided = args.Length;
210 // If only one argument is needed, our array may actually
211 // be the bare argument. If it is, we should wrap it in
212 // an outer object[] representing the list of arguments.
215 var singleParmType = parameters[0].ParameterType;
217 if (argsProvided == 0 || typeof(object[]).IsAssignableFrom(singleParmType))
219 if (argsProvided > 1 || singleParmType.IsAssignableFrom(args.GetType()))
221 args = new object[] { item };
226 else // It may be a scalar or a multi-dimensioned array. Wrap it in object[]
228 args = new object[] { item };
231 parms = new TestCaseParameters(args);
234 if (this.Category != null)
235 foreach (string cat in this.Category.Split(new char[] { ',' }))
236 parms.Properties.Add(PropertyNames.Category, cat);
245 data.Add(new TestCaseParameters(ex));
251 private IEnumerable GetTestCaseSource(IMethodInfo method)
253 Type sourceType = SourceType ?? method.TypeInfo.Type;
255 // Handle Type implementing IEnumerable separately
256 if (SourceName == null)
257 return Reflect.Construct(sourceType, null) as IEnumerable;
259 MemberInfo[] members = sourceType.GetMember(SourceName,
260 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
262 if (members.Length == 1)
264 MemberInfo member = members[0];
266 var field = member as FieldInfo;
268 return field.IsStatic
269 ? (MethodParams == null ? (IEnumerable)field.GetValue(null)
270 : ReturnErrorAsParameter(ParamGivenToField))
271 : ReturnErrorAsParameter(SourceMustBeStatic);
273 var property = member as PropertyInfo;
274 if (property != null)
275 return property.GetGetMethod(true).IsStatic
276 ? (MethodParams == null ? (IEnumerable)property.GetValue(null, null)
277 : ReturnErrorAsParameter(ParamGivenToProperty))
278 : ReturnErrorAsParameter(SourceMustBeStatic);
280 var m = member as MethodInfo;
285 ? (MethodParams == null || m.GetParameters().Length == MethodParams.Length ? (IEnumerable)m.Invoke(null, MethodParams)
286 : ReturnErrorAsParameter(NumberOfArgsDoesNotMatch))
287 : ReturnErrorAsParameter(SourceMustBeStatic);
293 private static IEnumerable ReturnErrorAsParameter(string errorMessage)
295 var parms = new TestCaseParameters();
296 parms.RunState = RunState.NotRunnable;
297 parms.Properties.Set(PropertyNames.SkipReason, errorMessage);
298 return new TestCaseParameters[] { parms };
301 private const string SourceMustBeStatic =
302 "The sourceName specified on a TestCaseSourceAttribute must refer to a static field, property or method.";
303 private const string ParamGivenToField = "You have specified a data source field but also given a set of parameters. Fields cannot take parameters, " +
304 "please revise the 3rd parameter passed to the TestCaseSourceAttribute and either remove " +
305 "it or specify a method.";
306 private const string ParamGivenToProperty = "You have specified a data source property but also given a set of parameters. " +
307 "Properties cannot take parameters, please revise the 3rd parameter passed to the " +
308 "TestCaseSource attribute and either remove it or specify a method.";
309 private const string NumberOfArgsDoesNotMatch = "You have given the wrong number of arguments to the method in the TestCaseSourceAttribute" +
310 ", please check the number of parameters passed in the object is correct in the 3rd parameter for the " +
311 "TestCaseSourceAttribute and this matches the number of parameters in the target method and try again.";