blob: cc83440b85ca68d072ee12544b863d6d131e6fcf [file] [log] [blame]
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;
}
}
}
}