| using System; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Text; |
| |
| namespace Lucene.Net.Search |
| { |
| using NUnit.Framework; |
| using System.IO; |
| using AllDeletedFilterReader = Lucene.Net.Index.AllDeletedFilterReader; |
| using AtomicReader = Lucene.Net.Index.AtomicReader; |
| using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; |
| using Bits = Lucene.Net.Util.Bits; |
| using Directory = Lucene.Net.Store.Directory; |
| using DirectoryReader = Lucene.Net.Index.DirectoryReader; |
| using Document = Documents.Document; |
| using IndexReader = Lucene.Net.Index.IndexReader; |
| using IndexWriter = Lucene.Net.Index.IndexWriter; |
| using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig; |
| 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 Assert = junit.framework.Assert; |
| |
| using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; |
| using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper; |
| using MultiReader = Lucene.Net.Index.MultiReader; |
| using RAMDirectory = Lucene.Net.Store.RAMDirectory; |
| using SlowCompositeReaderWrapper = Lucene.Net.Index.SlowCompositeReaderWrapper; |
| using Similarities; |
| |
| /// <summary> |
| /// Utility class for sanity-checking queries. |
| /// </summary> |
| public class QueryUtils |
| { |
| /// <summary> |
| /// Check the types of things query objects should be able to do. </summary> |
| public static void Check(Query q) |
| { |
| CheckHashEquals(q); |
| } |
| |
| /// <summary> |
| /// check very basic hashCode and equals </summary> |
| public static void CheckHashEquals(Query q) |
| { |
| Query q2 = (Query)q.Clone(); |
| CheckEqual(q, q2); |
| |
| Query q3 = (Query)q.Clone(); |
| q3.Boost = 7.21792348f; |
| CheckUnequal(q, q3); |
| |
| // test that a class check is done so that no exception is thrown |
| // in the implementation of equals() |
| Query whacky = new QueryAnonymousInnerClassHelper(); |
| whacky.Boost = q.Boost; |
| CheckUnequal(q, whacky); |
| |
| // null test |
| Assert.IsFalse(q.Equals(null)); |
| } |
| |
| private class QueryAnonymousInnerClassHelper : Query |
| { |
| public QueryAnonymousInnerClassHelper() |
| { |
| } |
| |
| public override string ToString(string field) |
| { |
| return "My Whacky Query"; |
| } |
| } |
| |
| public static void CheckEqual(Query q1, Query q2) |
| { |
| Assert.IsTrue(q1.Equals(q2)); |
| Assert.AreEqual(q1, q2); |
| Assert.AreEqual(q1.GetHashCode(), q2.GetHashCode()); |
| } |
| |
| public static void CheckUnequal(Query q1, Query q2) |
| { |
| Assert.IsFalse(q1.Equals(q2), q1 + " equal to " + q2); |
| Assert.IsFalse(q2.Equals(q1), q2 + " equal to " + q1); |
| |
| // possible this test can fail on a hash collision... if that |
| // happens, please change test to use a different example. |
| Assert.IsTrue(q1.GetHashCode() != q2.GetHashCode()); |
| } |
| |
| /// <summary> |
| /// deep check that explanations of a query 'score' correctly </summary> |
| public static void CheckExplanations(Query q, IndexSearcher s) |
| { |
| CheckHits.CheckExplanations(q, null, s, true); |
| } |
| |
| /// <summary> |
| /// Various query sanity checks on a searcher, some checks are only done for |
| /// instanceof IndexSearcher. |
| /// </summary> |
| /// <param name = "similarity" > |
| /// LUCENENET specific |
| /// Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/> |
| /// </param> |
| /// <seealso cref= #check(Query) </seealso> |
| /// <seealso cref= #checkFirstSkipTo </seealso> |
| /// <seealso cref= #checkSkipTo </seealso> |
| /// <seealso cref= #checkExplanations </seealso> |
| /// <seealso cref= #checkEqual </seealso> |
| public static void Check(Random random, Query q1, IndexSearcher s, Similarity similarity) |
| { |
| Check(random, q1, s, true, similarity); |
| } |
| |
| /// <param name = "similarity" > |
| /// LUCENENET specific |
| /// Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/> |
| /// </param> |
| public static void Check(Random random, Query q1, IndexSearcher s, bool wrap, Similarity similarity) |
| { |
| try |
| { |
| Check(q1); |
| if (s != null) |
| { |
| CheckFirstSkipTo(q1, s, similarity); |
| CheckSkipTo(q1, s, similarity); |
| if (wrap) |
| { |
| Check(random, q1, WrapUnderlyingReader(random, s, -1, similarity), false, similarity); |
| Check(random, q1, WrapUnderlyingReader(random, s, 0, similarity), false, similarity); |
| Check(random, q1, WrapUnderlyingReader(random, s, +1, similarity), false, similarity); |
| } |
| CheckExplanations(q1, s); |
| |
| Query q2 = (Query)q1.Clone(); |
| CheckEqual(s.Rewrite(q1), s.Rewrite(q2)); |
| } |
| } |
| catch (IOException e) |
| { |
| throw new Exception(e.Message, e); |
| } |
| } |
| |
| public static void PurgeFieldCache(IndexReader r) |
| { |
| // this is just a hack, to get an atomic reader that contains all subreaders for insanity checks |
| FieldCache.DEFAULT.PurgeByCacheKey(SlowCompositeReaderWrapper.Wrap(r).CoreCacheKey); |
| } |
| |
| /// <summary> |
| /// this is a MultiReader that can be used for randomly wrapping other readers |
| /// without creating FieldCache insanity. |
| /// The trick is to use an opaque/fake cache key. |
| /// </summary> |
| public class FCInvisibleMultiReader : MultiReader |
| { |
| internal readonly object CacheKey = new object(); |
| |
| public FCInvisibleMultiReader(params IndexReader[] readers) |
| : base(readers) |
| { |
| } |
| |
| public override object CoreCacheKey |
| { |
| get |
| { |
| return CacheKey; |
| } |
| } |
| |
| public override object CombinedCoreAndDeletesKey |
| { |
| get |
| { |
| return CacheKey; |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Given an IndexSearcher, returns a new IndexSearcher whose IndexReader |
| /// is a MultiReader containing the Reader of the original IndexSearcher, |
| /// as well as several "empty" IndexReaders -- some of which will have |
| /// deleted documents in them. this new IndexSearcher should |
| /// behave exactly the same as the original IndexSearcher. </summary> |
| /// <param name="s"> the searcher to wrap </param> |
| /// <param name="edge"> if negative, s will be the first sub; if 0, s will be in the middle, if positive s will be the last sub </param> |
| /// <param name="similarity"> |
| /// LUCENENET specific |
| /// Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/> |
| /// </param> |
| public static IndexSearcher WrapUnderlyingReader(Random random, IndexSearcher s, int edge, Similarity similarity) |
| { |
| IndexReader r = s.IndexReader; |
| |
| // we can't put deleted docs before the nested reader, because |
| // it will throw off the docIds |
| IndexReader[] readers = new IndexReader[] { edge < 0 ? r : EmptyReaders[0], EmptyReaders[0], new FCInvisibleMultiReader(edge < 0 ? EmptyReaders[4] : EmptyReaders[0], EmptyReaders[0], 0 == edge ? r : EmptyReaders[0]), 0 < edge ? EmptyReaders[0] : EmptyReaders[7], EmptyReaders[0], new FCInvisibleMultiReader(0 < edge ? EmptyReaders[0] : EmptyReaders[5], EmptyReaders[0], 0 < edge ? r : EmptyReaders[0]) }; |
| |
| IndexSearcher @out = LuceneTestCase.NewSearcher(new FCInvisibleMultiReader(readers), similarity); |
| @out.Similarity = s.Similarity; |
| return @out; |
| } |
| |
| internal static readonly IndexReader[] EmptyReaders = null;// = new IndexReader[8]; |
| |
| static QueryUtils() |
| { |
| EmptyReaders = new IndexReader[8]; |
| try |
| { |
| EmptyReaders[0] = new MultiReader(); |
| EmptyReaders[4] = MakeEmptyIndex(new Random(0), 4); |
| EmptyReaders[5] = MakeEmptyIndex(new Random(0), 5); |
| EmptyReaders[7] = MakeEmptyIndex(new Random(0), 7); |
| } |
| catch (IOException ex) |
| { |
| throw new Exception(ex.Message, ex); |
| } |
| } |
| |
| private static IndexReader MakeEmptyIndex(Random random, int numDocs) |
| { |
| Debug.Assert(numDocs > 0); |
| Directory d = new MockDirectoryWrapper(random, new RAMDirectory()); |
| IndexWriter w = new IndexWriter(d, new IndexWriterConfig(LuceneTestCase.TEST_VERSION_CURRENT, new MockAnalyzer(random))); |
| for (int i = 0; i < numDocs; i++) |
| { |
| w.AddDocument(new Document()); |
| } |
| w.ForceMerge(1); |
| w.Commit(); |
| w.Dispose(); |
| DirectoryReader reader = DirectoryReader.Open(d); |
| return new AllDeletedFilterReader(LuceneTestCase.GetOnlySegmentReader(reader)); |
| } |
| |
| /// <summary> |
| /// alternate scorer skipTo(),skipTo(),next(),next(),skipTo(),skipTo(), etc |
| /// and ensure a hitcollector receives same docs and scores |
| /// </summary> |
| /// <param name = "similarity" > |
| /// LUCENENET specific |
| /// Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/> |
| /// </param> |
| public static void CheckSkipTo(Query q, IndexSearcher s, Similarity similarity) |
| { |
| //System.out.println("Checking "+q); |
| IList<AtomicReaderContext> readerContextArray = s.TopReaderContext.Leaves; |
| if (s.CreateNormalizedWeight(q).ScoresDocsOutOfOrder()) // in this case order of skipTo() might differ from that of next(). |
| { |
| return; |
| } |
| |
| const int skip_op = 0; |
| const int next_op = 1; |
| int[][] orders = new int[][] { new int[] { next_op }, new int[] { skip_op }, new int[] { skip_op, next_op }, new int[] { next_op, skip_op }, new int[] { skip_op, skip_op, next_op, next_op }, new int[] { next_op, next_op, skip_op, skip_op }, new int[] { skip_op, skip_op, skip_op, next_op, next_op } }; |
| for (int k = 0; k < orders.Length; k++) |
| { |
| int[] order = orders[k]; |
| // System.out.print("Order:");for (int i = 0; i < order.Length; i++) |
| // System.out.print(order[i]==skip_op ? " skip()":" next()"); |
| // System.out.println(); |
| int[] opidx = new int[] { 0 }; |
| int[] lastDoc = new int[] { -1 }; |
| |
| // FUTURE: ensure scorer.Doc()==-1 |
| |
| const float maxDiff = 1e-5f; |
| AtomicReader[] lastReader = new AtomicReader[] { null }; |
| |
| s.Search(q, new CollectorAnonymousInnerClassHelper(q, s, readerContextArray, skip_op, order, opidx, lastDoc, maxDiff, lastReader, similarity)); |
| |
| if (lastReader[0] != null) |
| { |
| // confirm that skipping beyond the last doc, on the |
| // previous reader, hits NO_MORE_DOCS |
| AtomicReader previousReader = lastReader[0]; |
| IndexSearcher indexSearcher = LuceneTestCase.NewSearcher(previousReader, false, similarity); |
| indexSearcher.Similarity = s.Similarity; |
| Weight w = indexSearcher.CreateNormalizedWeight(q); |
| AtomicReaderContext ctx = (AtomicReaderContext)previousReader.Context; |
| Scorer scorer = w.Scorer(ctx, ((AtomicReader)ctx.Reader).LiveDocs); |
| if (scorer != null) |
| { |
| bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS; |
| Assert.IsFalse(more, "query's last doc was " + lastDoc[0] + " but skipTo(" + (lastDoc[0] + 1) + ") got to " + scorer.DocID()); |
| } |
| } |
| } |
| } |
| |
| private class CollectorAnonymousInnerClassHelper : Collector |
| { |
| private Query q; |
| private IndexSearcher s; |
| private IList<AtomicReaderContext> ReaderContextArray; |
| private int Skip_op; |
| private int[] Order; |
| private int[] Opidx; |
| private int[] LastDoc; |
| private float MaxDiff; |
| private AtomicReader[] LastReader; |
| private readonly Similarity Similarity; |
| |
| public CollectorAnonymousInnerClassHelper(Query q, IndexSearcher s, IList<AtomicReaderContext> readerContextArray, |
| int skip_op, int[] order, int[] opidx, int[] lastDoc, float maxDiff, AtomicReader[] lastReader, |
| Similarity similarity) |
| { |
| this.q = q; |
| this.s = s; |
| this.ReaderContextArray = readerContextArray; |
| this.Skip_op = skip_op; |
| this.Order = order; |
| this.Opidx = opidx; |
| this.LastDoc = lastDoc; |
| this.MaxDiff = maxDiff; |
| this.LastReader = lastReader; |
| this.Similarity = similarity; |
| } |
| |
| private Scorer sc; |
| private Scorer scorer; |
| private int leafPtr; |
| |
| public override Scorer Scorer |
| { |
| set |
| { |
| this.sc = value; |
| } |
| } |
| |
| public override void Collect(int doc) |
| { |
| float score = sc.Score(); |
| LastDoc[0] = doc; |
| try |
| { |
| if (scorer == null) |
| { |
| Weight w = s.CreateNormalizedWeight(q); |
| AtomicReaderContext context = ReaderContextArray[leafPtr]; |
| scorer = w.Scorer(context, (context.AtomicReader).LiveDocs); |
| } |
| |
| int op = Order[(Opidx[0]++) % Order.Length]; |
| // System.out.println(op==skip_op ? |
| // "skip("+(sdoc[0]+1)+")":"next()"); |
| bool more = op == Skip_op ? scorer.Advance(scorer.DocID() + 1) != DocIdSetIterator.NO_MORE_DOCS : scorer.NextDoc() != DocIdSetIterator.NO_MORE_DOCS; |
| int scorerDoc = scorer.DocID(); |
| float scorerScore = scorer.Score(); |
| float scorerScore2 = scorer.Score(); |
| float scoreDiff = Math.Abs(score - scorerScore); |
| float scorerDiff = Math.Abs(scorerScore2 - scorerScore); |
| if (!more || doc != scorerDoc || scoreDiff > MaxDiff || scorerDiff > MaxDiff) |
| { |
| StringBuilder sbord = new StringBuilder(); |
| for (int i = 0; i < Order.Length; i++) |
| { |
| sbord.Append(Order[i] == Skip_op ? " skip()" : " next()"); |
| } |
| throw new Exception("ERROR matching docs:" + "\n\t" + (doc != scorerDoc ? "--> " : "") + "doc=" + doc + ", scorerDoc=" + scorerDoc + "\n\t" + (!more ? "--> " : "") + "tscorer.more=" + more + "\n\t" + (scoreDiff > MaxDiff ? "--> " : "") + "scorerScore=" + scorerScore + " scoreDiff=" + scoreDiff + " maxDiff=" + MaxDiff + "\n\t" + (scorerDiff > MaxDiff ? "--> " : "") + "scorerScore2=" + scorerScore2 + " scorerDiff=" + scorerDiff + "\n\thitCollector.Doc=" + doc + " score=" + score + "\n\t Scorer=" + scorer + "\n\t Query=" + q + " " + q.GetType().Name + "\n\t Searcher=" + s + "\n\t Order=" + sbord + "\n\t Op=" + (op == Skip_op ? " skip()" : " next()")); |
| } |
| } |
| catch (IOException e) |
| { |
| throw new Exception(e.Message, e); |
| } |
| } |
| |
| public override AtomicReaderContext NextReader |
| { |
| set |
| { |
| // confirm that skipping beyond the last doc, on the |
| // previous reader, hits NO_MORE_DOCS |
| if (LastReader[0] != null) |
| { |
| AtomicReader previousReader = LastReader[0]; |
| IndexSearcher indexSearcher = LuceneTestCase.NewSearcher(previousReader, Similarity); |
| indexSearcher.Similarity = s.Similarity; |
| Weight w = indexSearcher.CreateNormalizedWeight(q); |
| AtomicReaderContext ctx = (AtomicReaderContext)indexSearcher.TopReaderContext; |
| Scorer scorer = w.Scorer(ctx, ((AtomicReader)ctx.Reader).LiveDocs); |
| if (scorer != null) |
| { |
| bool more = scorer.Advance(LastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS; |
| Assert.IsFalse(more, "query's last doc was " + LastDoc[0] + " but skipTo(" + (LastDoc[0] + 1) + ") got to " + scorer.DocID()); |
| } |
| leafPtr++; |
| } |
| LastReader[0] = (AtomicReader)value.Reader; |
| Debug.Assert(ReaderContextArray[leafPtr].Reader == value.Reader); |
| this.scorer = null; |
| LastDoc[0] = -1; |
| } |
| } |
| |
| public override bool AcceptsDocsOutOfOrder() |
| { |
| return false; |
| } |
| } |
| |
| /// <summary> |
| /// check that first skip on just created scorers always goes to the right doc</summary> |
| /// <param name = "similarity" > |
| /// LUCENENET specific |
| /// Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/> |
| /// </param> |
| public static void CheckFirstSkipTo(Query q, IndexSearcher s, Similarity similarity) |
| { |
| //System.out.println("checkFirstSkipTo: "+q); |
| const float maxDiff = 1e-3f; |
| int[] lastDoc = new int[] { -1 }; |
| AtomicReader[] lastReader = new AtomicReader[] { null }; |
| IList<AtomicReaderContext> context = s.TopReaderContext.Leaves; |
| s.Search(q, new CollectorAnonymousInnerClassHelper2(q, s, maxDiff, lastDoc, lastReader, context, similarity)); |
| |
| if (lastReader[0] != null) |
| { |
| // confirm that skipping beyond the last doc, on the |
| // previous reader, hits NO_MORE_DOCS |
| AtomicReader previousReader = lastReader[0]; |
| IndexSearcher indexSearcher = LuceneTestCase.NewSearcher(previousReader, similarity); |
| indexSearcher.Similarity = s.Similarity; |
| Weight w = indexSearcher.CreateNormalizedWeight(q); |
| Scorer scorer = w.Scorer((AtomicReaderContext)indexSearcher.TopReaderContext, previousReader.LiveDocs); |
| if (scorer != null) |
| { |
| bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS; |
| Assert.IsFalse(more, "query's last doc was " + lastDoc[0] + " but skipTo(" + (lastDoc[0] + 1) + ") got to " + scorer.DocID()); |
| } |
| } |
| } |
| |
| private class CollectorAnonymousInnerClassHelper2 : Collector |
| { |
| private Query q; |
| private IndexSearcher s; |
| private float MaxDiff; |
| private int[] LastDoc; |
| private AtomicReader[] LastReader; |
| private IList<AtomicReaderContext> Context; |
| private readonly Similarity Similarity; |
| |
| public CollectorAnonymousInnerClassHelper2(Query q, IndexSearcher s, float maxDiff, int[] lastDoc, AtomicReader[] lastReader, IList<AtomicReaderContext> context, Similarity similarity) |
| { |
| this.q = q; |
| this.s = s; |
| this.MaxDiff = maxDiff; |
| this.LastDoc = lastDoc; |
| this.LastReader = lastReader; |
| this.Context = context; |
| this.Similarity = similarity; |
| } |
| |
| private Scorer scorer; |
| private int leafPtr; |
| private Bits liveDocs; |
| |
| public override Scorer Scorer |
| { |
| set |
| { |
| this.scorer = value; |
| } |
| } |
| |
| public override void Collect(int doc) |
| { |
| float score = scorer.Score(); |
| try |
| { |
| long startMS = Environment.TickCount; |
| for (int i = LastDoc[0] + 1; i <= doc; i++) |
| { |
| Weight w = s.CreateNormalizedWeight(q); |
| Scorer scorer_ = w.Scorer(Context[leafPtr], liveDocs); |
| Assert.IsTrue(scorer_.Advance(i) != DocIdSetIterator.NO_MORE_DOCS, "query collected " + doc + " but skipTo(" + i + ") says no more docs!"); |
| Assert.AreEqual(doc, scorer_.DocID(), "query collected " + doc + " but skipTo(" + i + ") got to " + scorer_.DocID()); |
| float skipToScore = scorer_.Score(); |
| Assert.AreEqual(skipToScore, scorer_.Score(), MaxDiff, "unstable skipTo(" + i + ") score!"); |
| Assert.AreEqual(score, skipToScore, MaxDiff, "query assigned doc " + doc + " a score of <" + score + "> but skipTo(" + i + ") has <" + skipToScore + ">!"); |
| |
| // Hurry things along if they are going slow (eg |
| // if you got SimpleText codec this will kick in): |
| if (i < doc && Environment.TickCount - startMS > 5) |
| { |
| i = doc - 1; |
| } |
| } |
| LastDoc[0] = doc; |
| } |
| catch (IOException e) |
| { |
| throw new Exception(e.Message, e); |
| } |
| } |
| |
| public override AtomicReaderContext NextReader |
| { |
| set |
| { |
| // confirm that skipping beyond the last doc, on the |
| // previous reader, hits NO_MORE_DOCS |
| if (LastReader[0] != null) |
| { |
| AtomicReader previousReader = LastReader[0]; |
| IndexSearcher indexSearcher = LuceneTestCase.NewSearcher(previousReader, Similarity); |
| indexSearcher.Similarity = s.Similarity; |
| Weight w = indexSearcher.CreateNormalizedWeight(q); |
| Scorer scorer = w.Scorer((AtomicReaderContext)indexSearcher.TopReaderContext, previousReader.LiveDocs); |
| if (scorer != null) |
| { |
| bool more = scorer.Advance(LastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS; |
| Assert.IsFalse(more, "query's last doc was " + LastDoc[0] + " but skipTo(" + (LastDoc[0] + 1) + ") got to " + scorer.DocID()); |
| } |
| leafPtr++; |
| } |
| |
| LastReader[0] = (AtomicReader)value.Reader; |
| LastDoc[0] = -1; |
| liveDocs = ((AtomicReader)value.Reader).LiveDocs; |
| } |
| } |
| |
| public override bool AcceptsDocsOutOfOrder() |
| { |
| return false; |
| } |
| } |
| } |
| } |