blob: 5fc529b308f527654525c0ee1628ac5df594f17f [file] [log] [blame]
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Lucene.Net.Util
{
#region Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License.
// Copyright (c) 2021 Charlie Poole, Rob Prouse
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#endregion
/// <summary>
/// NUnitTestFixtureBuilder is able to build a fixture given
/// a class marked with a TestFixtureAttribute or an unmarked
/// class containing test methods. In the first case, it is
/// called by the attribute and in the second directly by
/// NUnitSuiteBuilder.
/// </summary>
internal class NUnitTestFixtureBuilder
{
const int SETUP_FIXTURE_SEED_OFFSET = 7;
const int TEST_FIXTURE_SEED_OFFSET = 3;
private static RandomizedContext setUpFixtureRandomizedContext = null;
#region Messages
const string NO_TYPE_ARGS_MSG =
"Fixture type contains generic parameters. You must either provide Type arguments or specify constructor arguments that allow NUnit to deduce the Type arguments.";
const string PARALLEL_NOT_ALLOWED_MSG =
"ParallelizableAttribute is only allowed on test methods and fixtures";
#endregion
#region Instance Fields
private readonly ITestCaseBuilder _testBuilder = new DefaultTestCaseBuilder();
private readonly LuceneSetUpFixtureBuilder _setUpFixtureBuilder = new LuceneSetUpFixtureBuilder();
private readonly LuceneRandomSeedInitializer _randomSeedInitializer = new LuceneRandomSeedInitializer();
#endregion
#region Public Methods
/// <summary>
/// Build a TestFixture from type provided. A non-null TestSuite
/// must always be returned, since the method is generally called
/// because the user has marked the target class as a fixture.
/// If something prevents the fixture from being used, it should
/// be returned nonetheless, labelled as non-runnable.
/// </summary>
/// <param name="typeInfo">An ITypeInfo for the fixture to be used.</param>
/// <param name="filter">Filter used to select methods as tests.</param>
/// <returns>A TestSuite object or one derived from TestSuite.</returns>
// TODO: This should really return a TestFixture, but that requires changes to the Test hierarchy.
public TestSuite BuildFrom(ITypeInfo typeInfo, IPreFilter filter)
{
// Build our custom SetUpFixture to get the NUnit runner to initialize us
// even though we don't own the test assembly.
var setUpFixture = _setUpFixtureBuilder.BuildFrom(typeInfo);
var fixture = new TestFixture(typeInfo);
SetUpRandomizedContext(setUpFixture, fixture);
if (fixture.RunState != RunState.NotRunnable)
CheckTestFixtureIsValid(fixture);
fixture.ApplyAttributesToTest(typeInfo.Type.GetTypeInfo());
AddTestCasesToFixture(fixture, filter);
setUpFixture.Add(fixture);
return setUpFixture;
}
/// <summary>
/// Overload of BuildFrom called by tests that have arguments.
/// Builds a fixture using the provided type and information
/// in the ITestFixtureData object.
/// </summary>
/// <param name="typeInfo">The TypeInfo for which to construct a fixture.</param>
/// <param name="filter">Filter used to select methods as tests.</param>
/// <param name="testFixtureData">An object implementing ITestFixtureData or null.</param>
/// <returns></returns>
public TestSuite BuildFrom(ITypeInfo typeInfo, IPreFilter filter, ITestFixtureData testFixtureData)
{
//Guard.ArgumentNotNull(testFixtureData, nameof(testFixtureData));
if (testFixtureData is null)
throw new ArgumentNullException(nameof(testFixtureData));
object[] arguments = testFixtureData.Arguments;
if (typeInfo.ContainsGenericParameters)
{
Type[] typeArgs = testFixtureData.TypeArgs;
if (typeArgs is null || typeArgs.Length == 0)
{
int cnt = 0;
foreach (object o in arguments)
if (o is Type) cnt++;
else break;
typeArgs = new Type[cnt];
for (int i = 0; i < cnt; i++)
typeArgs[i] = (Type)arguments[i];
if (cnt > 0)
{
object[] args = new object[arguments.Length - cnt];
for (int i = 0; i < args.Length; i++)
args[i] = arguments[cnt + i];
arguments = args;
}
}
if (typeArgs.Length > 0 ||
TypeHelper.CanDeduceTypeArgsFromArgs(typeInfo.Type, arguments, ref typeArgs))
{
typeInfo = typeInfo.MakeGenericType(typeArgs);
}
}
// Build our custom SetUpFixture to get the NUnit runner to initialize us
// even though we don't own the test assembly.
var setUpFixture = _setUpFixtureBuilder.BuildFrom(typeInfo);
var fixture = new TestFixture(typeInfo, arguments);
SetUpRandomizedContext(setUpFixture, fixture);
string name = fixture.Name;
if (testFixtureData.TestName != null)
{
fixture.Name = testFixtureData.TestName;
}
else
{
//var argDisplayNames = (testFixtureData as NUnit.Framework.Internal.TestParameters)?.ArgDisplayNames;
var testParameters = testFixtureData as NUnit.Framework.Internal.TestParameters;
string[] argDisplayNames = null;
if (testParameters != null)
{
// Hack so we can call the same internal field that NUnit does
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
FieldInfo field = typeof(NUnit.Framework.Internal.TestParameters).GetField("_argDisplayNames", bindFlags);
argDisplayNames = (string[])field.GetValue(testFixtureData);
}
if (argDisplayNames != null)
{
fixture.Name = typeInfo.GetDisplayName();
if (argDisplayNames.Length != 0)
fixture.Name += '(' + string.Join(", ", argDisplayNames) + ')';
}
else if (arguments != null && arguments.Length > 0)
{
fixture.Name = typeInfo.GetDisplayName(arguments);
}
}
if (fixture.Name != name) // name was changed
{
string nspace = typeInfo.Namespace;
fixture.FullName = nspace != null && nspace != ""
? nspace + "." + fixture.Name
: fixture.Name;
}
if (fixture.RunState != RunState.NotRunnable)
fixture.RunState = testFixtureData.RunState;
foreach (string key in testFixtureData.Properties.Keys)
foreach (object val in testFixtureData.Properties[key])
fixture.Properties.Add(key, val);
if (fixture.RunState != RunState.NotRunnable)
CheckTestFixtureIsValid(fixture);
fixture.ApplyAttributesToTest(typeInfo.Type.GetTypeInfo());
AddTestCasesToFixture(fixture, filter);
setUpFixture.Add(fixture);
return setUpFixture;
}
#endregion
#region Helper Methods
/// <summary>
/// Sets up the randomized context for both the set up fixture and the test fixture.
/// We use the same instance for every set up fixture in the assembly, but each test
/// fixture has its own distinct randomized context instance.
/// </summary>
/// <param name="setUpFixture">The setup fixture.</param>
/// <param name="testFixture">The test fixture.</param>
private void SetUpRandomizedContext(Test setUpFixture, Test testFixture)
{
// Setup the factories so we can read the random seed from the system properties
LuceneTestCase.SetUpFixture.EnsureInitialized(setUpFixture, testFixture);
// Reuse the same randomized context for each setup fixture instance, since these all need to report
// the same seed. Note that setUpFixtureRandomizedContext is static, so we do this once per assembly.
if (setUpFixtureRandomizedContext is null)
setUpFixtureRandomizedContext = _randomSeedInitializer.InitializeTestFixture(setUpFixture, testFixture.TypeInfo.Assembly, SETUP_FIXTURE_SEED_OFFSET);
else
_randomSeedInitializer.InitializeTestFixture(setUpFixture, setUpFixtureRandomizedContext);
_randomSeedInitializer.InitializeTestFixture(testFixture, testFixture.TypeInfo.Assembly, TEST_FIXTURE_SEED_OFFSET);
}
/// <summary>
/// Method to add test cases to the newly constructed fixture.
/// </summary>
private void AddTestCasesToFixture(TestFixture fixture, IPreFilter filter)
{
// TODO: Check this logic added from Neil's build.
if (fixture.TypeInfo.ContainsGenericParameters)
{
fixture.MakeInvalid(NO_TYPE_ARGS_MSG);
return;
}
// We sort the methods in a deterministic order, since BuildTestCase() will invoke the
// Randomizer to create seeds for each test. We want those seeds to be deterministically repeatable
// so we can re-run the same conditions by manually fixing the Randomizer.InitialSeed.
var methods = new SortedSet<IMethodInfo>(fixture.TypeInfo.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static),
MethodInfoComparer.Default);
foreach (IMethodInfo method in methods)
{
// Generate the seed whether or not we use a filter to ensure we invoke the Randomizer to
// move to the next random test seed (a test should always get the same seed in the sequence).
Test test = BuildTestCase(method, fixture);
_randomSeedInitializer.GenerateRandomSeeds(test);
if (filter.IsMatch(fixture.TypeInfo.Type, method.MethodInfo))
{
if (test != null)
fixture.Add(test);
else // it's not a test, check for disallowed attributes
if (method.MethodInfo.HasAttribute<ParallelizableAttribute>(false))
fixture.MakeInvalid(PARALLEL_NOT_ALLOWED_MSG);
}
}
}
/// <summary>
/// Method to create a test case from a MethodInfo and add
/// it to the fixture being built. It first checks to see if
/// any global TestCaseBuilder addin wants to build the
/// test case. If not, it uses the internal builder
/// collection maintained by this fixture builder.
///
/// The default implementation has no test case builders.
/// Derived classes should add builders to the collection
/// in their constructor.
/// </summary>
/// <param name="method">The method for which a test is to be created</param>
/// <param name="suite">The test suite being built.</param>
/// <returns>A newly constructed Test</returns>
private Test BuildTestCase(IMethodInfo method, TestSuite suite)
{
return _testBuilder.CanBuildFrom(method, suite)
? _testBuilder.BuildFrom(method, suite)
: null;
}
private static void CheckTestFixtureIsValid(TestFixture fixture)
{
if (fixture.TypeInfo.ContainsGenericParameters)
{
fixture.MakeInvalid(NO_TYPE_ARGS_MSG);
}
else if (!fixture.TypeInfo.IsStaticClass)
{
Type[] argTypes = /*Reflect.*/GetTypeArray(fixture.Arguments);
if (!/*Reflect.*/GetConstructors(fixture.TypeInfo.Type, argTypes).Any())
{
fixture.MakeInvalid("No suitable constructor was found");
}
}
}
/// <summary>
/// Returns an array of types from an array of objects.
/// Differs from <see cref="M:System.Type.GetTypeArray(System.Object[])"/> by returning <see langword="null"/>
/// for null elements rather than throwing <see cref="ArgumentNullException"/>.
/// </summary>
internal static Type[] GetTypeArray(object[] objects)
{
Type[] types = new Type[objects.Length];
int index = 0;
foreach (object o in objects)
{
types[index++] = o?.GetType();
}
return types;
}
/// <summary>
/// Gets the constructors to which the specified argument types can be coerced.
/// </summary>
internal static IEnumerable<ConstructorInfo> GetConstructors(Type type, Type[] matchingTypes)
{
return type
.GetConstructors()
.Where(c => c.GetParameters().ParametersMatch(matchingTypes));
}
#endregion
}
internal class MethodInfoComparer : IComparer<IMethodInfo>
{
private MethodInfoComparer() { } // LUCENENT: Made into singleton
public static IComparer<IMethodInfo> Default { get; } = new MethodInfoComparer();
public int Compare(IMethodInfo x, IMethodInfo y)
{
StringComparer stringComparer = StringComparer.Ordinal;
int nameCompare = stringComparer.Compare(x.Name, y.Name);
if (nameCompare != 0)
return nameCompare;
var xParameters = x.GetParameters();
var yParameters = y.GetParameters();
if (xParameters.Length > yParameters.Length)
return 1;
if (xParameters.Length < yParameters.Length)
return -1;
for (int i = 0; i < xParameters.Length; i++)
{
var px = xParameters[i];
var py = xParameters[i];
int parameterTypeCompare = stringComparer.Compare(px.ParameterType.FullName, py.ParameterType.FullName);
if (parameterTypeCompare != 0)
return parameterTypeCompare;
}
return 0;
}
}
internal static class Extensions
{
// From NUnit's Reflect class
/// <summary>
/// Determines if the given types can be coerced to match the given parameters.
/// </summary>
internal static bool ParametersMatch(this ParameterInfo[] pinfos, Type[] ptypes)
{
if (pinfos.Length != ptypes.Length)
return false;
for (int i = 0; i < pinfos.Length; i++)
{
if (!ptypes[i].CanImplicitlyConvertTo(pinfos[i].ParameterType))
return false;
}
return true;
}
// §6.1.2 (Implicit numeric conversions) of the specification
private static readonly Dictionary<Type, List<Type>> convertibleValueTypes = new Dictionary<Type, List<Type>>() {
{ typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
{ typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
{ typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
{ typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
{ typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
{ typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } },
{ typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
{ typeof(ushort), new List<Type> { typeof(byte), typeof(char) } },
{ typeof(short), new List<Type> { typeof(byte) } }
};
/// <summary>
/// Determines whether the current type can be implicitly converted to the specified type.
/// </summary>
internal static bool CanImplicitlyConvertTo(this Type from, Type to)
{
if (to.IsAssignableFrom(from))
return true;
// Look for the marker that indicates from was null
if (from is null && (to.GetTypeInfo().IsClass || to.FullName.StartsWith("System.Nullable")))
return true;
if (convertibleValueTypes.ContainsKey(to) && convertibleValueTypes[to].Contains(from))
return true;
return from
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Any(m => m.ReturnType == to && m.Name == "op_Implicit");
}
internal static IEnumerable<Type> TypeAndBaseTypes(this Type type)
{
for (; type != null; type = type.GetTypeInfo().BaseType)
{
yield return type;
}
}
// From NUnit's Extensions class
public static bool IsStatic(this Type type)
{
return type.GetTypeInfo().IsAbstract && type.GetTypeInfo().IsSealed;
}
public static bool HasAttribute<T>(this ICustomAttributeProvider attributeProvider, bool inherit)
{
return attributeProvider.IsDefined(typeof(T), inherit);
}
public static bool HasAttribute<T>(this Type type, bool inherit)
{
return ((ICustomAttributeProvider)type.GetTypeInfo()).HasAttribute<T>(inherit);
}
public static T[] GetAttributes<T>(this ICustomAttributeProvider attributeProvider, bool inherit) where T : class
{
return (T[])attributeProvider.GetCustomAttributes(typeof(T), inherit);
}
public static T[] GetAttributes<T>(this Assembly assembly) where T : class
{
return assembly.GetAttributes<T>(inherit: false);
}
public static T[] GetAttributes<T>(this Type type, bool inherit) where T : class
{
return ((ICustomAttributeProvider)type.GetTypeInfo()).GetAttributes<T>(inherit);
}
}
}