-
Notifications
You must be signed in to change notification settings - Fork 641
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
Added DisposeAfterSuite/DisposeAfterTest feature to test framework #1096
Conversation
…st() and DisposeAfterSuite() and removed TestMarkerFailure and SuiteMarkerFailure, since this info is already available through NUnit's TestExecutionContext.
…omments to indicate why we don't need this class in .NET. It is not sensible to have it because result state is already tracked by NUnit and is context sensitive.
…Writer, and IndexReader were not being disposed in any of these tests. Not closing the wrapped reader was being detected by the test framework as a problem and casing it to fail the test run, so added using statements to each of these instances so they will be cleaned up at the end of each test.
… caused any failure, so we can debug more easily.
…to ignore the fact that the LineDocsFile is not being deleted.
…domizedtesting and is not exposed publicly by Lucene.
@@ -37,6 +40,12 @@ internal class RandomizedContext | |||
private readonly long randomSeed; | |||
private readonly string randomSeedAsHex; | |||
private readonly long testSeed; | |||
private List<IDisposable>? toDisposeAtEnd = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on using Lazy<ConcurrentBag<IDisposable>>
here with one of the thread-safe constructor options? That would remove the need for the locking. I would imagine in this case we don't need to worry about the "uninterruptible"-ness, but let me know if we do. I also suggest ConcurrentBag
in the case that the order doesn't matter, but if it does, we might need to think about using ConcurrentStack
instead, because you might want it to go in opposite order for disposal. Or ConcurrentQueue
would allow for insertion-order disposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was partially following the upstream code. https://github.com/randomizedtesting/randomizedtesting/blob/master/randomized-runner/src/main/java/com/carrotsearch/randomizedtesting/RandomizedContext.java#L150-L160.
The DisposeResources()
method will always be called exactly once on each test and once on each suite (class), so I suppose the lock is not strictly necessary, for now. We took a very different approach, though. Our RandomizedContext
instance is stored inside of the NUnit
test or suite instance. So, it is not very likely multiple threads will access the same instance of RandomizedContext
and if so, they will likely be threads applying to a single test. It will return to the main thread to dispose.
Disposal order is FIFO, since they also used a List<T>
in Java.
However, they are removing the list from the collection in closeResources()
inside of the lock before removing anything from the list. So, to be on the safe side, we should probably nullify that field after grabbing the instance and then dispose outside of the lock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up using your suggestion of Lazy<ConcurrentQueue<DisposableResourceInfo>>
after porting the ClosableResourceInfo
class from randomizedtesting.
DisposableResourceInfo
keeps track of the details of the caller of LuceneTestCase.DisposeAfterSuite()
(including the stack trace) so it can be used to log the caller details when an exception is thrown from Dispose()
. The cheapest way I could find to do this was to instantiate a StackTrace
instance at the point where we want to record the details and wait until after an exception is thrown to call ToString()
on it to allocate the string. Let me know if you can think of a more efficient way.
Also note that I didn't port the part of randomizedtesting
that gives each thread a unique name (yet). So at present, ThreadName
is always null
.
…e<IDisposable>> to eliminate locking
…hread name, and caller stack trace so this info can be included in the output if there is an exception during dispose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might have been somewhat misinterpreted about Lazy<T>
here, so added clarifying comments. I think this code can be simplified further and be made more thread-safe at the same time, as the coalesce is not thread-safe as-is.
/// Coordination at context level. | ||
/// </summary> | ||
private readonly object contextLock = new object(); | ||
private Lazy<ConcurrentQueue<IDisposable>>? toDisposeAtEnd = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is now "double-lazy" - and the coalesce below removes the thread-safety guarantees of Lazy<T>
. I think this should be definitely initialized to a new Lazy<T>(() => ...)
, either with the default thread-safety mode or an explicit one, rather than setting it to null. That way when .Value
is accessed, it will run the initializer callback if it hasn't been created in a thread-safe way.
This will also simplify the code below, because the property ToDisposeAtEnd
can then just be => toDisposeAtEnd.Value;
@@ -189,19 +184,11 @@ internal void AddDisposableAtEnd(IDisposable resource) | |||
|
|||
internal void DisposeResources() | |||
{ | |||
if (toDisposeAtEnd is not null) | |||
Lazy<ConcurrentQueue<IDisposable>>? toDispose = Interlocked.Exchange(ref toDisposeAtEnd, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified and still be thread-safe if the Lazy is non-nullable above. This line could be removed, and replace toDispose
with toDisposeAtEnd
below this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primary concern is about object lifetime. It is more important to ensure that the list of disposable objects is allowed to go out of scope than it is to ensure it is lazily created. The lifetime of RandomizedContext
is from the test discovery phase to the end of the test run and we need to ensure these disposable instances go out of scope after the test or class is finished executing. Calling Clear()
after each test and/or class would be expensive, so it is better to let the list go out of scope without clearing it by setting the field to null
.
I think that using a lock with a nullable List<T>
field is simpler. These aren't the only features that use the lock in the upstream code. It would allow the other features of RandomizedContext
to be added without having to go back and rework it later to use a lock again.
In addition, there won't be any lock contention caused by the test framework because NUnit is managing these events and they do not happen simultaneously. Lock contention could only happen if the user explicitly calls the DisposeAfterTest()
and/or DisposeAfterSuite()
methods in parallel. But for users, the TearDown()
and OneTimeTearDown()
methods are generally better options than these for calling Dispose()
on disposable objects.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify, there is 1 RandomizedContext
instance per class and 1 RandomizedContext
instance per test. So, when interacting with RandomizedContext.CurrentContext
, the instance that is retrieved is different depending on which test or class is currently being executed. What I am trying to achieve is to let the disposable instances that are associated with that specific instance of RandomizedContext
go out of scope as soon as the test is done with them. Neither the disposable objects or the list instance is used again after that point until the end of the test run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It didn't seem to me from reading the code like it needed to be set back to null, you would just dispose of all objects in the queue. If you then needed to dispose of more objects, they could be added to the same queue if the instance is shared and then disposed when cleared out again. But if you think it needs to be set to null, I defer to your judgement on that.
…egular List<T> to stay more closely aligned with the upstream randomizedtesting code.
…empty throw statement to preserve stack details, since we are still in the finally block.
…y if it is required
Added DisposeAfterSuite/DisposeAfterTest feature to test framework
Fixes #1094
Description
This adds the
LuceneTestCase.DisposeAfterTest()
andLuceneTestCase.DisposeAfterSuite()
methods to the test framework that exist in Lucene 4.8.0.It also removes the
LuceneTestCase.SuiteFailureMarker
property that wasn't implemented. NUnit already tracks the result state of the tests inNUnit.Framework.Internal.TestExectionContext.CurrentContext.TestResult.ResultState
andNUnit.Framework.TestContext.CurrentContext.Result.Outcome
in a context-sensitive and thread safe manner, so there is no need to reinvent the wheel with this. For the same reason, we don't need theTestFailureMarker
property orTestRuleMarkFailure
class, which are all part of this feature.The new feature also revealed that there were some directories that were not being disposed in
Lucene.Net.Tests.Grouping
which were also addressed.For some reason
TestFsts.TestRealTerms()
does not reliably cleanup its line file docs temp file on Windows, so this adds the[SuppressTempFileChecks]
attribute to suppress the issue when it happens (for now). At some point we should have a look at this, but it is not a blocker for the release.