You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2017/02/26 23:36:56 UTC
[08/72] [abbrv] [partial] lucenenet git commit: Lucene.Net.Tests:
Removed \core directory and put its contents in root directory
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestCachingWrapperFilter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestCachingWrapperFilter.cs b/src/Lucene.Net.Tests/Search/TestCachingWrapperFilter.cs
new file mode 100644
index 0000000..2bbb21c
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestCachingWrapperFilter.cs
@@ -0,0 +1,513 @@
+using System;
+using Lucene.Net.Documents;
+
+namespace Lucene.Net.Search
+{
+ using Lucene.Net.Index;
+ using NUnit.Framework;
+ using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+ using IBits = Lucene.Net.Util.IBits;
+ using Directory = Lucene.Net.Store.Directory;
+ using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+ using Document = Documents.Document;
+ using Field = Field;
+ using FixedBitSet = Lucene.Net.Util.FixedBitSet;
+ using IndexReader = Lucene.Net.Index.IndexReader;
+ using IOUtils = Lucene.Net.Util.IOUtils;
+ using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
+
+ /*
+ * 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.
+ */
+
+ using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+ using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+ using SerialMergeScheduler = Lucene.Net.Index.SerialMergeScheduler;
+ using SlowCompositeReaderWrapper = Lucene.Net.Index.SlowCompositeReaderWrapper;
+ using StringField = StringField;
+ using Term = Lucene.Net.Index.Term;
+
+ [TestFixture]
+ public class TestCachingWrapperFilter : LuceneTestCase
+ {
+ internal Directory Dir;
+ internal DirectoryReader Ir;
+ internal IndexSearcher @is;
+ internal RandomIndexWriter Iw;
+
+ [SetUp]
+ public override void SetUp()
+ {
+ base.SetUp();
+ Dir = NewDirectory();
+ Iw = new RandomIndexWriter(Random(), Dir, Similarity, TimeZone);
+ Document doc = new Document();
+ Field idField = new StringField("id", "", Field.Store.NO);
+ doc.Add(idField);
+ // add 500 docs with id 0..499
+ for (int i = 0; i < 500; i++)
+ {
+ idField.SetStringValue(Convert.ToString(i));
+ Iw.AddDocument(doc);
+ }
+ // delete 20 of them
+ for (int i = 0; i < 20; i++)
+ {
+ Iw.DeleteDocuments(new Term("id", Convert.ToString(Random().Next(Iw.MaxDoc))));
+ }
+ Ir = Iw.Reader;
+ @is = NewSearcher(Ir);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ IOUtils.Close(Iw, Ir, Dir);
+ base.TearDown();
+ }
+
+ private void AssertFilterEquals(Filter f1, Filter f2)
+ {
+ Query query = new MatchAllDocsQuery();
+ TopDocs hits1 = @is.Search(query, f1, Ir.MaxDoc);
+ TopDocs hits2 = @is.Search(query, f2, Ir.MaxDoc);
+ Assert.AreEqual(hits1.TotalHits, hits2.TotalHits);
+ CheckHits.CheckEqual(query, hits1.ScoreDocs, hits2.ScoreDocs);
+ // now do it again to confirm caching works
+ TopDocs hits3 = @is.Search(query, f1, Ir.MaxDoc);
+ TopDocs hits4 = @is.Search(query, f2, Ir.MaxDoc);
+ Assert.AreEqual(hits3.TotalHits, hits4.TotalHits);
+ CheckHits.CheckEqual(query, hits3.ScoreDocs, hits4.ScoreDocs);
+ }
+
+ /// <summary>
+ /// test null iterator </summary>
+ [Test]
+ public virtual void TestEmpty()
+ {
+ Query query = new BooleanQuery();
+ Filter expected = new QueryWrapperFilter(query);
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+
+ /// <summary>
+ /// test iterator returns NO_MORE_DOCS </summary>
+ [Test]
+ public virtual void TestEmpty2()
+ {
+ BooleanQuery query = new BooleanQuery();
+ query.Add(new TermQuery(new Term("id", "0")), Occur.MUST);
+ query.Add(new TermQuery(new Term("id", "0")), Occur.MUST_NOT);
+ Filter expected = new QueryWrapperFilter(query);
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+
+ /// <summary>
+ /// test null docidset </summary>
+ [Test]
+ public virtual void TestEmpty3()
+ {
+ Filter expected = new PrefixFilter(new Term("bogusField", "bogusVal"));
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+
+ /// <summary>
+ /// test iterator returns single document </summary>
+ [Test]
+ public virtual void TestSingle()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ int id = Random().Next(Ir.MaxDoc);
+ Query query = new TermQuery(new Term("id", Convert.ToString(id)));
+ Filter expected = new QueryWrapperFilter(query);
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+ }
+
+ /// <summary>
+ /// test sparse filters (match single documents) </summary>
+ [Test]
+ public virtual void TestSparse()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ int id_start = Random().Next(Ir.MaxDoc - 1);
+ int id_end = id_start + 1;
+ Query query = TermRangeQuery.NewStringRange("id", Convert.ToString(id_start), Convert.ToString(id_end), true, true);
+ Filter expected = new QueryWrapperFilter(query);
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+ }
+
+ /// <summary>
+ /// test dense filters (match entire index) </summary>
+ [Test]
+ public virtual void TestDense()
+ {
+ Query query = new MatchAllDocsQuery();
+ Filter expected = new QueryWrapperFilter(query);
+ Filter actual = new CachingWrapperFilter(expected);
+ AssertFilterEquals(expected, actual);
+ }
+
+ [Test]
+ public virtual void TestCachingWorks()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone);
+ writer.Dispose();
+
+ IndexReader reader = SlowCompositeReaderWrapper.Wrap(DirectoryReader.Open(dir));
+ AtomicReaderContext context = (AtomicReaderContext)reader.Context;
+ MockFilter filter = new MockFilter();
+ CachingWrapperFilter cacher = new CachingWrapperFilter(filter);
+
+ // first time, nested filter is called
+ DocIdSet strongRef = cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs);
+ Assert.IsTrue(filter.WasCalled(), "first time");
+
+ // make sure no exception if cache is holding the wrong docIdSet
+ cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs);
+
+ // second time, nested filter should not be called
+ filter.Clear();
+ cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs);
+ Assert.IsFalse(filter.WasCalled(), "second time");
+
+ reader.Dispose();
+ dir.Dispose();
+ }
+
+ [Test]
+ public virtual void TestNullDocIdSet()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone);
+ writer.Dispose();
+
+ IndexReader reader = SlowCompositeReaderWrapper.Wrap(DirectoryReader.Open(dir));
+ AtomicReaderContext context = (AtomicReaderContext)reader.Context;
+
+ Filter filter = new FilterAnonymousInnerClassHelper(this, context);
+ CachingWrapperFilter cacher = new CachingWrapperFilter(filter);
+
+ // the caching filter should return the empty set constant
+ //Assert.IsNull(cacher.GetDocIdSet(context, "second time", (context.AtomicReader).LiveDocs));
+ Assert.IsNull(cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs));
+
+ reader.Dispose();
+ dir.Dispose();
+ }
+
+ private class FilterAnonymousInnerClassHelper : Filter
+ {
+ private readonly TestCachingWrapperFilter OuterInstance;
+
+ private AtomicReaderContext Context;
+
+ public FilterAnonymousInnerClassHelper(TestCachingWrapperFilter outerInstance, AtomicReaderContext context)
+ {
+ this.OuterInstance = outerInstance;
+ this.Context = context;
+ }
+
+ public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+ {
+ return null;
+ }
+ }
+
+ [Test]
+ public virtual void TestNullDocIdSetIterator()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone);
+ writer.Dispose();
+
+ IndexReader reader = SlowCompositeReaderWrapper.Wrap(DirectoryReader.Open(dir));
+ AtomicReaderContext context = (AtomicReaderContext)reader.Context;
+
+ Filter filter = new FilterAnonymousInnerClassHelper2(this, context);
+ CachingWrapperFilter cacher = new CachingWrapperFilter(filter);
+
+ // the caching filter should return the empty set constant
+ Assert.IsNull(cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs));
+
+ reader.Dispose();
+ dir.Dispose();
+ }
+
+ private class FilterAnonymousInnerClassHelper2 : Filter
+ {
+ private readonly TestCachingWrapperFilter OuterInstance;
+
+ private AtomicReaderContext Context;
+
+ public FilterAnonymousInnerClassHelper2(TestCachingWrapperFilter outerInstance, AtomicReaderContext context)
+ {
+ this.OuterInstance = outerInstance;
+ this.Context = context;
+ }
+
+ public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+ {
+ return new DocIdSetAnonymousInnerClassHelper(this);
+ }
+
+ private class DocIdSetAnonymousInnerClassHelper : DocIdSet
+ {
+ private readonly FilterAnonymousInnerClassHelper2 OuterInstance;
+
+ public DocIdSetAnonymousInnerClassHelper(FilterAnonymousInnerClassHelper2 outerInstance)
+ {
+ this.OuterInstance = outerInstance;
+ }
+
+ public override DocIdSetIterator GetIterator()
+ {
+ return null;
+ }
+ }
+ }
+
+ private static void AssertDocIdSetCacheable(IndexReader reader, Filter filter, bool shouldCacheable)
+ {
+ Assert.IsTrue(reader.Context is AtomicReaderContext);
+ AtomicReaderContext context = (AtomicReaderContext)reader.Context;
+ CachingWrapperFilter cacher = new CachingWrapperFilter(filter);
+ DocIdSet originalSet = filter.GetDocIdSet(context, (context.AtomicReader).LiveDocs);
+ DocIdSet cachedSet = cacher.GetDocIdSet(context, (context.AtomicReader).LiveDocs);
+ if (originalSet == null)
+ {
+ Assert.IsNull(cachedSet);
+ }
+ if (cachedSet == null)
+ {
+ Assert.IsTrue(originalSet == null || originalSet.GetIterator() == null);
+ }
+ else
+ {
+ Assert.IsTrue(cachedSet.IsCacheable);
+ Assert.AreEqual(shouldCacheable, originalSet.IsCacheable);
+ //System.out.println("Original: "+originalSet.getClass().getName()+" -- cached: "+cachedSet.getClass().getName());
+ if (originalSet.IsCacheable)
+ {
+ Assert.AreEqual(originalSet.GetType(), cachedSet.GetType(), "Cached DocIdSet must be of same class like uncached, if cacheable");
+ }
+ else
+ {
+ Assert.IsTrue(cachedSet is FixedBitSet || cachedSet == null, "Cached DocIdSet must be an FixedBitSet if the original one was not cacheable");
+ }
+ }
+ }
+
+ [Test]
+ public virtual void TestIsCacheAble()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone);
+ writer.AddDocument(new Document());
+ writer.Dispose();
+
+ IndexReader reader = SlowCompositeReaderWrapper.Wrap(DirectoryReader.Open(dir));
+
+ // not cacheable:
+ AssertDocIdSetCacheable(reader, new QueryWrapperFilter(new TermQuery(new Term("test", "value"))), false);
+ // returns default empty docidset, always cacheable:
+ AssertDocIdSetCacheable(reader, NumericRangeFilter.NewInt32Range("test", Convert.ToInt32(10000), Convert.ToInt32(-10000), true, true), true);
+ // is cacheable:
+ AssertDocIdSetCacheable(reader, FieldCacheRangeFilter.NewInt32Range("test", Convert.ToInt32(10), Convert.ToInt32(20), true, true), true);
+ // a fixedbitset filter is always cacheable
+ AssertDocIdSetCacheable(reader, new FilterAnonymousInnerClassHelper3(this), true);
+
+ reader.Dispose();
+ dir.Dispose();
+ }
+
+ private class FilterAnonymousInnerClassHelper3 : Filter
+ {
+ private readonly TestCachingWrapperFilter OuterInstance;
+
+ public FilterAnonymousInnerClassHelper3(TestCachingWrapperFilter outerInstance)
+ {
+ this.OuterInstance = outerInstance;
+ }
+
+ public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
+ {
+ return new FixedBitSet(context.Reader.MaxDoc);
+ }
+ }
+
+ [Test]
+ public virtual void TestEnforceDeletions()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetMergeScheduler(new SerialMergeScheduler()).SetMergePolicy(NewLogMergePolicy(10)));
+ // asserts below requires no unexpected merges:
+
+ // NOTE: cannot use writer.getReader because RIW (on
+ // flipping a coin) may give us a newly opened reader,
+ // but we use .reopen on this reader below and expect to
+ // (must) get an NRT reader:
+ DirectoryReader reader = DirectoryReader.Open(writer.w, true);
+ // same reason we don't wrap?
+ IndexSearcher searcher = NewSearcher(reader, false, Similarity);
+
+ // add a doc, refresh the reader, and check that it's there
+ Document doc = new Document();
+ doc.Add(NewStringField("id", "1", Field.Store.YES));
+ writer.AddDocument(doc);
+
+ reader = RefreshReader(reader);
+ searcher = NewSearcher(reader, false, Similarity);
+
+ TopDocs docs = searcher.Search(new MatchAllDocsQuery(), 1);
+ Assert.AreEqual(1, docs.TotalHits, "Should find a hit...");
+
+ Filter startFilter = new QueryWrapperFilter(new TermQuery(new Term("id", "1")));
+
+ CachingWrapperFilter filter = new CachingWrapperFilter(startFilter);
+
+ docs = searcher.Search(new MatchAllDocsQuery(), filter, 1);
+ Assert.IsTrue(filter.SizeInBytes() > 0);
+
+ Assert.AreEqual(1, docs.TotalHits, "[query + filter] Should find a hit...");
+
+ Query constantScore = new ConstantScoreQuery(filter);
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(1, docs.TotalHits, "[just filter] Should find a hit...");
+
+ // make sure we get a cache hit when we reopen reader
+ // that had no change to deletions
+
+ // fake delete (deletes nothing):
+ writer.DeleteDocuments(new Term("foo", "bar"));
+
+ IndexReader oldReader = reader;
+ reader = RefreshReader(reader);
+ Assert.IsTrue(reader == oldReader);
+ int missCount = filter.missCount;
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(1, docs.TotalHits, "[just filter] Should find a hit...");
+
+ // cache hit:
+ Assert.AreEqual(missCount, filter.missCount);
+
+ // now delete the doc, refresh the reader, and see that it's not there
+ writer.DeleteDocuments(new Term("id", "1"));
+
+ // NOTE: important to hold ref here so GC doesn't clear
+ // the cache entry! Else the assert below may sometimes
+ // fail:
+ oldReader = reader;
+ reader = RefreshReader(reader);
+
+ searcher = NewSearcher(reader, false, Similarity);
+
+ missCount = filter.missCount;
+ docs = searcher.Search(new MatchAllDocsQuery(), filter, 1);
+ Assert.AreEqual(0, docs.TotalHits, "[query + filter] Should *not* find a hit...");
+
+ // cache hit
+ Assert.AreEqual(missCount, filter.missCount);
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(0, docs.TotalHits, "[just filter] Should *not* find a hit...");
+
+ // apply deletes dynamically:
+ filter = new CachingWrapperFilter(startFilter);
+ writer.AddDocument(doc);
+ reader = RefreshReader(reader);
+ searcher = NewSearcher(reader, false, Similarity);
+
+ docs = searcher.Search(new MatchAllDocsQuery(), filter, 1);
+ Assert.AreEqual(1, docs.TotalHits, "[query + filter] Should find a hit...");
+ missCount = filter.missCount;
+ Assert.IsTrue(missCount > 0);
+ constantScore = new ConstantScoreQuery(filter);
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(1, docs.TotalHits, "[just filter] Should find a hit...");
+ Assert.AreEqual(missCount, filter.missCount);
+
+ writer.AddDocument(doc);
+
+ // NOTE: important to hold ref here so GC doesn't clear
+ // the cache entry! Else the assert below may sometimes
+ // fail:
+ oldReader = reader;
+
+ reader = RefreshReader(reader);
+ searcher = NewSearcher(reader, false, Similarity);
+
+ docs = searcher.Search(new MatchAllDocsQuery(), filter, 1);
+ Assert.AreEqual(2, docs.TotalHits, "[query + filter] Should find 2 hits...");
+ Assert.IsTrue(filter.missCount > missCount);
+ missCount = filter.missCount;
+
+ constantScore = new ConstantScoreQuery(filter);
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(2, docs.TotalHits, "[just filter] Should find a hit...");
+ Assert.AreEqual(missCount, filter.missCount);
+
+ // now delete the doc, refresh the reader, and see that it's not there
+ writer.DeleteDocuments(new Term("id", "1"));
+
+ reader = RefreshReader(reader);
+ searcher = NewSearcher(reader, false, Similarity);
+
+ docs = searcher.Search(new MatchAllDocsQuery(), filter, 1);
+ Assert.AreEqual(0, docs.TotalHits, "[query + filter] Should *not* find a hit...");
+ // CWF reused the same entry (it dynamically applied the deletes):
+ Assert.AreEqual(missCount, filter.missCount);
+
+ docs = searcher.Search(constantScore, 1);
+ Assert.AreEqual(0, docs.TotalHits, "[just filter] Should *not* find a hit...");
+ // CWF reused the same entry (it dynamically applied the deletes):
+ Assert.AreEqual(missCount, filter.missCount);
+
+ // NOTE: silliness to make sure JRE does not eliminate
+ // our holding onto oldReader to prevent
+ // CachingWrapperFilter's WeakHashMap from dropping the
+ // entry:
+ Assert.IsTrue(oldReader != null);
+
+ reader.Dispose();
+ writer.Dispose();
+ dir.Dispose();
+ }
+
+ private static DirectoryReader RefreshReader(DirectoryReader reader)
+ {
+ DirectoryReader oldReader = reader;
+ reader = DirectoryReader.OpenIfChanged(reader);
+ if (reader != null)
+ {
+ oldReader.Dispose();
+ return reader;
+ }
+ else
+ {
+ return oldReader;
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestComplexExplanations.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestComplexExplanations.cs b/src/Lucene.Net.Tests/Search/TestComplexExplanations.cs
new file mode 100644
index 0000000..4408fa9
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestComplexExplanations.cs
@@ -0,0 +1,389 @@
+using NUnit.Framework;
+
+namespace Lucene.Net.Search
+{
+ using Lucene.Net.Search.Spans;
+ using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity;
+
+ /*
+ * 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.
+ */
+
+ using Term = Lucene.Net.Index.Term;
+
+ /// <summary>
+ /// TestExplanations subclass that builds up super crazy complex queries
+ /// on the assumption that if the explanations work out right for them,
+ /// they should work for anything.
+ /// </summary>
+ [TestFixture]
+ public class TestComplexExplanations : TestExplanations
+ {
+ /// <summary>
+ /// Override the Similarity used in our searcher with one that plays
+ /// nice with boosts of 0.0
+ /// </summary>
+ [SetUp]
+ public override void SetUp()
+ {
+ base.SetUp();
+ Searcher.Similarity = CreateQnorm1Similarity();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ Searcher.Similarity = IndexSearcher.DefaultSimilarity;
+ base.TearDown();
+ }
+
+ // must be static for weight serialization tests
+ private static DefaultSimilarity CreateQnorm1Similarity()
+ {
+ return new DefaultSimilarityAnonymousInnerClassHelper();
+ }
+
+ private class DefaultSimilarityAnonymousInnerClassHelper : DefaultSimilarity
+ {
+ public DefaultSimilarityAnonymousInnerClassHelper()
+ {
+ }
+
+ public override float QueryNorm(float sumOfSquaredWeights)
+ {
+ return 1.0f; // / (float) Math.sqrt(1.0f + sumOfSquaredWeights);
+ }
+ }
+
+ [Test]
+ public virtual void Test1()
+ {
+ BooleanQuery q = new BooleanQuery();
+
+ PhraseQuery phraseQuery = new PhraseQuery();
+ phraseQuery.Slop = 1;
+ phraseQuery.Add(new Term(FIELD, "w1"));
+ phraseQuery.Add(new Term(FIELD, "w2"));
+ q.Add(phraseQuery, Occur.MUST);
+ q.Add(Snear(St("w2"), Sor("w5", "zz"), 4, true), Occur.SHOULD);
+ q.Add(Snear(Sf("w3", 2), St("w2"), St("w3"), 5, true), Occur.SHOULD);
+
+ Query t = new FilteredQuery(new TermQuery(new Term(FIELD, "xx")), new ItemizedFilter(new int[] { 1, 3 }));
+ t.Boost = 1000;
+ q.Add(t, Occur.SHOULD);
+
+ t = new ConstantScoreQuery(new ItemizedFilter(new int[] { 0, 2 }));
+ t.Boost = 30;
+ q.Add(t, Occur.SHOULD);
+
+ DisjunctionMaxQuery dm = new DisjunctionMaxQuery(0.2f);
+ dm.Add(Snear(St("w2"), Sor("w5", "zz"), 4, true));
+ dm.Add(new TermQuery(new Term(FIELD, "QQ")));
+
+ BooleanQuery xxYYZZ = new BooleanQuery();
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "xx")), Occur.SHOULD);
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "yy")), Occur.SHOULD);
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "zz")), Occur.MUST_NOT);
+
+ dm.Add(xxYYZZ);
+
+ BooleanQuery xxW1 = new BooleanQuery();
+ xxW1.Add(new TermQuery(new Term(FIELD, "xx")), Occur.MUST_NOT);
+ xxW1.Add(new TermQuery(new Term(FIELD, "w1")), Occur.MUST_NOT);
+
+ dm.Add(xxW1);
+
+ DisjunctionMaxQuery dm2 = new DisjunctionMaxQuery(0.5f);
+ dm2.Add(new TermQuery(new Term(FIELD, "w1")));
+ dm2.Add(new TermQuery(new Term(FIELD, "w2")));
+ dm2.Add(new TermQuery(new Term(FIELD, "w3")));
+ dm.Add(dm2);
+
+ q.Add(dm, Occur.SHOULD);
+
+ BooleanQuery b = new BooleanQuery();
+ b.MinimumNumberShouldMatch = 2;
+ b.Add(Snear("w1", "w2", 1, true), Occur.SHOULD);
+ b.Add(Snear("w2", "w3", 1, true), Occur.SHOULD);
+ b.Add(Snear("w1", "w3", 3, true), Occur.SHOULD);
+
+ q.Add(b, Occur.SHOULD);
+
+ Qtest(q, new int[] { 0, 1, 2 });
+ }
+
+ [Test]
+ public virtual void Test2()
+ {
+ BooleanQuery q = new BooleanQuery();
+
+ PhraseQuery phraseQuery = new PhraseQuery();
+ phraseQuery.Slop = 1;
+ phraseQuery.Add(new Term(FIELD, "w1"));
+ phraseQuery.Add(new Term(FIELD, "w2"));
+ q.Add(phraseQuery, Occur.MUST);
+ q.Add(Snear(St("w2"), Sor("w5", "zz"), 4, true), Occur.SHOULD);
+ q.Add(Snear(Sf("w3", 2), St("w2"), St("w3"), 5, true), Occur.SHOULD);
+
+ Query t = new FilteredQuery(new TermQuery(new Term(FIELD, "xx")), new ItemizedFilter(new int[] { 1, 3 }));
+ t.Boost = 1000;
+ q.Add(t, Occur.SHOULD);
+
+ t = new ConstantScoreQuery(new ItemizedFilter(new int[] { 0, 2 }));
+ t.Boost = -20.0f;
+ q.Add(t, Occur.SHOULD);
+
+ DisjunctionMaxQuery dm = new DisjunctionMaxQuery(0.2f);
+ dm.Add(Snear(St("w2"), Sor("w5", "zz"), 4, true));
+ dm.Add(new TermQuery(new Term(FIELD, "QQ")));
+
+ BooleanQuery xxYYZZ = new BooleanQuery();
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "xx")), Occur.SHOULD);
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "yy")), Occur.SHOULD);
+ xxYYZZ.Add(new TermQuery(new Term(FIELD, "zz")), Occur.MUST_NOT);
+
+ dm.Add(xxYYZZ);
+
+ BooleanQuery xxW1 = new BooleanQuery();
+ xxW1.Add(new TermQuery(new Term(FIELD, "xx")), Occur.MUST_NOT);
+ xxW1.Add(new TermQuery(new Term(FIELD, "w1")), Occur.MUST_NOT);
+
+ dm.Add(xxW1);
+
+ DisjunctionMaxQuery dm2 = new DisjunctionMaxQuery(0.5f);
+ dm2.Add(new TermQuery(new Term(FIELD, "w1")));
+ dm2.Add(new TermQuery(new Term(FIELD, "w2")));
+ dm2.Add(new TermQuery(new Term(FIELD, "w3")));
+ dm.Add(dm2);
+
+ q.Add(dm, Occur.SHOULD);
+
+ BooleanQuery b = new BooleanQuery();
+ b.MinimumNumberShouldMatch = 2;
+ b.Add(Snear("w1", "w2", 1, true), Occur.SHOULD);
+ b.Add(Snear("w2", "w3", 1, true), Occur.SHOULD);
+ b.Add(Snear("w1", "w3", 3, true), Occur.SHOULD);
+ b.Boost = 0.0f;
+
+ q.Add(b, Occur.SHOULD);
+
+ Qtest(q, new int[] { 0, 1, 2 });
+ }
+
+ // :TODO: we really need more crazy complex cases.
+
+ // //////////////////////////////////////////////////////////////////
+
+ // The rest of these aren't that complex, but they are <i>somewhat</i>
+ // complex, and they expose weakness in dealing with queries that match
+ // with scores of 0 wrapped in other queries
+
+ [Test]
+ public virtual void TestT3()
+ {
+ TermQuery query = new TermQuery(new Term(FIELD, "w1"));
+ query.Boost = 0;
+ Bqtest(query, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestMA3()
+ {
+ Query q = new MatchAllDocsQuery();
+ q.Boost = 0;
+ Bqtest(q, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestFQ5()
+ {
+ TermQuery query = new TermQuery(new Term(FIELD, "xx"));
+ query.Boost = 0;
+ Bqtest(new FilteredQuery(query, new ItemizedFilter(new int[] { 1, 3 })), new int[] { 3 });
+ }
+
+ [Test]
+ public virtual void TestCSQ4()
+ {
+ Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] { 3 }));
+ q.Boost = 0;
+ Bqtest(q, new int[] { 3 });
+ }
+
+ [Test]
+ public virtual void TestDMQ10()
+ {
+ DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.5f);
+
+ BooleanQuery query = new BooleanQuery();
+ query.Add(new TermQuery(new Term(FIELD, "yy")), Occur.SHOULD);
+ TermQuery boostedQuery = new TermQuery(new Term(FIELD, "w5"));
+ boostedQuery.Boost = 100;
+ query.Add(boostedQuery, Occur.SHOULD);
+
+ q.Add(query);
+
+ TermQuery xxBoostedQuery = new TermQuery(new Term(FIELD, "xx"));
+ xxBoostedQuery.Boost = 0;
+
+ q.Add(xxBoostedQuery);
+ q.Boost = 0.0f;
+ Bqtest(q, new int[] { 0, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestMPQ7()
+ {
+ MultiPhraseQuery q = new MultiPhraseQuery();
+ q.Add(Ta(new string[] { "w1" }));
+ q.Add(Ta(new string[] { "w2" }));
+ q.Slop = 1;
+ q.Boost = 0.0f;
+ Bqtest(q, new int[] { 0, 1, 2 });
+ }
+
+ [Test]
+ public virtual void TestBQ12()
+ {
+ // NOTE: using qtest not bqtest
+ BooleanQuery query = new BooleanQuery();
+ query.Add(new TermQuery(new Term(FIELD, "w1")), Occur.SHOULD);
+ TermQuery boostedQuery = new TermQuery(new Term(FIELD, "w2"));
+ boostedQuery.Boost = 0;
+ query.Add(boostedQuery, Occur.SHOULD);
+
+ Qtest(query, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestBQ13()
+ {
+ // NOTE: using qtest not bqtest
+ BooleanQuery query = new BooleanQuery();
+ query.Add(new TermQuery(new Term(FIELD, "w1")), Occur.SHOULD);
+ TermQuery boostedQuery = new TermQuery(new Term(FIELD, "w5"));
+ boostedQuery.Boost = 0;
+ query.Add(boostedQuery, Occur.MUST_NOT);
+
+ Qtest(query, new int[] { 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestBQ18()
+ {
+ // NOTE: using qtest not bqtest
+ BooleanQuery query = new BooleanQuery();
+ TermQuery boostedQuery = new TermQuery(new Term(FIELD, "w1"));
+ boostedQuery.Boost = 0;
+ query.Add(boostedQuery, Occur.MUST);
+ query.Add(new TermQuery(new Term(FIELD, "w2")), Occur.SHOULD);
+
+ Qtest(query, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestBQ21()
+ {
+ BooleanQuery query = new BooleanQuery();
+ query.Add(new TermQuery(new Term(FIELD, "w1")), Occur.MUST);
+ query.Add(new TermQuery(new Term(FIELD, "w2")), Occur.SHOULD);
+ query.Boost = 0;
+
+ Bqtest(query, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestBQ22()
+ {
+ BooleanQuery query = new BooleanQuery();
+ TermQuery boostedQuery = new TermQuery(new Term(FIELD, "w1"));
+ boostedQuery.Boost = 0;
+ query.Add(boostedQuery, Occur.MUST);
+ query.Add(new TermQuery(new Term(FIELD, "w2")), Occur.SHOULD);
+ query.Boost = 0;
+
+ Bqtest(query, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestST3()
+ {
+ SpanQuery q = St("w1");
+ q.Boost = 0;
+ Bqtest(q, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestST6()
+ {
+ SpanQuery q = St("xx");
+ q.Boost = 0;
+ Qtest(q, new int[] { 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestSF3()
+ {
+ SpanQuery q = Sf(("w1"), 1);
+ q.Boost = 0;
+ Bqtest(q, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestSF7()
+ {
+ SpanQuery q = Sf(("xx"), 3);
+ q.Boost = 0;
+ Bqtest(q, new int[] { 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestSNot3()
+ {
+ SpanQuery q = Snot(Sf("w1", 10), St("QQ"));
+ q.Boost = 0;
+ Bqtest(q, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestSNot6()
+ {
+ SpanQuery q = Snot(Sf("w1", 10), St("xx"));
+ q.Boost = 0;
+ Bqtest(q, new int[] { 0, 1, 2, 3 });
+ }
+
+ [Test]
+ public virtual void TestSNot8()
+ {
+ // NOTE: using qtest not bqtest
+ SpanQuery f = Snear("w1", "w3", 10, true);
+ f.Boost = 0;
+ SpanQuery q = Snot(f, St("xx"));
+ Qtest(q, new int[] { 0, 1, 3 });
+ }
+
+ [Test]
+ public virtual void TestSNot9()
+ {
+ // NOTE: using qtest not bqtest
+ SpanQuery t = St("xx");
+ t.Boost = 0;
+ SpanQuery q = Snot(Snear("w1", "w3", 10, true), t);
+ Qtest(q, new int[] { 0, 1, 3 });
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestComplexExplanationsOfNonMatches.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestComplexExplanationsOfNonMatches.cs b/src/Lucene.Net.Tests/Search/TestComplexExplanationsOfNonMatches.cs
new file mode 100644
index 0000000..18a2759
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestComplexExplanationsOfNonMatches.cs
@@ -0,0 +1,197 @@
+using NUnit.Framework;
+
+namespace Lucene.Net.Search
+{
+ /*
+ * 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.
+ */
+
+ /// <summary>
+ /// subclass of TestSimpleExplanations that verifies non matches.
+ /// </summary>
+ [TestFixture]
+ public class TestComplexExplanationsOfNonMatches : TestComplexExplanations
+ {
+ /// <summary>
+ /// Overrides superclass to ignore matches and focus on non-matches
+ /// </summary>
+ /// <seealso cref= CheckHits#checkNoMatchExplanations </seealso>
+ public override void Qtest(Query q, int[] expDocNrs)
+ {
+ CheckHits.CheckNoMatchExplanations(q, FIELD, Searcher, expDocNrs);
+ }
+
+
+ #region TestComplexExplanations
+ // LUCENENET NOTE: Tests in a base class are not pulled into the correct
+ // context in Visual Studio. This fixes that with the minimum amount of code necessary
+ // to run them in the correct context without duplicating all of the tests.
+
+ [Test]
+ public override void Test1()
+ {
+ base.Test1();
+ }
+
+ [Test]
+ public override void Test2()
+ {
+ base.Test2();
+ }
+
+ // :TODO: we really need more crazy complex cases.
+
+ // //////////////////////////////////////////////////////////////////
+
+ // The rest of these aren't that complex, but they are <i>somewhat</i>
+ // complex, and they expose weakness in dealing with queries that match
+ // with scores of 0 wrapped in other queries
+
+ [Test]
+ public override void TestT3()
+ {
+ base.TestT3();
+ }
+
+ [Test]
+ public override void TestMA3()
+ {
+ base.TestMA3();
+ }
+
+ [Test]
+ public override void TestFQ5()
+ {
+ base.TestFQ5();
+ }
+
+ [Test]
+ public override void TestCSQ4()
+ {
+ base.TestCSQ4();
+ }
+
+ [Test]
+ public override void TestDMQ10()
+ {
+ base.TestDMQ10();
+ }
+
+ [Test]
+ public override void TestMPQ7()
+ {
+ base.TestMPQ7();
+ }
+
+ [Test]
+ public override void TestBQ12()
+ {
+ base.TestBQ12();
+ }
+
+ [Test]
+ public override void TestBQ13()
+ {
+ base.TestBQ13();
+ }
+
+ [Test]
+ public override void TestBQ18()
+ {
+ base.TestBQ18();
+ }
+
+ [Test]
+ public override void TestBQ21()
+ {
+ base.TestBQ21();
+ }
+
+ [Test]
+ public override void TestBQ22()
+ {
+ base.TestBQ22();
+ }
+
+ [Test]
+ public override void TestST3()
+ {
+ base.TestST3();
+ }
+
+ [Test]
+ public override void TestST6()
+ {
+ base.TestST6();
+ }
+
+ [Test]
+ public override void TestSF3()
+ {
+ base.TestSF3();
+ }
+
+ [Test]
+ public override void TestSF7()
+ {
+ base.TestSF7();
+ }
+
+ [Test]
+ public override void TestSNot3()
+ {
+ base.TestSNot3();
+ }
+
+ [Test]
+ public override void TestSNot6()
+ {
+ base.TestSNot6();
+ }
+
+ [Test]
+ public override void TestSNot8()
+ {
+ base.TestSNot8();
+ }
+
+ [Test]
+ public override void TestSNot9()
+ {
+ base.TestSNot9();
+ }
+
+ #endregion
+
+ #region TestExplanations
+ // LUCENENET NOTE: Tests in an abstract base class are not pulled into the correct
+ // context in Visual Studio. This fixes that with the minimum amount of code necessary
+ // to run them in the correct context without duplicating all of the tests.
+
+
+ /// <summary>
+ /// Placeholder: JUnit freaks if you don't have one test ... making
+ /// class abstract doesn't help
+ /// </summary>
+ [Test]
+ public override void TestNoop()
+ {
+ base.TestNoop();
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestConjunctions.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestConjunctions.cs b/src/Lucene.Net.Tests/Search/TestConjunctions.cs
new file mode 100644
index 0000000..2f5cdc2
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestConjunctions.cs
@@ -0,0 +1,161 @@
+using Lucene.Net.Documents;
+
+namespace Lucene.Net.Search
+{
+ using NUnit.Framework;
+
+ /*
+ * 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.
+ */
+
+ using Analyzer = Lucene.Net.Analysis.Analyzer;
+ using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+ using BytesRef = Lucene.Net.Util.BytesRef;
+ using Directory = Lucene.Net.Store.Directory;
+ using Document = Documents.Document;
+ using FieldInvertState = Lucene.Net.Index.FieldInvertState;
+ using IndexReader = Lucene.Net.Index.IndexReader;
+ using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+ using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
+ using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+ using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+ using Similarity = Lucene.Net.Search.Similarities.Similarity;
+ using Store = Field.Store;
+ using StringField = StringField;
+ using Term = Lucene.Net.Index.Term;
+ using TextField = TextField;
+
+ [TestFixture]
+ public class TestConjunctions : LuceneTestCase
+ {
+ internal Analyzer Analyzer;
+ internal Directory Dir;
+ internal IndexReader Reader;
+ internal IndexSearcher Searcher;
+
+ internal const string F1 = "title";
+ internal const string F2 = "body";
+
+ [SetUp]
+ public override void SetUp()
+ {
+ base.SetUp();
+ Analyzer = new MockAnalyzer(Random());
+ Dir = NewDirectory();
+ IndexWriterConfig config = NewIndexWriterConfig(TEST_VERSION_CURRENT, Analyzer);
+ config.SetMergePolicy(NewLogMergePolicy()); // we will use docids to validate
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), Dir, config);
+ writer.AddDocument(Doc("lucene", "lucene is a very popular search engine library"));
+ writer.AddDocument(Doc("solr", "solr is a very popular search server and is using lucene"));
+ writer.AddDocument(Doc("nutch", "nutch is an internet search engine with web crawler and is using lucene and hadoop"));
+ Reader = writer.Reader;
+ writer.Dispose();
+ Searcher = NewSearcher(Reader);
+ Searcher.Similarity = new TFSimilarity();
+ }
+
+ internal static Document Doc(string v1, string v2)
+ {
+ Document doc = new Document();
+ doc.Add(new StringField(F1, v1, Store.YES));
+ doc.Add(new TextField(F2, v2, Store.YES));
+ return doc;
+ }
+
+ [Test]
+ public virtual void TestTermConjunctionsWithOmitTF()
+ {
+ BooleanQuery bq = new BooleanQuery();
+ bq.Add(new TermQuery(new Term(F1, "nutch")), Occur.MUST);
+ bq.Add(new TermQuery(new Term(F2, "is")), Occur.MUST);
+ TopDocs td = Searcher.Search(bq, 3);
+ Assert.AreEqual(1, td.TotalHits);
+ Assert.AreEqual(3F, td.ScoreDocs[0].Score, 0.001F); // f1:nutch + f2:is + f2:is
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ Reader.Dispose();
+ Dir.Dispose();
+ base.TearDown();
+ }
+
+ // Similarity that returns the TF as score
+ private class TFSimilarity : Similarity
+ {
+ public override long ComputeNorm(FieldInvertState state)
+ {
+ return 1; // we dont care
+ }
+
+ public override SimWeight ComputeWeight(float queryBoost, CollectionStatistics collectionStats, params TermStatistics[] termStats)
+ {
+ return new SimWeightAnonymousInnerClassHelper(this);
+ }
+
+ private class SimWeightAnonymousInnerClassHelper : SimWeight
+ {
+ private readonly TFSimilarity OuterInstance;
+
+ public SimWeightAnonymousInnerClassHelper(TFSimilarity outerInstance)
+ {
+ this.OuterInstance = outerInstance;
+ }
+
+ public override float GetValueForNormalization()
+ {
+ return 1; // we don't care
+ }
+
+ public override void Normalize(float queryNorm, float topLevelBoost)
+ {
+ // we don't care
+ }
+ }
+
+ public override SimScorer GetSimScorer(SimWeight weight, AtomicReaderContext context)
+ {
+ return new SimScorerAnonymousInnerClassHelper(this);
+ }
+
+ private class SimScorerAnonymousInnerClassHelper : SimScorer
+ {
+ private readonly TFSimilarity OuterInstance;
+
+ public SimScorerAnonymousInnerClassHelper(TFSimilarity outerInstance)
+ {
+ this.OuterInstance = outerInstance;
+ }
+
+ public override float Score(int doc, float freq)
+ {
+ return freq;
+ }
+
+ public override float ComputeSlopFactor(int distance)
+ {
+ return 1F;
+ }
+
+ public override float ComputePayloadFactor(int doc, int start, int end, BytesRef payload)
+ {
+ return 1F;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestConstantScoreQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestConstantScoreQuery.cs b/src/Lucene.Net.Tests/Search/TestConstantScoreQuery.cs
new file mode 100644
index 0000000..e458c63
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestConstantScoreQuery.cs
@@ -0,0 +1,241 @@
+using System.Diagnostics;
+using Lucene.Net.Documents;
+
+namespace Lucene.Net.Search
+{
+ using NUnit.Framework;
+ using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+ using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity;
+ using Directory = Lucene.Net.Store.Directory;
+
+ /*
+ * 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.
+ */
+
+ using Document = Documents.Document;
+ using Field = Field;
+ using IndexReader = Lucene.Net.Index.IndexReader;
+ using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
+ using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+ using Term = Lucene.Net.Index.Term;
+
+ /// <summary>
+ /// this class only tests some basic functionality in CSQ, the main parts are mostly
+ /// tested by MultiTermQuery tests, explanations seems to be tested in TestExplanations!
+ /// </summary>
+ [TestFixture]
+ public class TestConstantScoreQuery : LuceneTestCase
+ {
+ [Test]
+ public virtual void TestCSQ()
+ {
+ Query q1 = new ConstantScoreQuery(new TermQuery(new Term("a", "b")));
+ Query q2 = new ConstantScoreQuery(new TermQuery(new Term("a", "c")));
+ Query q3 = new ConstantScoreQuery(TermRangeFilter.NewStringRange("a", "b", "c", true, true));
+ QueryUtils.Check(q1);
+ QueryUtils.Check(q2);
+ QueryUtils.CheckEqual(q1, q1);
+ QueryUtils.CheckEqual(q2, q2);
+ QueryUtils.CheckEqual(q3, q3);
+ QueryUtils.CheckUnequal(q1, q2);
+ QueryUtils.CheckUnequal(q2, q3);
+ QueryUtils.CheckUnequal(q1, q3);
+ QueryUtils.CheckUnequal(q1, new TermQuery(new Term("a", "b")));
+ }
+
+ private void CheckHits(IndexSearcher searcher, Query q, float expectedScore, string scorerClassName, string innerScorerClassName)
+ {
+ int[] count = new int[1];
+ searcher.Search(q, new CollectorAnonymousInnerClassHelper(this, expectedScore, scorerClassName, innerScorerClassName, count));
+ Assert.AreEqual(1, count[0], "invalid number of results");
+ }
+
+ private class CollectorAnonymousInnerClassHelper : ICollector
+ {
+ private readonly TestConstantScoreQuery OuterInstance;
+
+ private float ExpectedScore;
+ private string ScorerClassName;
+ private string InnerScorerClassName;
+ private int[] Count;
+
+ public CollectorAnonymousInnerClassHelper(TestConstantScoreQuery outerInstance, float expectedScore, string scorerClassName, string innerScorerClassName, int[] count)
+ {
+ this.OuterInstance = outerInstance;
+ this.ExpectedScore = expectedScore;
+ this.ScorerClassName = scorerClassName;
+ this.InnerScorerClassName = innerScorerClassName;
+ this.Count = count;
+ }
+
+ private Scorer scorer;
+
+ public virtual void SetScorer(Scorer scorer)
+ {
+ this.scorer = scorer;
+ Assert.AreEqual(ScorerClassName, scorer.GetType().Name, "Scorer is implemented by wrong class");
+ if (InnerScorerClassName != null && scorer is ConstantScoreQuery.ConstantScorer)
+ {
+ ConstantScoreQuery.ConstantScorer innerScorer = (ConstantScoreQuery.ConstantScorer)scorer;
+ Assert.AreEqual(InnerScorerClassName, innerScorer.docIdSetIterator.GetType().Name, "inner Scorer is implemented by wrong class");
+ }
+ }
+
+ public virtual void Collect(int doc)
+ {
+ Assert.AreEqual(ExpectedScore, this.scorer.GetScore(), 0, "Score differs from expected");
+ Count[0]++;
+ }
+
+ public virtual void SetNextReader(AtomicReaderContext context)
+ {
+ }
+
+ public virtual bool AcceptsDocsOutOfOrder
+ {
+ get { return true; }
+ }
+ }
+
+ [Test]
+ public virtual void TestWrapped2Times()
+ {
+ Directory directory = null;
+ IndexReader reader = null;
+ IndexSearcher searcher = null;
+ try
+ {
+ directory = NewDirectory();
+ RandomIndexWriter writer = new RandomIndexWriter(Random(), directory, Similarity, TimeZone);
+
+ Document doc = new Document();
+ doc.Add(NewStringField("field", "term", Field.Store.NO));
+ writer.AddDocument(doc);
+
+ reader = writer.Reader;
+ writer.Dispose();
+ // we don't wrap with AssertingIndexSearcher in order to have the original scorer in setScorer.
+ searcher = NewSearcher(reader, true, false);
+
+ // set a similarity that does not normalize our boost away
+ searcher.Similarity = new DefaultSimilarityAnonymousInnerClassHelper(this);
+
+ Query csq1 = new ConstantScoreQuery(new TermQuery(new Term("field", "term")));
+ csq1.Boost = 2.0f;
+ Query csq2 = new ConstantScoreQuery(csq1);
+ csq2.Boost = 5.0f;
+
+ BooleanQuery bq = new BooleanQuery();
+ bq.Add(csq1, Occur.SHOULD);
+ bq.Add(csq2, Occur.SHOULD);
+
+ Query csqbq = new ConstantScoreQuery(bq);
+ csqbq.Boost = 17.0f;
+
+ CheckHits(searcher, csq1, csq1.Boost, typeof(ConstantScoreQuery.ConstantScorer).Name, null);
+ CheckHits(searcher, csq2, csq2.Boost, typeof(ConstantScoreQuery.ConstantScorer).Name, typeof(ConstantScoreQuery.ConstantScorer).Name);
+
+ // for the combined BQ, the scorer should always be BooleanScorer's BucketScorer, because our scorer supports out-of order collection!
+ string bucketScorerClass = typeof(FakeScorer).Name;
+ CheckHits(searcher, bq, csq1.Boost + csq2.Boost, bucketScorerClass, null);
+ CheckHits(searcher, csqbq, csqbq.Boost, typeof(ConstantScoreQuery.ConstantScorer).Name, bucketScorerClass);
+ }
+ finally
+ {
+ if (reader != null)
+ {
+ reader.Dispose();
+ }
+ if (directory != null)
+ {
+ directory.Dispose();
+ }
+ }
+ }
+
+ private class DefaultSimilarityAnonymousInnerClassHelper : DefaultSimilarity
+ {
+ private readonly TestConstantScoreQuery OuterInstance;
+
+ public DefaultSimilarityAnonymousInnerClassHelper(TestConstantScoreQuery outerInstance)
+ {
+ this.OuterInstance = outerInstance;
+ }
+
+ public override float QueryNorm(float sumOfSquaredWeights)
+ {
+ return 1.0f;
+ }
+ }
+
+ [Test]
+ public virtual void TestConstantScoreQueryAndFilter()
+ {
+ Directory d = NewDirectory();
+ RandomIndexWriter w = new RandomIndexWriter(Random(), d, Similarity, TimeZone);
+ Document doc = new Document();
+ doc.Add(NewStringField("field", "a", Field.Store.NO));
+ w.AddDocument(doc);
+ doc = new Document();
+ doc.Add(NewStringField("field", "b", Field.Store.NO));
+ w.AddDocument(doc);
+ IndexReader r = w.Reader;
+ w.Dispose();
+
+ Filter filterB = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("field", "b"))));
+ Query query = new ConstantScoreQuery(filterB);
+
+ IndexSearcher s = NewSearcher(r);
+ Assert.AreEqual(1, s.Search(query, filterB, 1).TotalHits); // Query for field:b, Filter field:b
+
+ Filter filterA = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Term("field", "a"))));
+ query = new ConstantScoreQuery(filterA);
+
+ Assert.AreEqual(0, s.Search(query, filterB, 1).TotalHits); // Query field:b, Filter field:a
+
+ r.Dispose();
+ d.Dispose();
+ }
+
+ // LUCENE-5307
+ // don't reuse the scorer of filters since they have been created with bulkScorer=false
+ [Test]
+ public virtual void TestQueryWrapperFilter()
+ {
+ Directory d = NewDirectory();
+ RandomIndexWriter w = new RandomIndexWriter(Random(), d, Similarity, TimeZone);
+ Document doc = new Document();
+ doc.Add(NewStringField("field", "a", Field.Store.NO));
+ w.AddDocument(doc);
+ IndexReader r = w.Reader;
+ w.Dispose();
+
+ Filter filter = new QueryWrapperFilter(AssertingQuery.Wrap(Random(), new TermQuery(new Term("field", "a"))));
+ IndexSearcher s = NewSearcher(r);
+ Debug.Assert(s is AssertingIndexSearcher);
+ // this used to fail
+ s.Search(new ConstantScoreQuery(filter), new TotalHitCountCollector());
+
+ // check the rewrite
+ Query rewritten = (new ConstantScoreQuery(filter)).Rewrite(r);
+ Assert.IsTrue(rewritten is ConstantScoreQuery);
+ Assert.IsTrue(((ConstantScoreQuery)rewritten).Query is AssertingQuery);
+
+ r.Dispose();
+ d.Dispose();
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs b/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
new file mode 100644
index 0000000..d447b9e
--- /dev/null
+++ b/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
@@ -0,0 +1,731 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Search
+{
+ /*
+ * 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.
+ */
+
+ using Analyzer = Lucene.Net.Analysis.Analyzer;
+ using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+ using Document = Lucene.Net.Documents.Document;
+ using Field = Lucene.Net.Documents.Field;
+ using TextField = Lucene.Net.Documents.TextField;
+ using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+ using IndexCommit = Lucene.Net.Index.IndexCommit;
+ using IndexReader = Lucene.Net.Index.IndexReader;
+ using IndexWriter = Lucene.Net.Index.IndexWriter;
+ using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+ using IIndexableField = Lucene.Net.Index.IIndexableField;
+ using KeepOnlyLastCommitDeletionPolicy = Lucene.Net.Index.KeepOnlyLastCommitDeletionPolicy;
+ using NoMergePolicy = Lucene.Net.Index.NoMergePolicy;
+ using OpenMode = Lucene.Net.Index.OpenMode;
+ using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+ using SnapshotDeletionPolicy = Lucene.Net.Index.SnapshotDeletionPolicy;
+ using Term = Lucene.Net.Index.Term;
+ using ThreadedIndexingAndSearchingTestCase = Lucene.Net.Index.ThreadedIndexingAndSearchingTestCase;
+ using TrackingIndexWriter = Lucene.Net.Index.TrackingIndexWriter;
+ using Directory = Lucene.Net.Store.Directory;
+ using NRTCachingDirectory = Lucene.Net.Store.NRTCachingDirectory;
+ using IOUtils = Lucene.Net.Util.IOUtils;
+ using SuppressCodecs = Lucene.Net.Util.LuceneTestCase.SuppressCodecs;
+ using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
+ using TestUtil = Lucene.Net.Util.TestUtil;
+ //using ThreadInterruptedException = Lucene.Net.Util.ThreadInterruptedException;
+ using Version = Lucene.Net.Util.LuceneVersion;
+
+ [SuppressCodecs("SimpleText", "Memory", "Direct")]
+ [TestFixture]
+ public class TestControlledRealTimeReopenThread : ThreadedIndexingAndSearchingTestCase
+ {
+
+ // Not guaranteed to reflect deletes:
+ private SearcherManager NrtNoDeletes;
+
+ // Is guaranteed to reflect deletes:
+ private SearcherManager NrtDeletes;
+
+ private TrackingIndexWriter GenWriter;
+
+ private ControlledRealTimeReopenThread<IndexSearcher> NrtDeletesThread;
+ private ControlledRealTimeReopenThread<IndexSearcher> NrtNoDeletesThread;
+
+ private readonly ThreadLocal<long?> LastGens = new ThreadLocal<long?>();
+ private bool WarmCalled;
+
+ [Test]
+ public virtual void TestControlledRealTimeReopenThread_Mem()
+ {
+ RunTest("TestControlledRealTimeReopenThread");
+ }
+
+ protected internal override IndexSearcher FinalSearcher
+ {
+ get
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine("TEST: finalSearcher maxGen=" + MaxGen);
+ }
+ NrtDeletesThread.WaitForGeneration(MaxGen);
+ return NrtDeletes.Acquire();
+ }
+ }
+
+ protected internal override Directory GetDirectory(Directory @in)
+ {
+ // Randomly swap in NRTCachingDir
+ if (Random().NextBoolean())
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine("TEST: wrap NRTCachingDir");
+ }
+
+ return new NRTCachingDirectory(@in, 5.0, 60.0);
+ }
+ else
+ {
+ return @in;
+ }
+ }
+
+ protected internal override void UpdateDocuments(Term id, IEnumerable<IEnumerable<IIndexableField>> docs)
+ {
+ long gen = GenWriter.UpdateDocuments(id, docs);
+
+ // Randomly verify the update "took":
+ if (Random().Next(20) == 2)
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: verify " + id);
+ }
+ NrtDeletesThread.WaitForGeneration(gen);
+ IndexSearcher s = NrtDeletes.Acquire();
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: got searcher=" + s);
+ }
+ try
+ {
+ Assert.AreEqual(docs.Count(), s.Search(new TermQuery(id), 10).TotalHits);
+ }
+ finally
+ {
+ NrtDeletes.Release(s);
+ }
+ }
+
+ LastGens.Value = gen;
+
+ }
+
+ protected internal override void AddDocuments(Term id, IEnumerable<IEnumerable<IIndexableField>> docs)
+ {
+ long gen = GenWriter.AddDocuments(docs);
+ // Randomly verify the add "took":
+ if (Random().Next(20) == 2)
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: verify " + id);
+ }
+ NrtNoDeletesThread.WaitForGeneration(gen);
+ IndexSearcher s = NrtNoDeletes.Acquire();
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: got searcher=" + s);
+ }
+ try
+ {
+ Assert.AreEqual(docs.Count(), s.Search(new TermQuery(id), 10).TotalHits);
+ }
+ finally
+ {
+ NrtNoDeletes.Release(s);
+ }
+ }
+ LastGens.Value = gen;
+ }
+
+ protected internal override void AddDocument(Term id, IEnumerable<IIndexableField> doc)
+ {
+ long gen = GenWriter.AddDocument(doc);
+
+ // Randomly verify the add "took":
+ if (Random().Next(20) == 2)
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: verify " + id);
+ }
+ NrtNoDeletesThread.WaitForGeneration(gen);
+ IndexSearcher s = NrtNoDeletes.Acquire();
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: got searcher=" + s);
+ }
+ try
+ {
+ Assert.AreEqual(1, s.Search(new TermQuery(id), 10).TotalHits);
+ }
+ finally
+ {
+ NrtNoDeletes.Release(s);
+ }
+ }
+ LastGens.Value = gen;
+ }
+
+ protected internal override void UpdateDocument(Term id, IEnumerable<IIndexableField> doc)
+ {
+ long gen = GenWriter.UpdateDocument(id, doc);
+ // Randomly verify the udpate "took":
+ if (Random().Next(20) == 2)
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: verify " + id);
+ }
+ NrtDeletesThread.WaitForGeneration(gen);
+ IndexSearcher s = NrtDeletes.Acquire();
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: got searcher=" + s);
+ }
+ try
+ {
+ Assert.AreEqual(1, s.Search(new TermQuery(id), 10).TotalHits);
+ }
+ finally
+ {
+ NrtDeletes.Release(s);
+ }
+ }
+ LastGens.Value = gen;
+ }
+
+ protected internal override void DeleteDocuments(Term id)
+ {
+ long gen = GenWriter.DeleteDocuments(id);
+ // randomly verify the delete "took":
+ if (Random().Next(20) == 7)
+ {
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: verify del " + id);
+ }
+ NrtDeletesThread.WaitForGeneration(gen);
+ IndexSearcher s = NrtDeletes.Acquire();
+ if (VERBOSE)
+ {
+ Console.WriteLine(Thread.CurrentThread.Name + ": nrt: got searcher=" + s);
+ }
+ try
+ {
+ Assert.AreEqual(0, s.Search(new TermQuery(id), 10).TotalHits);
+ }
+ finally
+ {
+ NrtDeletes.Release(s);
+ }
+ }
+ LastGens.Value = gen;
+ }
+
+ protected internal override void DoAfterWriter(TaskScheduler es)
+ {
+ double minReopenSec = 0.01 + 0.05 * Random().NextDouble();
+ double maxReopenSec = minReopenSec * (1.0 + 10 * Random().NextDouble());
+
+ if (VERBOSE)
+ {
+ Console.WriteLine("TEST: make SearcherManager maxReopenSec=" + maxReopenSec + " minReopenSec=" + minReopenSec);
+ }
+
+ GenWriter = new TrackingIndexWriter(Writer);
+
+ SearcherFactory sf = new SearcherFactoryAnonymousInnerClassHelper(this, es);
+
+ NrtNoDeletes = new SearcherManager(Writer, false, sf);
+ NrtDeletes = new SearcherManager(Writer, true, sf);
+
+ NrtDeletesThread = new ControlledRealTimeReopenThread<IndexSearcher>(GenWriter, NrtDeletes, maxReopenSec, minReopenSec);
+ NrtDeletesThread.Name = "NRTDeletes Reopen Thread";
+ NrtDeletesThread.Priority = (ThreadPriority)Math.Min((int)Thread.CurrentThread.Priority + 2, (int)ThreadPriority.Highest);
+ NrtDeletesThread.SetDaemon(true);
+ NrtDeletesThread.Start();
+
+ NrtNoDeletesThread = new ControlledRealTimeReopenThread<IndexSearcher>(GenWriter, NrtNoDeletes, maxReopenSec, minReopenSec);
+ NrtNoDeletesThread.Name = "NRTNoDeletes Reopen Thread";
+ NrtNoDeletesThread.Priority = (ThreadPriority)Math.Min((int)Thread.CurrentThread.Priority + 2, (int)ThreadPriority.Highest);
+ NrtNoDeletesThread.SetDaemon(true);
+ NrtNoDeletesThread.Start();
+ }
+
+ private class SearcherFactoryAnonymousInnerClassHelper : SearcherFactory
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private TaskScheduler Es;
+
+ public SearcherFactoryAnonymousInnerClassHelper(TestControlledRealTimeReopenThread outerInstance, TaskScheduler es)
+ {
+ this.OuterInstance = outerInstance;
+ this.Es = es;
+ }
+
+ public override IndexSearcher NewSearcher(IndexReader r)
+ {
+ OuterInstance.WarmCalled = true;
+ IndexSearcher s = new IndexSearcher(r, Es);
+ s.Search(new TermQuery(new Term("body", "united")), 10);
+ return s;
+ }
+ }
+
+ protected internal override void DoAfterIndexingThreadDone()
+ {
+ long? gen = LastGens.Value;
+ if (gen != null)
+ {
+ AddMaxGen((long)gen);
+ }
+ }
+
+ private long MaxGen = -1;
+
+ private void AddMaxGen(long gen)
+ {
+ lock (this)
+ {
+ MaxGen = Math.Max(gen, MaxGen);
+ }
+ }
+
+ protected internal override void DoSearching(TaskScheduler es, DateTime stopTime)
+ {
+ RunSearchThreads(stopTime);
+ }
+
+ protected internal override IndexSearcher CurrentSearcher
+ {
+ get
+ {
+ // Test doesn't assert deletions until the end, so we
+ // can randomize whether dels must be applied
+ SearcherManager nrt;
+ if (Random().NextBoolean())
+ {
+ nrt = NrtDeletes;
+ }
+ else
+ {
+ nrt = NrtNoDeletes;
+ }
+
+ return nrt.Acquire();
+ }
+ }
+
+ protected internal override void ReleaseSearcher(IndexSearcher s)
+ {
+ // NOTE: a bit iffy... technically you should release
+ // against the same SearcherManager you acquired from... but
+ // both impls just decRef the underlying reader so we
+ // can get away w/ cheating:
+ NrtNoDeletes.Release(s);
+ }
+
+ protected internal override void DoClose()
+ {
+ Assert.IsTrue(WarmCalled);
+ if (VERBOSE)
+ {
+ Console.WriteLine("TEST: now close SearcherManagers");
+ }
+ NrtDeletesThread.Dispose();
+ NrtDeletes.Dispose();
+ NrtNoDeletesThread.Dispose();
+ NrtNoDeletes.Dispose();
+ }
+
+ /*
+ * LUCENE-3528 - NRTManager hangs in certain situations
+ */
+ [Test]
+ public virtual void TestThreadStarvationNoDeleteNRTReader()
+ {
+ IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+ conf.SetMergePolicy(Random().NextBoolean() ? NoMergePolicy.COMPOUND_FILES : NoMergePolicy.NO_COMPOUND_FILES);
+ Directory d = NewDirectory();
+ CountdownEvent latch = new CountdownEvent(1);
+ CountdownEvent signal = new CountdownEvent(1);
+
+ LatchedIndexWriter _writer = new LatchedIndexWriter(d, conf, latch, signal);
+ TrackingIndexWriter writer = new TrackingIndexWriter(_writer);
+ SearcherManager manager = new SearcherManager(_writer, false, null);
+ Document doc = new Document();
+ doc.Add(NewTextField("test", "test", Field.Store.YES));
+ writer.AddDocument(doc);
+ manager.MaybeRefresh();
+ ThreadClass t = new ThreadAnonymousInnerClassHelper(this, latch, signal, writer, manager);
+ t.Start();
+ _writer.WaitAfterUpdate = true; // wait in addDocument to let some reopens go through
+ long lastGen = writer.UpdateDocument(new Term("foo", "bar"), doc); // once this returns the doc is already reflected in the last reopen
+
+ Assert.IsFalse(manager.IsSearcherCurrent()); // false since there is a delete in the queue
+
+ IndexSearcher searcher = manager.Acquire();
+ try
+ {
+ Assert.AreEqual(2, searcher.IndexReader.NumDocs);
+ }
+ finally
+ {
+ manager.Release(searcher);
+ }
+ ControlledRealTimeReopenThread<IndexSearcher> thread = new ControlledRealTimeReopenThread<IndexSearcher>(writer, manager, 0.01, 0.01);
+ thread.Start(); // start reopening
+ if (VERBOSE)
+ {
+ Console.WriteLine("waiting now for generation " + lastGen);
+ }
+
+ AtomicBoolean finished = new AtomicBoolean(false);
+ ThreadClass waiter = new ThreadAnonymousInnerClassHelper2(this, lastGen, thread, finished);
+ waiter.Start();
+ manager.MaybeRefresh();
+ waiter.Join(1000);
+ if (!finished.Get())
+ {
+ waiter.Interrupt();
+ Assert.Fail("thread deadlocked on waitForGeneration");
+ }
+ thread.Dispose();
+ thread.Join();
+ IOUtils.Close(manager, _writer, d);
+ }
+
+ private class ThreadAnonymousInnerClassHelper : ThreadClass
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private CountdownEvent Latch;
+ private CountdownEvent Signal;
+ private TrackingIndexWriter Writer;
+ private SearcherManager Manager;
+
+ public ThreadAnonymousInnerClassHelper(TestControlledRealTimeReopenThread outerInstance, CountdownEvent latch, CountdownEvent signal, TrackingIndexWriter writer, SearcherManager manager)
+ {
+ this.OuterInstance = outerInstance;
+ this.Latch = latch;
+ this.Signal = signal;
+ this.Writer = writer;
+ this.Manager = manager;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+ Signal.Wait();
+ Manager.MaybeRefresh();
+ Writer.DeleteDocuments(new TermQuery(new Term("foo", "barista")));
+ Manager.MaybeRefresh(); // kick off another reopen so we inc. the internal gen
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.ToString());
+ Console.Write(e.StackTrace);
+ }
+ finally
+ {
+ Latch.Reset(Latch.CurrentCount == 0 ? 0 : Latch.CurrentCount - 1); // let the add below finish
+ }
+ }
+ }
+
+ private class ThreadAnonymousInnerClassHelper2 : ThreadClass
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private long LastGen;
+ private ControlledRealTimeReopenThread<IndexSearcher> thread;
+ private AtomicBoolean Finished;
+
+ public ThreadAnonymousInnerClassHelper2(TestControlledRealTimeReopenThread outerInstance, long lastGen, ControlledRealTimeReopenThread<IndexSearcher> thread, AtomicBoolean finished)
+ {
+ this.OuterInstance = outerInstance;
+ this.LastGen = lastGen;
+ this.thread = thread;
+ this.Finished = finished;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+ thread.WaitForGeneration(LastGen);
+ }
+ catch (ThreadInterruptedException ie)
+ {
+ Thread.CurrentThread.Interrupt();
+ throw new Exception(ie.Message, ie);
+ }
+ Finished.Set(true);
+ }
+ }
+
+ public class LatchedIndexWriter : IndexWriter
+ {
+
+ internal CountdownEvent Latch;
+ internal bool WaitAfterUpdate = false;
+ internal CountdownEvent Signal;
+
+ public LatchedIndexWriter(Directory d, IndexWriterConfig conf, CountdownEvent latch, CountdownEvent signal)
+ : base(d, conf)
+ {
+ this.Latch = latch;
+ this.Signal = signal;
+
+ }
+
+ public override void UpdateDocument(Term term, IEnumerable<IIndexableField> doc, Analyzer analyzer)
+ {
+ base.UpdateDocument(term, doc, analyzer);
+ try
+ {
+ if (WaitAfterUpdate)
+ {
+ Signal.Reset(Signal.CurrentCount == 0 ? 0 : Signal.CurrentCount - 1);
+ Latch.Wait();
+ }
+ }
+#pragma warning disable 168
+ catch (ThreadInterruptedException e)
+#pragma warning restore 168
+ {
+ throw;
+ }
+ }
+ }
+
+ [Test]
+ public virtual void TestEvilSearcherFactory()
+ {
+ Directory dir = NewDirectory();
+ RandomIndexWriter w = new RandomIndexWriter(Random(), dir, Similarity, TimeZone);
+ w.Commit();
+
+ IndexReader other = DirectoryReader.Open(dir);
+
+ SearcherFactory theEvilOne = new SearcherFactoryAnonymousInnerClassHelper2(this, other);
+
+ try
+ {
+ new SearcherManager(w.w, false, theEvilOne);
+ Assert.Fail("didn't hit expected exception");
+ }
+#pragma warning disable 168
+ catch (InvalidOperationException ise)
+#pragma warning restore 168
+ {
+ // expected
+ }
+ w.Dispose();
+ other.Dispose();
+ dir.Dispose();
+ }
+
+ private class SearcherFactoryAnonymousInnerClassHelper2 : SearcherFactory
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private IndexReader Other;
+
+ public SearcherFactoryAnonymousInnerClassHelper2(TestControlledRealTimeReopenThread outerInstance, IndexReader other)
+ {
+ this.OuterInstance = outerInstance;
+ this.Other = other;
+ }
+
+ public override IndexSearcher NewSearcher(IndexReader ignored)
+ {
+ return OuterInstance.NewSearcher(Other);
+ }
+ }
+
+ [Test]
+ public virtual void TestListenerCalled()
+ {
+ Directory dir = NewDirectory();
+ IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null));
+ AtomicBoolean afterRefreshCalled = new AtomicBoolean(false);
+ SearcherManager sm = new SearcherManager(iw, true, new SearcherFactory());
+ sm.AddListener(new RefreshListenerAnonymousInnerClassHelper(this, afterRefreshCalled));
+ iw.AddDocument(new Document());
+ iw.Commit();
+ Assert.IsFalse(afterRefreshCalled.Get());
+ sm.MaybeRefreshBlocking();
+ Assert.IsTrue(afterRefreshCalled.Get());
+ sm.Dispose();
+ iw.Dispose();
+ dir.Dispose();
+ }
+
+ private class RefreshListenerAnonymousInnerClassHelper : ReferenceManager.IRefreshListener
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private AtomicBoolean AfterRefreshCalled;
+
+ public RefreshListenerAnonymousInnerClassHelper(TestControlledRealTimeReopenThread outerInstance, AtomicBoolean afterRefreshCalled)
+ {
+ this.OuterInstance = outerInstance;
+ this.AfterRefreshCalled = afterRefreshCalled;
+ }
+
+ public void BeforeRefresh()
+ {
+ }
+ public void AfterRefresh(bool didRefresh)
+ {
+ if (didRefresh)
+ {
+ AfterRefreshCalled.Set(true);
+ }
+ }
+ }
+
+ // LUCENE-5461
+ [Test, Timeout(120000)]
+ public virtual void TestCRTReopen()
+ {
+ //test behaving badly
+
+ //should be high enough
+ int maxStaleSecs = 20;
+
+ //build crap data just to store it.
+ string s = " abcdefghijklmnopqrstuvwxyz ";
+ char[] chars = s.ToCharArray();
+ StringBuilder builder = new StringBuilder(2048);
+ for (int i = 0; i < 2048; i++)
+ {
+ builder.Append(chars[Random().Next(chars.Length)]);
+ }
+ string content = builder.ToString();
+
+ SnapshotDeletionPolicy sdp = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
+ Directory dir = new NRTCachingDirectory(NewFSDirectory(CreateTempDir("nrt")), 5, 128);
+ IndexWriterConfig config = new IndexWriterConfig(
+#pragma warning disable 612, 618
+ Version.LUCENE_46,
+#pragma warning restore 612, 618
+ new MockAnalyzer(Random()));
+ config.SetIndexDeletionPolicy(sdp);
+ config.SetOpenMode(OpenMode.CREATE_OR_APPEND);
+ IndexWriter iw = new IndexWriter(dir, config);
+ SearcherManager sm = new SearcherManager(iw, true, new SearcherFactory());
+ TrackingIndexWriter tiw = new TrackingIndexWriter(iw);
+ ControlledRealTimeReopenThread<IndexSearcher> controlledRealTimeReopenThread = new ControlledRealTimeReopenThread<IndexSearcher>(tiw, sm, maxStaleSecs, 0);
+
+ controlledRealTimeReopenThread.SetDaemon(true);
+ controlledRealTimeReopenThread.Start();
+
+ IList<ThreadClass> commitThreads = new List<ThreadClass>();
+
+ for (int i = 0; i < 500; i++)
+ {
+ if (i > 0 && i % 50 == 0)
+ {
+ ThreadClass commitThread = new RunnableAnonymousInnerClassHelper(this, sdp, dir, iw);
+ commitThread.Start();
+ commitThreads.Add(commitThread);
+ }
+ Document d = new Document();
+ d.Add(new TextField("count", i + "", Field.Store.NO));
+ d.Add(new TextField("content", content, Field.Store.YES));
+ long start = DateTime.Now.Millisecond;
+ long l = tiw.AddDocument(d);
+ controlledRealTimeReopenThread.WaitForGeneration(l);
+ long wait = DateTime.Now.Millisecond - start;
+ Assert.IsTrue(wait < (maxStaleSecs * 1000), "waited too long for generation " + wait);
+ IndexSearcher searcher = sm.Acquire();
+ TopDocs td = searcher.Search(new TermQuery(new Term("count", i + "")), 10);
+ sm.Release(searcher);
+ Assert.AreEqual(1, td.TotalHits);
+ }
+
+ foreach (ThreadClass commitThread in commitThreads)
+ {
+ commitThread.Join();
+ }
+
+ controlledRealTimeReopenThread.Dispose();
+ sm.Dispose();
+ iw.Dispose();
+ dir.Dispose();
+ }
+
+ private class RunnableAnonymousInnerClassHelper : ThreadClass
+ {
+ private readonly TestControlledRealTimeReopenThread OuterInstance;
+
+ private SnapshotDeletionPolicy Sdp;
+ private Directory Dir;
+ private IndexWriter Iw;
+
+ public RunnableAnonymousInnerClassHelper(TestControlledRealTimeReopenThread outerInstance, SnapshotDeletionPolicy sdp, Directory dir, IndexWriter iw)
+ {
+ this.OuterInstance = outerInstance;
+ this.Sdp = sdp;
+ this.Dir = dir;
+ this.Iw = iw;
+ }
+
+ public override void Run()
+ {
+ try
+ {
+ Iw.Commit();
+ IndexCommit ic = Sdp.Snapshot();
+ foreach (string name in ic.FileNames)
+ {
+ //distribute, and backup
+ //System.out.println(names);
+ Assert.IsTrue(SlowFileExists(Dir, name));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new Exception(e.Message, e);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file