diff --git a/src/Lucene.Net.TestFramework/Analysis/BaseTokenStreamTestCase.cs b/src/Lucene.Net.TestFramework/Analysis/BaseTokenStreamTestCase.cs index 6e5f3f8d31..050825893e 100644 --- a/src/Lucene.Net.TestFramework/Analysis/BaseTokenStreamTestCase.cs +++ b/src/Lucene.Net.TestFramework/Analysis/BaseTokenStreamTestCase.cs @@ -986,10 +986,6 @@ private static void CheckAnalysisConsistency(Random random, Analyzer a, bool use // LUCENENET: We are doing this in the finally block to ensure it happens // when there are exceptions thrown (such as when the assert fails). ts.Close(); - - // ... and we dispose of it because it's the last use of the token stream - // before being reassigned below - ts.Dispose(); } // verify reusing is "reproducable" and also get the normal tokenstream sanity checks @@ -1047,7 +1043,6 @@ private static void CheckAnalysisConsistency(Random random, Analyzer a, bool use finally { ts.Close(); - ts.Dispose(); } } else if (evilness == 7) @@ -1080,7 +1075,6 @@ private static void CheckAnalysisConsistency(Random random, Analyzer a, bool use finally { ts.Close(); - ts.Dispose(); } } } @@ -1185,7 +1179,6 @@ private static void CheckAnalysisConsistency(Random random, Analyzer a, bool use finally { ts.Close(); - ts.Dispose(); } if (field != null) diff --git a/src/Lucene.Net.Tests.TestFramework/Analysis/TestBaseTokenStreamTestCase.cs b/src/Lucene.Net.Tests.TestFramework/Analysis/TestBaseTokenStreamTestCase.cs new file mode 100644 index 0000000000..a4b82b4d1c --- /dev/null +++ b/src/Lucene.Net.Tests.TestFramework/Analysis/TestBaseTokenStreamTestCase.cs @@ -0,0 +1,141 @@ +using Lucene.Net.Analysis.Core; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Analysis.TokenAttributes; +using Lucene.Net.Analysis.Util; +using Lucene.Net.Attributes; +using Lucene.Net.Util; +using NUnit.Framework; + +#nullable enable + +namespace Lucene.Net.Analysis +{ + /* + * 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. + */ + + /// + /// Tests for . + /// + [TestFixture] + public class TestBaseTokenStreamTestCase : BaseTokenStreamTestCase + { + [Test] + [LuceneNetSpecific] // lucenenet#271 + public void TestTokenStreamNotUsedAfterDispose() + { + DisposeTrackingLowerCaseFilter? leakedReference = null; + + using (var a = Analyzer.NewAnonymous((_, reader) => + { + // copied from StandardAnalyzer, but purposefully leaking our dispose tracking reference + var src = new StandardTokenizer(LuceneVersion.LUCENE_48, reader); + src.MaxTokenLength = 255; + TokenStream tok = new StandardFilter(LuceneVersion.LUCENE_48, src); + tok = leakedReference = new DisposeTrackingLowerCaseFilter(LuceneVersion.LUCENE_48, tok); + return new TokenStreamComponents(src, tok); + })) + { + CheckAnalysisConsistency(Random, a, false, "This is a test to make sure dispose is called only once"); + } + + Assert.IsNotNull(leakedReference); + Assert.IsTrue(leakedReference!.IsDisposed, "Dispose was not called on the token stream"); + } + + /// + /// LUCENENET specific class for + /// that tracks whether was called, and throws an exception + /// if it is called more than once, or if other operations are called after it is disposed. + /// Code copied from . + /// + private class DisposeTrackingLowerCaseFilter : TokenFilter + { + private readonly CharacterUtils charUtils; + private readonly ICharTermAttribute termAtt; + + public DisposeTrackingLowerCaseFilter(LuceneVersion matchVersion, TokenStream @in) + : base(@in) + { + termAtt = AddAttribute(); + charUtils = CharacterUtils.GetInstance(matchVersion); + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + IsDisposed = true; + base.Dispose(disposing); + } + else + { + throw new AssertionException("Dispose called more than once on TokenStream instance"); + } + } + + public override bool IncrementToken() + { + if (IsDisposed) + { + throw new AssertionException("IncrementToken called after Dispose"); + } + + if (m_input.IncrementToken()) + { + charUtils.ToLower(termAtt.Buffer, 0, termAtt.Length); + return true; + } + else + { + return false; + } + } + + public override void End() + { + if (IsDisposed) + { + throw new AssertionException("End called after Dispose"); + } + + base.End(); + } + + public override void Reset() + { + if (IsDisposed) + { + throw new AssertionException("Reset called after Dispose"); + } + + base.Reset(); + } + + public override void Close() + { + if (IsDisposed) + { + throw new AssertionException("Close called after Dispose"); + } + + base.Close(); + } + + public bool IsDisposed { get; private set; } + } + } +}