1 // ***********************************************************************
2 // Copyright (c) 2014-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.Reflection;
32 using NUnit.Compatibility;
33 using NUnit.Framework.Interfaces;
35 namespace NUnit.Framework.Internal.Builders
38 /// NUnitTestFixtureBuilder is able to build a fixture given
39 /// a class marked with a TestFixtureAttribute or an unmarked
40 /// class containing test methods. In the first case, it is
41 /// called by the attribute and in the second directly by
42 /// NUnitSuiteBuilder.
44 public class NUnitTestFixtureBuilder
48 static readonly string NO_TYPE_ARGS_MSG =
49 "Fixture type contains generic parameters. You must either provide " +
50 "Type arguments or specify constructor arguments that allow NUnit " +
51 "to deduce the Type arguments.";
55 #region Instance Fields
57 private ITestCaseBuilder _testBuilder = new DefaultTestCaseBuilder();
61 #region Public Methods
64 /// Build a TestFixture from type provided. A non-null TestSuite
65 /// must always be returned, since the method is generally called
66 /// because the user has marked the target class as a fixture.
67 /// If something prevents the fixture from being used, it should
68 /// be returned nonetheless, labelled as non-runnable.
70 /// <param name="typeInfo">An ITypeInfo for the fixture to be used.</param>
71 /// <returns>A TestSuite object or one derived from TestSuite.</returns>
72 // TODO: This should really return a TestFixture, but that requires changes to the Test hierarchy.
73 public TestSuite BuildFrom(ITypeInfo typeInfo)
75 var fixture = new TestFixture(typeInfo);
77 if (fixture.RunState != RunState.NotRunnable)
78 CheckTestFixtureIsValid(fixture);
80 fixture.ApplyAttributesToTest(typeInfo.Type.GetTypeInfo());
82 AddTestCasesToFixture(fixture);
88 /// Overload of BuildFrom called by tests that have arguments.
89 /// Builds a fixture using the provided type and information
90 /// in the ITestFixtureData object.
92 /// <param name="typeInfo">The TypeInfo for which to construct a fixture.</param>
93 /// <param name="testFixtureData">An object implementing ITestFixtureData or null.</param>
94 /// <returns></returns>
95 public TestSuite BuildFrom(ITypeInfo typeInfo, ITestFixtureData testFixtureData)
97 Guard.ArgumentNotNull(testFixtureData, "testFixtureData");
99 object[] arguments = testFixtureData.Arguments;
101 if (typeInfo.ContainsGenericParameters)
103 Type[] typeArgs = testFixtureData.TypeArgs;
104 if (typeArgs.Length == 0)
107 foreach (object o in arguments)
108 if (o is Type) cnt++;
111 typeArgs = new Type[cnt];
112 for (int i = 0; i < cnt; i++)
113 typeArgs[i] = (Type)arguments[i];
117 object[] args = new object[arguments.Length - cnt];
118 for (int i = 0; i < args.Length; i++)
119 args[i] = arguments[cnt + i];
125 if (typeArgs.Length > 0 ||
126 TypeHelper.CanDeduceTypeArgsFromArgs(typeInfo.Type, arguments, ref typeArgs))
128 typeInfo = typeInfo.MakeGenericType(typeArgs);
132 var fixture = new TestFixture(typeInfo);
134 if (arguments != null && arguments.Length > 0)
136 string name = fixture.Name = typeInfo.GetDisplayName(arguments);
137 string nspace = typeInfo.Namespace;
138 fixture.FullName = nspace != null && nspace != ""
139 ? nspace + "." + name
141 fixture.Arguments = arguments;
144 if (fixture.RunState != RunState.NotRunnable)
145 fixture.RunState = testFixtureData.RunState;
147 foreach (string key in testFixtureData.Properties.Keys)
148 foreach (object val in testFixtureData.Properties[key])
149 fixture.Properties.Add(key, val);
151 if (fixture.RunState != RunState.NotRunnable)
152 CheckTestFixtureIsValid(fixture);
154 fixture.ApplyAttributesToTest(typeInfo.Type.GetTypeInfo());
156 AddTestCasesToFixture(fixture);
163 #region Helper Methods
166 /// Method to add test cases to the newly constructed fixture.
168 /// <param name="fixture">The fixture to which cases should be added</param>
169 private void AddTestCasesToFixture(TestFixture fixture)
171 // TODO: Check this logic added from Neil's build.
172 if (fixture.TypeInfo.ContainsGenericParameters)
174 fixture.RunState = RunState.NotRunnable;
175 fixture.Properties.Set(PropertyNames.SkipReason, NO_TYPE_ARGS_MSG);
179 var methods = fixture.TypeInfo.GetMethods(
180 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
182 foreach (IMethodInfo method in methods)
184 Test test = BuildTestCase(method, fixture);
194 /// Method to create a test case from a MethodInfo and add
195 /// it to the fixture being built. It first checks to see if
196 /// any global TestCaseBuilder addin wants to build the
197 /// test case. If not, it uses the internal builder
198 /// collection maintained by this fixture builder.
200 /// The default implementation has no test case builders.
201 /// Derived classes should add builders to the collection
202 /// in their constructor.
204 /// <param name="method">The method for which a test is to be created</param>
205 /// <param name="suite">The test suite being built.</param>
206 /// <returns>A newly constructed Test</returns>
207 private Test BuildTestCase(IMethodInfo method, TestSuite suite)
209 return _testBuilder.CanBuildFrom(method, suite)
210 ? _testBuilder.BuildFrom(method, suite)
214 private static void CheckTestFixtureIsValid(TestFixture fixture)
216 if (fixture.TypeInfo.ContainsGenericParameters)
218 fixture.RunState = RunState.NotRunnable;
219 fixture.Properties.Set(PropertyNames.SkipReason, NO_TYPE_ARGS_MSG);
221 else if (!fixture.TypeInfo.IsStaticClass)
223 Type[] argTypes = Reflect.GetTypeArray(fixture.Arguments);
225 if (!fixture.TypeInfo.HasConstructor(argTypes))
227 fixture.RunState = RunState.NotRunnable;
228 fixture.Properties.Set(PropertyNames.SkipReason, "No suitable constructor was found");
233 private static bool IsStaticClass(Type type)
235 return type.GetTypeInfo().IsAbstract && type.GetTypeInfo().IsSealed;