PageRenderTime 46ms CodeModel.GetById 14ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NUnit/core/Builders/NUnitTestCaseBuilder.cs

#
C# | 390 lines | 240 code | 53 blank | 97 comment | 75 complexity | d6967d493eed6d45c78182a57b940c37 MD5 | raw file
  1// ****************************************************************
  2// Copyright 2008, Charlie Poole
  3// This is free software licensed under the NUnit license. You may
  4// obtain a copy of the license at http://nunit.org.
  5// ****************************************************************
  6
  7using System;
  8using System.Reflection;
  9using NUnit.Core.Extensibility;
 10
 11namespace NUnit.Core.Builders
 12{
 13    /// <summary>
 14    /// Class to build ether a parameterized or a normal NUnitTestMethod.
 15    /// There are four cases that the builder must deal with:
 16    ///   1. The method needs no params and none are provided
 17    ///   2. The method needs params and they are provided
 18    ///   3. The method needs no params but they are provided in error
 19    ///   4. The method needs params but they are not provided
 20    /// This could have been done using two different builders, but it
 21    /// turned out to be simpler to have just one. The BuildFrom method
 22    /// takes a different branch depending on whether any parameters are
 23    /// provided, but all four cases are dealt with in lower-level methods
 24    /// </summary>
 25    public class NUnitTestCaseBuilder : ITestCaseBuilder2
 26	{
 27        private readonly bool allowOldStyleTests = NUnitConfiguration.AllowOldStyleTests;
 28
 29        #region ITestCaseBuilder Methods
 30        /// <summary>
 31        /// Determines if the method can be used to build an NUnit test
 32        /// test method of some kind. The method must normally be marked
 33        /// with an identifying attriute for this to be true. If the test
 34        /// config file sets AllowOldStyleTests to true, then any method beginning 
 35        /// "test..." (case-insensitive) is treated as a test unless 
 36        /// it is also marked as a setup or teardown method.
 37        /// 
 38        /// Note that this method does not check that the signature
 39        /// of the method for validity. If we did that here, any
 40        /// test methods with invalid signatures would be passed
 41        /// over in silence in the test run. Since we want such
 42        /// methods to be reported, the check for validity is made
 43        /// in BuildFrom rather than here.
 44        /// </summary>
 45        /// <param name="method">A MethodInfo for the method being used as a test method</param>
 46        /// <param name="suite">The test suite being built, to which the new test would be added</param>
 47        /// <returns>True if the builder can create a test case from this method</returns>
 48        public bool CanBuildFrom(MethodInfo method)
 49        {
 50            return Reflect.HasAttribute(method, NUnitFramework.TestAttribute, false)
 51                || Reflect.HasAttribute(method, NUnitFramework.TestCaseAttribute, false)
 52                || Reflect.HasAttribute(method, NUnitFramework.TestCaseSourceAttribute, false)
 53                || Reflect.HasAttribute(method, NUnitFramework.TheoryAttribute, false)
 54                || allowOldStyleTests && method.Name.ToLower().StartsWith("test")
 55                && !Reflect.HasAttribute(method, NUnitFramework.SetUpAttribute, true)
 56                && !Reflect.HasAttribute(method, NUnitFramework.TearDownAttribute, true)
 57                && !Reflect.HasAttribute(method, NUnitFramework.FixtureSetUpAttribute, true)
 58                && !Reflect.HasAttribute(method, NUnitFramework.FixtureTearDownAttribute, true);
 59        }
 60
 61		/// <summary>
 62        /// Build a Test from the provided MethodInfo. Depending on
 63        /// whether the method takes arguments and on the availability
 64        /// of test case data, this method may return a single test
 65        /// or a group of tests contained in a ParameterizedMethodSuite.
 66        /// </summary>
 67        /// <param name="method">The MethodInfo for which a test is to be built</param>
 68        /// <param name="suite">The test fixture being populated, or null</param>
 69        /// <returns>A Test representing one or more method invocations</returns>
 70        public Test BuildFrom(MethodInfo method)
 71		{
 72            return BuildFrom(method, null);
 73        }
 74
 75        #region ITestCaseBuilder2 Members
 76
 77        public bool CanBuildFrom(MethodInfo method, Test parentSuite)
 78        {
 79            return CanBuildFrom(method);
 80        }
 81
 82        public Test BuildFrom(MethodInfo method, Test parentSuite)
 83        {
 84            return CoreExtensions.Host.TestCaseProviders.HasTestCasesFor(method)
 85                ? BuildParameterizedMethodSuite(method, parentSuite)
 86                : BuildSingleTestMethod(method, parentSuite, null);
 87        }
 88
 89        #endregion
 90
 91        /// <summary>
 92        /// Builds a ParameterizedMetodSuite containing individual
 93        /// test cases for each set of parameters provided for
 94        /// this method.
 95        /// </summary>
 96        /// <param name="method">The MethodInfo for which a test is to be built</param>
 97        /// <returns>A ParameterizedMethodSuite populated with test cases</returns>
 98        public static Test BuildParameterizedMethodSuite(MethodInfo method, Test parentSuite)
 99        {
100            ParameterizedMethodSuite methodSuite = new ParameterizedMethodSuite(method);
101            NUnitFramework.ApplyCommonAttributes(method, methodSuite);
102
103            foreach (object source in CoreExtensions.Host.TestCaseProviders.GetTestCasesFor(method, parentSuite))
104            {
105                ParameterSet parms;
106
107                if (source == null)
108                {
109                    parms = new ParameterSet();
110                    parms.Arguments = new object[] { null };
111                }
112                else
113                    parms = source as ParameterSet;
114
115                if (parms == null)
116                {
117                    if (source.GetType().GetInterface("NUnit.Framework.ITestCaseData") != null)
118                        parms = ParameterSet.FromDataSource(source);
119                    else
120                    {
121                        parms = new ParameterSet();
122
123                        ParameterInfo[] parameters = method.GetParameters();
124                        Type sourceType = source.GetType();
125
126                        if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(sourceType))
127                            parms.Arguments = new object[] { source };
128                        else if (source is object[])
129                            parms.Arguments = (object[])source;
130                        else if (source is Array)
131                        {
132                            Array array = (Array)source;
133                            if (array.Rank == 1)
134                            {
135                                parms.Arguments = new object[array.Length];
136                                for (int i = 0; i < array.Length; i++)
137                                    parms.Arguments[i] = (object)array.GetValue(i);
138                            }
139                        }
140                        else
141                            parms.Arguments = new object[] { source };
142                    }
143                }
144
145                TestMethod test = BuildSingleTestMethod(method, parentSuite, parms);
146
147                methodSuite.Add(test);
148            }
149
150            return methodSuite;
151        }
152
153        /// <summary>
154        /// Builds a single NUnitTestMethod, either as a child of the fixture 
155        /// or as one of a set of test cases under a ParameterizedTestMethodSuite.
156        /// </summary>
157        /// <param name="method">The MethodInfo from which to construct the TestMethod</param>
158        /// <param name="parms">The ParameterSet to be used, or null</param>
159        /// <returns></returns>
160        public static NUnitTestMethod BuildSingleTestMethod(MethodInfo method, Test parentSuite, ParameterSet parms)
161        {
162            NUnitTestMethod testMethod = new NUnitTestMethod(method);
163
164            string prefix = method.ReflectedType.FullName;
165
166            if (parentSuite != null)
167            {
168                prefix = parentSuite.TestName.FullName;
169                testMethod.TestName.FullName = prefix + "." + testMethod.TestName.Name;
170            }
171
172            if (CheckTestMethodSignature(testMethod, parms))
173            {
174                if (parms == null)
175                    NUnitFramework.ApplyCommonAttributes(method, testMethod);
176                NUnitFramework.ApplyExpectedExceptionAttribute(method, testMethod);
177            }
178
179            if (parms != null)
180            {
181                // NOTE: After the call to CheckTestMethodSignature, the Method
182                // property of testMethod may no longer be the same as the
183                // original MethodInfo, so we reassign it here.
184                method = testMethod.Method;
185
186                if (parms.TestName != null)
187                {
188                    testMethod.TestName.Name = parms.TestName;
189                    testMethod.TestName.FullName = prefix + "." + parms.TestName;
190                }
191                else if (parms.OriginalArguments != null)
192                {
193                    string name = MethodHelper.GetDisplayName(method, parms.OriginalArguments);
194                    testMethod.TestName.Name = name;
195                    testMethod.TestName.FullName = prefix + "." + name;
196                }
197
198                if (parms.Ignored)
199                {
200                    testMethod.RunState = RunState.Ignored;
201                    testMethod.IgnoreReason = parms.IgnoreReason;
202                }
203
204                if (parms.ExpectedExceptionName != null)
205                    testMethod.exceptionProcessor = new ExpectedExceptionProcessor(testMethod, parms);
206
207                foreach (string key in parms.Properties.Keys)
208                    testMethod.Properties[key] = parms.Properties[key];
209
210                // Description is stored in parms.Properties
211                if (parms.Description != null)
212                    testMethod.Description = parms.Description;
213            }
214
215            if (testMethod.BuilderException != null)
216                testMethod.RunState = RunState.NotRunnable;
217
218            return testMethod;
219        }
220		#endregion
221
222        #region Helper Methods
223        /// <summary>
224        /// Helper method that checks the signature of a TestMethod and
225        /// any supplied parameters to determine if the test is valid.
226        /// 
227        /// Currently, NUnitTestMethods are required to be public, 
228        /// non-abstract methods, either static or instance,
229        /// returning void. They may take arguments but the values must
230        /// be provided or the TestMethod is not considered runnable.
231        /// 
232        /// Methods not meeting these criteria will be marked as
233        /// non-runnable and the method will return false in that case.
234        /// </summary>
235        /// <param name="testMethod">The TestMethod to be checked. If it
236        /// is found to be non-runnable, it will be modified.</param>
237        /// <param name="parms">Parameters to be used for this test, or null</param>
238        /// <returns>True if the method signature is valid, false if not</returns>
239        private static bool CheckTestMethodSignature(TestMethod testMethod, ParameterSet parms)
240		{
241            if (testMethod.Method.IsAbstract)
242            {
243                testMethod.RunState = RunState.NotRunnable;
244                testMethod.IgnoreReason = "Method is abstract";
245                return false;
246            }
247
248            if (!testMethod.Method.IsPublic)
249            {
250                testMethod.RunState = RunState.NotRunnable;
251                testMethod.IgnoreReason = "Method is not public";
252                return false;
253            }
254
255            ParameterInfo[] parameters = testMethod.Method.GetParameters();
256            int argsNeeded = parameters.Length;
257
258            object[] arglist = null;
259            int argsProvided = 0;
260
261            if (parms != null)
262            {
263                testMethod.arguments = parms.Arguments;
264                testMethod.expectedResult = parms.Result;
265                testMethod.hasExpectedResult = parms.HasExpectedResult;
266                testMethod.RunState = parms.RunState;
267                testMethod.IgnoreReason = parms.NotRunReason;
268                testMethod.BuilderException = parms.ProviderException;
269
270                arglist = parms.Arguments;
271
272                if (arglist != null)
273                    argsProvided = arglist.Length;
274
275                if (testMethod.RunState != RunState.Runnable)
276                    return false;
277            }
278
279            if (!testMethod.Method.ReturnType.Equals(typeof(void)) &&
280                (parms == null || !parms.HasExpectedResult && parms.ExpectedExceptionName == null))
281            {
282                testMethod.RunState = RunState.NotRunnable;
283                testMethod.IgnoreReason = "Method has non-void return value";
284                return false;
285            }
286
287            if (argsProvided > 0 && argsNeeded == 0)
288            {
289                testMethod.RunState = RunState.NotRunnable;
290                testMethod.IgnoreReason = "Arguments provided for method not taking any";
291                return false;
292            }
293
294            if (argsProvided == 0 && argsNeeded > 0)
295            {
296                testMethod.RunState = RunState.NotRunnable;
297                testMethod.IgnoreReason = "No arguments were provided";
298                return false;
299            }
300
301            //if (argsProvided > argsNeeded)
302            //{
303            //    ParameterInfo lastParameter = parameters[argsNeeded - 1];
304            //    Type lastParameterType = lastParameter.ParameterType;
305
306            //    if (lastParameterType.IsArray && lastParameter.IsDefined(typeof(ParamArrayAttribute), false))
307            //    {
308            //        object[] newArglist = new object[argsNeeded];
309            //        for (int i = 0; i < argsNeeded; i++)
310            //            newArglist[i] = arglist[i];
311
312            //        int length = argsProvided - argsNeeded + 1;
313            //        Array array = Array.CreateInstance(lastParameterType.GetElementType(), length);
314            //        for (int i = 0; i < length; i++)
315            //            array.SetValue(arglist[argsNeeded + i - 1], i);
316
317            //        newArglist[argsNeeded - 1] = array;
318            //        testMethod.arguments = arglist = newArglist;
319            //        argsProvided = argsNeeded;
320            //    }
321            //}
322
323            if (argsProvided != argsNeeded )
324            {
325                testMethod.RunState = RunState.NotRunnable;
326                testMethod.IgnoreReason = "Wrong number of arguments provided";
327                return false;
328            }
329
330#if NET_2_0
331            if (testMethod.Method.IsGenericMethodDefinition)
332            {
333                Type[] typeArguments = GetTypeArgumentsForMethod(testMethod.Method, arglist);
334                foreach (object o in typeArguments)
335                    if (o == null)
336                    {
337                        testMethod.RunState = RunState.NotRunnable;
338                        testMethod.IgnoreReason = "Unable to determine type arguments for fixture";
339                        return false;
340                    }
341
342                testMethod.method = testMethod.Method.MakeGenericMethod(typeArguments);
343                parameters = testMethod.Method.GetParameters();
344
345                for (int i = 0; i < parameters.Length; i++)
346                {
347                    if (arglist[i].GetType() != parameters[i].ParameterType && arglist[i] is IConvertible)
348                    {
349                        try
350                        {
351                            arglist[i] = Convert.ChangeType(arglist[i], parameters[i].ParameterType);
352                        }
353                        catch (Exception)
354                        {
355                            // Do nothing - the incompatible argument will be reported below
356                        }
357                    }
358                }
359            }
360#endif
361
362            return true;
363        }
364
365#if NET_2_0
366        private static Type[] GetTypeArgumentsForMethod(MethodInfo method, object[] arglist)
367        {
368            Type[] typeParameters = method.GetGenericArguments();
369            Type[] typeArguments = new Type[typeParameters.Length];
370            ParameterInfo[] parameters = method.GetParameters();
371
372            for (int typeIndex = 0; typeIndex < typeArguments.Length; typeIndex++)
373            {
374                Type typeParameter = typeParameters[typeIndex];
375
376                for (int argIndex = 0; argIndex < parameters.Length; argIndex++)
377                {
378                    if (parameters[argIndex].ParameterType.Equals(typeParameter))
379                        typeArguments[typeIndex] = TypeHelper.BestCommonType(
380                            typeArguments[typeIndex],
381                            arglist[argIndex].GetType());
382                }
383            }
384
385            return typeArguments;
386        }
387#endif
388        #endregion
389    }
390}