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; }
+ }
+ }
+}