Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added DisposeAfterSuite/DisposeAfterTest feature to test framework #1096

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
24 changes: 24 additions & 0 deletions src/Lucene.Net.TestFramework/Support/Util/LifecycleScope.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lucene.Net.Util
{
/// <summary>
/// Lifecycle stages for tracking resources.
/// </summary>
internal enum LifecycleScope // From randomizedtesing
{
/// <summary>
/// A single test case.
/// </summary>
TEST,

/// <summary>
/// A single suite (class).
/// </summary>
SUITE
}
}
110 changes: 102 additions & 8 deletions src/Lucene.Net.TestFramework/Support/Util/RandomizedContext.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using J2N;
using Lucene.Net.Support.Threading;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
#nullable enable

namespace Lucene.Net.Util
{
Expand Down Expand Up @@ -37,6 +40,12 @@ internal class RandomizedContext
private readonly long randomSeed;
private readonly string randomSeedAsHex;
private readonly long testSeed;
private List<IDisposable>? toDisposeAtEnd = null;

/// <summary>
/// Coordination at context level.
/// </summary>
private readonly object contextLock = new object();

/// <summary>
/// Initializes the randomized context.
Expand Down Expand Up @@ -92,21 +101,106 @@ public RandomizedContext(Test currentTest, Assembly currentTestAssembly, long ra
/// random test data in these cases. Using the <see cref="LuceneTestCase.TestFixtureAttribute"/>
/// will set the seed properly and make it possible to repeat the result.
/// </summary>
public Random RandomGenerator => randomGenerator.Value;
public Random RandomGenerator => randomGenerator.Value!;

/// <summary>
/// Gets the randomized context for the current test or test fixture.
/// <para/>
/// If <c>null</c>, the call is being made out of context and the random test behavior
/// will not be repeatable.
/// </summary>
public static RandomizedContext CurrentContext
public static RandomizedContext? CurrentContext => TestExecutionContext.CurrentContext.CurrentTest.GetRandomizedContext();

/// <summary>
/// Registers the given <paramref name="resource"/> at the end of a given
/// lifecycle <paramref name="scope"/>.
/// </summary>
/// <typeparam name="T">Type of <see cref="IDisposable"/>.</typeparam>
/// <param name="resource">A resource to dispose.</param>
/// <param name="scope">The scope to dispose the resource in.</param>
/// <returns>The <paramref name="resource"/> (for chaining).</returns>
/// <remarks>
/// Due to limitations of NUnit, any exceptions or assertions raised
/// from the <paramref name="resource"/> will not be respected. However, if
/// you want to detect a failure, do note that the message from either one
/// will be printed to StdOut.
/// </remarks>
public T DisposeAtEnd<T>(T resource, LifecycleScope scope) where T : IDisposable
{
if (currentTest.IsTest())
{
if (scope == LifecycleScope.TEST)
{
AddDisposableAtEnd(resource);
}
else
{
var context = FindClassLevelTest(currentTest).GetRandomizedContext();
if (context is null)
throw new InvalidOperationException($"The provided {LifecycleScope.TEST} has no conceptual {LifecycleScope.SUITE} associated with it.");
context.AddDisposableAtEnd(resource);
}
}
else if (currentTest.IsTestClass())
{
AddDisposableAtEnd(resource);
}
else
{
throw new NotSupportedException("Only runnable tests and test classes are supported.");
}

return resource;
}

internal void AddDisposableAtEnd(IDisposable resource)
{
UninterruptableMonitor.Enter(contextLock);
try
{
if (toDisposeAtEnd is null)
toDisposeAtEnd = new List<IDisposable>();

toDisposeAtEnd.Add(resource);
}
finally
{
UninterruptableMonitor.Exit(contextLock);
}
}

private Test? FindClassLevelTest(Test test)
{
get
ITest? current = test;

while (current != null)
{
var currentTest = TestExecutionContext.CurrentContext.CurrentTest;
// Check if this test is at the class level
if (current.IsTestClass() && current is Test t)
{
return t;
}

current = current.Parent;
}

if (currentTest.Properties.ContainsKey(RandomizedContextPropertyName))
return (RandomizedContext)currentTest.Properties.Get(RandomizedContextPropertyName);
return null;
}

internal void DisposeResources()
{
if (toDisposeAtEnd is not null)
{
UninterruptableMonitor.Enter(contextLock);
try
{

return null; // We are out of random context and cannot respond with results that are repeatable.
IOUtils.Dispose(toDisposeAtEnd);
}
finally
{
UninterruptableMonitor.Exit(contextLock);
}
}
}
}
Expand Down
58 changes: 57 additions & 1 deletion src/Lucene.Net.TestFramework/Support/Util/TestExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework.Internal;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -28,6 +29,61 @@ namespace Lucene.Net.Util
/// </summary>
internal static class TestExtensions
{
/// <summary>
/// Retrieves the <see cref="RandomizedContext"/> instance for a given <see cref="Test"/>.
/// <para/>
///
/// </summary>
/// <param name="test"></param>
/// <returns>The <see cref="RandomizedContext"/>. If the <paramref name="test"/> is <c>null</c>
/// or doesn't have a <see cref="RandomizedContext"/>
/// (it doesn't belong to our framework), then the return value is <c>null</c>.</returns>
public static RandomizedContext? GetRandomizedContext(this Test? test)
{
if (test is null)
return null;

if (test.Properties.ContainsKey(RandomizedContext.RandomizedContextPropertyName))
return (RandomizedContext?)test.Properties.Get(RandomizedContext.RandomizedContextPropertyName);

return null;
}

/// <summary>
/// Determines if the current <paramref name="test"/> is a
/// type of runnable test (for example, a [Test] or [TestCase()] as
/// opposed to a class or other container).
/// </summary>
/// <param name="test">This <see cref="Test"/>.</param>
/// <returns><c>true</c> if the current <paramref name="test"/> is a
/// type of runnable test; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="test"/> is <c>null</c>.</exception>
public static bool IsTest(this ITest test)
{
if (test is null)
throw new ArgumentNullException(nameof(test));
if (test is Test t)
return !t.IsSuite;
return false;
}

/// <summary>
/// Determines if the current <paramref name="test"/> is a
/// test class (as opposed to a runnable test).
/// </summary>
/// <param name="test">This <see cref="Test"/>.</param>
/// <returns><c>true</c> if the current <paramref name="test"/> is a
/// test class; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool IsTestClass(this ITest test)
{
if (test is null)
throw new ArgumentNullException(nameof(test));
if (test is Test t)
return t.IsSuite && t.TypeInfo is not null;
return false;
}

/// <summary>
/// Mark the test and all descendents as Invalid (not runnable) specifying a reason and an exception.
/// </summary>
Expand Down
62 changes: 29 additions & 33 deletions src/Lucene.Net.TestFramework/Util/CloseableDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
#if TESTFRAMEWORK
// LUCENENET NOTE: This is incomplete
using Lucene.Net.Store;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;

namespace Lucene.Net.Util
{

using NUnit.Framework;
using System;
using BaseDirectoryWrapper = Lucene.Net.Store.BaseDirectoryWrapper;
using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper;
//using Assert = org.junit.Assert;

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
Expand All @@ -30,36 +26,36 @@ namespace Lucene.Net.Util
/// <summary>
/// Attempts to close a <seealso cref="BaseDirectoryWrapper"/>.
/// </summary>
/// <seealso> cref= LuceneTestCase#newDirectory(java.util.Random) </seealso>
internal sealed class IDisposableDirectory : IDisposable
/// <seealso cref="LuceneTestCase.NewDirectory(Random)"/>
internal sealed class DisposableDirectory : IDisposable
{
private readonly BaseDirectoryWrapper Dir;
private readonly TestRuleMarkFailure FailureMarker;
private readonly BaseDirectoryWrapper dir;

public IDisposableDirectory(BaseDirectoryWrapper dir, TestRuleMarkFailure failureMarker)
{
this.Dir = dir;
this.FailureMarker = failureMarker;
}

public void Dispose()
{
// We only attempt to check open/closed state if there were no other test
// failures.
try
public DisposableDirectory(BaseDirectoryWrapper dir)
{
if (FailureMarker.WasSuccessful() && Dir.Open)
{
Assert.Fail("Directory not closed: " + Dir);
}
this.dir = dir ?? throw new ArgumentNullException(nameof(dir));
}
finally

public void Dispose()
{
// TODO: perform real close of the delegate: LUCENE-4058
// dir.close();
// We only attempt to check open/closed state if there were no other test
// failures.
try
{
//if (FailureMarker.WasSuccessful() && dir.Open)
// LUCENENET NOTE: Outcome is context sensitive and only exists after a test run.
ResultState outcome = TestContext.CurrentContext.Result.Outcome;
if (outcome != ResultState.Failure && outcome != ResultState.Inconclusive && dir.IsOpen)
{
Assert.Fail($"Directory not disposed: {dir}");
}
}
finally
{
// TODO: perform real close of the delegate: LUCENE-4058
// dir.Dispose();
}
}
}
}

}
#endif
Loading
Loading