blob: 1f57513fc190413ab8425c8abe00b8013c3d8136 [file] [log] [blame]
using J2N.Threading.Atomic;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace Lucene.Net.Util
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public abstract partial class LuceneTestCase
{
/// <summary>
/// Captures all of the OneTimeSetUp and OneTimeTearDown calls from NUnit and uses reference counting to ensure
/// a single <see cref="LuceneTestFrameworkInitializer"/> instance is shared between all <see cref="SetUpFixture"/>
/// test wrappers and that each of its <see cref="LuceneTestFrameworkInitializer.Initialize()"/>,
/// <see cref="LuceneTestFrameworkInitializer.TestFrameworkSetUp()"/>
/// <see cref="LuceneTestFrameworkInitializer.TestFrameworkTearDown()"/> methods are only executed once.
/// </summary>
internal class SetUpFixture
{
/// <summary>
/// The singleton instance of LuceneTestFrameworkInitializer that is called before and after all tests.
/// </summary>
private static LuceneTestFrameworkInitializer initializer;
private readonly static AtomicInt32 stackCount = new AtomicInt32(0);
/// <summary>
///
/// </summary>
/// <param name="setUpFixture">The setup wrapper fixture for the current context. This is the top level parent class. It is used to report exceptions.</param>
/// <param name="testFixture">The setup fixture for the current tests. This is the original NUnit test fixture.
/// We use it's assembly to scan for <see cref="LuceneTestFrameworkInitializer"/> subclasses.</param>
public static void EnsureInitialized(Test setUpFixture, Test testFixture)
{
LazyInitializer.EnsureInitialized(ref initializer, () => Initialize(setUpFixture, testFixture));
}
private static LuceneTestFrameworkInitializer Initialize(Test setUpFixture, Test testFixture)
{
var initializer = CreateInitializer(setUpFixture, testFixture);
// Allow the factories to be instantiated first. We do this to allow a
// dependency injection step, which can only be done once. No random context
// is availble here because we need to load the SystemProperties setup here before
// we can read them.
initializer.DoInitialize();
return initializer;
}
[OneTimeSetUp]
public void OneTimeSetUpWrapper()
{
//var fixture = TestExecutionContext.CurrentContext.CurrentTest;
if (stackCount.GetAndIncrement() == 0)
{
// Set up for assembly
// This is where a global application setup can be done that includes a randomized context,
// therefore LuceneTestCase.Random calls are allowed here and are repeatable.
initializer.DoTestFrameworkSetUp();
}
}
[OneTimeTearDown]
public void OneTimeTearDownWrapper()
{
if (stackCount.DecrementAndGet() == 0)
{
// Tear down for assembly
initializer.DoTestFrameworkTearDown();
}
}
private static LuceneTestFrameworkInitializer CreateInitializer(Test wrapperSetUpFixture, Test testFixture)
{
if (TryGetSetUpFixtureType(testFixture.TypeInfo.Assembly, out ITypeInfo setUpFixture, out Type[] candidateTypes))
{
// No need for Reflection in this case.
if (setUpFixture.Type.Equals(typeof(LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer)))
return new LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer();
if (!IsValidFixtureType(new TypeWrapper(setUpFixture.Type), out string reason))
{
wrapperSetUpFixture.MakeInvalid(reason);
return new LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer();
}
try
{
return (LuceneTestFrameworkInitializer)Reflect.Construct(setUpFixture.Type);
}
catch (Exception ex)
{
var exception = ex is TargetInvocationException ? ex.InnerException : ex;
wrapperSetUpFixture.MakeInvalid("The LuceneTestFrameworkInitializer subclass could not be instantiated.\n\n" + exception.ToString());
return new LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer();
}
}
else
{
wrapperSetUpFixture.MakeInvalid($"Multiple subclasses of {typeof(LuceneTestFrameworkInitializer).FullName} were found in {testFixture.TypeInfo.Assembly.FullName}. " +
$"Only 1 non-abstract subclass is allowed per assembly.\n\n" +
$"Types Found:\n" +
$"{string.Join("\n ", candidateTypes.Select(t => t.FullName))}");
return new LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer();
}
}
private static bool TryGetSetUpFixtureType(Assembly assembly, out ITypeInfo setUpFixtureType, out Type[] candidateTypes)
{
candidateTypes = (from assemblyType in assembly.GetTypes()
where typeof(LuceneTestFrameworkInitializer).IsAssignableFrom(assemblyType)
&& !assemblyType.GetTypeInfo().IsAbstract
select assemblyType).ToArray();
bool result;
switch (candidateTypes.Length)
{
case 0: // The user didn't subclass, so use ours
candidateTypes = new Type[] { typeof(LuceneTestFrameworkInitializer.DefaultLuceneTestFrameworkInitializer) };
result = true;
break;
case 1: // The user created 1 subclass, so use theirs
result = true;
break;
default: // The user created multiple subclasses - this is invalid. Return the first type so we can mark it invalid.
result = false;
break;
}
setUpFixtureType = new DefaultNamespaceTypeWrapper(candidateTypes[0]);
return result;
}
private static bool IsValidFixtureType(ITypeInfo typeInfo, out string reason)
{
if (!typeInfo.IsStaticClass)
{
if (typeInfo.IsAbstract)
{
reason = string.Format("{0} is an abstract class", typeInfo.FullName);
return false;
}
if (!typeInfo.HasConstructor(new Type[0]))
{
reason = string.Format("{0} does not have a default constructor", typeInfo.FullName);
return false;
}
}
var invalidAttributes = new Type[] {
typeof(OneTimeSetUpAttribute),
typeof(OneTimeTearDownAttribute),
typeof(SetUpAttribute),
typeof(TearDownAttribute)
};
foreach (Type invalidType in invalidAttributes)
if (typeInfo.HasMethodWithAttribute(invalidType))
{
reason = invalidType.Name + $" not allowed in a {nameof(LuceneTestFrameworkInitializer)} subclass.";
return false;
}
if (typeInfo.IsDefined<SetUpFixtureAttribute>(inherit: true))
{
reason = $"{nameof(SetUpFixtureAttribute)} not allowed on a {nameof(LuceneTestFrameworkInitializer)} subclass.";
return false;
}
reason = null;
return true;
}
}
}
}