blob: ea5ea5ecd64692e3ced5ecf17a5defcfc5150d7e [file] [log] [blame]
/*
* 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 System;
using Lucene.Net.Support;
using NUnit.Framework;
using WhitespaceAnalyzer = Lucene.Net.Analysis.WhitespaceAnalyzer;
using Document = Lucene.Net.Documents.Document;
using Field = Lucene.Net.Documents.Field;
using IndexReader = Lucene.Net.Index.IndexReader;
using IndexWriter = Lucene.Net.Index.IndexWriter;
using MaxFieldLength = Lucene.Net.Index.IndexWriter.MaxFieldLength;
using QueryParser = Lucene.Net.QueryParsers.QueryParser;
using Directory = Lucene.Net.Store.Directory;
using RAMDirectory = Lucene.Net.Store.RAMDirectory;
using TimeExceededException = Lucene.Net.Search.TimeLimitingCollector.TimeExceededException;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
namespace Lucene.Net.Search
{
/// <summary> Tests the {@link TimeLimitingCollector}. This test checks (1) search
/// correctness (regardless of timeout), (2) expected timeout behavior,
/// and (3) a sanity test with multiple searching threads.
/// </summary>
[TestFixture]
public class TestTimeLimitingCollector:LuceneTestCase
{
private class AnonymousClassThread:ThreadClass
{
public AnonymousClassThread(bool withTimeout, System.Collections.BitArray success, int num, TestTimeLimitingCollector enclosingInstance)
{
InitBlock(withTimeout, success, num, enclosingInstance);
}
private void InitBlock(bool withTimeout, System.Collections.BitArray success, int num, TestTimeLimitingCollector enclosingInstance)
{
this.withTimeout = withTimeout;
this.success = success;
this.num = num;
this.enclosingInstance = enclosingInstance;
}
private bool withTimeout;
private System.Collections.BitArray success;
private int num;
private TestTimeLimitingCollector enclosingInstance;
public TestTimeLimitingCollector Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
override public void Run()
{
if (withTimeout)
{
Enclosing_Instance.DoTestTimeout(true, true);
}
else
{
Enclosing_Instance.DoTestSearch();
}
lock (success.SyncRoot)
{
success.Set(num, true);
}
}
}
private const int SLOW_DOWN = 47;
private static readonly long TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs.
// max time allowed is relaxed for multithreading tests.
// the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000).
// but this is not a real failure, just noise.
private const double MULTI_THREAD_SLACK = 7;
private const int N_DOCS = 3000;
private const int N_THREADS = 50;
private Searcher searcher;
private System.String FIELD_NAME = "body";
private Query query;
/*public TestTimeLimitingCollector(System.String name):base(name)
{
}*/
/// <summary> initializes searcher with a document set</summary>
[SetUp]
public override void SetUp()
{
base.SetUp();
System.String[] docText = new System.String[]{"docThatNeverMatchesSoWeCanRequireLastDocCollectedToBeGreaterThanZero", "one blah three", "one foo three multiOne", "one foobar three multiThree", "blueberry pancakes", "blueberry pie", "blueberry strudel", "blueberry pizza"};
Directory directory = new RAMDirectory();
IndexWriter iw = new IndexWriter(directory, new WhitespaceAnalyzer(), true, MaxFieldLength.UNLIMITED);
for (int i = 0; i < N_DOCS; i++)
{
Add(docText[i % docText.Length], iw);
}
iw.Close();
searcher = new IndexSearcher(directory, true);
System.String qtxt = "one";
// start from 1, so that the 0th doc never matches
for (int i = 0; i < docText.Length; i++)
{
qtxt += (' ' + docText[i]); // large query so that search will be longer
}
QueryParser queryParser = new QueryParser(Util.Version.LUCENE_CURRENT, FIELD_NAME, new WhitespaceAnalyzer());
query = queryParser.Parse(qtxt);
// warm the searcher
searcher.Search(query, null, 1000);
}
[TearDown]
public override void TearDown()
{
searcher.Close();
base.TearDown();
}
private void Add(System.String value_Renamed, IndexWriter iw)
{
Document d = new Document();
d.Add(new Field(FIELD_NAME, value_Renamed, Field.Store.NO, Field.Index.ANALYZED));
iw.AddDocument(d);
}
private void Search(Collector collector)
{
searcher.Search(query, collector);
}
/// <summary> test search correctness with no timeout</summary>
[Test]
public virtual void TestSearch()
{
DoTestSearch();
}
private void DoTestSearch()
{
int totalResults = 0;
int totalTLCResults = 0;
try
{
MyHitCollector myHc = new MyHitCollector(this);
Search(myHc);
totalResults = myHc.HitCount();
myHc = new MyHitCollector(this);
long oneHour = 3600000;
Collector tlCollector = CreateTimedCollector(myHc, oneHour, false);
Search(tlCollector);
totalTLCResults = myHc.HitCount();
}
catch (System.Exception e)
{
System.Console.Error.WriteLine(e.StackTrace);
Assert.IsTrue(false, "Unexpected exception: " + e); //==fail
}
Assert.AreEqual(totalResults, totalTLCResults, "Wrong number of results!");
}
private Collector CreateTimedCollector(MyHitCollector hc, long timeAllowed, bool greedy)
{
TimeLimitingCollector res = new TimeLimitingCollector(hc, timeAllowed);
res.IsGreedy = greedy; // set to true to make sure at least one doc is collected.
return res;
}
/// <summary> Test that timeout is obtained, and soon enough!</summary>
[Test]
public virtual void TestTimeoutGreedy()
{
DoTestTimeout(false, true);
}
/// <summary> Test that timeout is obtained, and soon enough!</summary>
[Test]
public virtual void TestTimeoutNotGreedy()
{
DoTestTimeout(false, false);
}
private void DoTestTimeout(bool multiThreaded, bool greedy)
{
// setup
MyHitCollector myHc = new MyHitCollector(this);
myHc.SetSlowDown(SLOW_DOWN);
Collector tlCollector = CreateTimedCollector(myHc, TIME_ALLOWED, greedy);
// search
TimeExceededException timoutException = null;
try
{
Search(tlCollector);
}
catch (TimeExceededException x)
{
timoutException = x;
}
catch (System.Exception e)
{
Assert.IsTrue(false, "Unexpected exception: " + e); //==fail
}
// must get exception
Assert.IsNotNull(timoutException, "Timeout expected!");
// greediness affect last doc collected
int exceptionDoc = timoutException.LastDocCollected;
int lastCollected = myHc.GetLastDocCollected();
Assert.IsTrue(exceptionDoc > 0, "doc collected at timeout must be > 0!");
if (greedy)
{
Assert.IsTrue(exceptionDoc == lastCollected, "greedy=" + greedy + " exceptionDoc=" + exceptionDoc + " != lastCollected=" + lastCollected);
Assert.IsTrue(myHc.HitCount() > 0, "greedy, but no hits found!");
}
else
{
Assert.IsTrue(exceptionDoc > lastCollected, "greedy=" + greedy + " exceptionDoc=" + exceptionDoc + " not > lastCollected=" + lastCollected);
}
// verify that elapsed time at exception is within valid limits
Assert.AreEqual(timoutException.TimeAllowed, TIME_ALLOWED);
// a) Not too early
Assert.IsTrue(timoutException.TimeElapsed > TIME_ALLOWED - TimeLimitingCollector.Resolution, "elapsed=" + timoutException.TimeElapsed + " <= (allowed-resolution)=" + (TIME_ALLOWED - TimeLimitingCollector.Resolution));
// b) Not too late.
// This part is problematic in a busy test system, so we just print a warning.
// We already verified that a timeout occurred, we just can't be picky about how long it took.
if (timoutException.TimeElapsed > MaxTime(multiThreaded))
{
System.Console.Out.WriteLine("Informative: timeout exceeded (no action required: most probably just " + " because the test machine is slower than usual): " + "lastDoc=" + exceptionDoc + " ,&& allowed=" + timoutException.TimeAllowed + " ,&& elapsed=" + timoutException.TimeElapsed + " >= " + MaxTimeStr(multiThreaded));
}
}
private long MaxTime(bool multiThreaded)
{
long res = 2 * TimeLimitingCollector.Resolution + TIME_ALLOWED + SLOW_DOWN; // some slack for less noise in this test
if (multiThreaded)
{
res = (long) (res * MULTI_THREAD_SLACK); // larger slack
}
return res;
}
private System.String MaxTimeStr(bool multiThreaded)
{
System.String s = "( " + "2*resolution + TIME_ALLOWED + SLOW_DOWN = " + "2*" + TimeLimitingCollector.Resolution + " + " + TIME_ALLOWED + " + " + SLOW_DOWN + ")";
if (multiThreaded)
{
s = MULTI_THREAD_SLACK + " * " + s;
}
return MaxTime(multiThreaded) + " = " + s;
}
/// <summary> Test timeout behavior when resolution is modified. </summary>
[Test]
public virtual void TestModifyResolution()
{
try
{
// increase and test
uint resolution = 20 * TimeLimitingCollector.DEFAULT_RESOLUTION; //400
TimeLimitingCollector.Resolution = resolution;
Assert.AreEqual(resolution, TimeLimitingCollector.Resolution);
DoTestTimeout(false, true);
// decrease much and test
resolution = 5;
TimeLimitingCollector.Resolution = resolution;
Assert.AreEqual(resolution, TimeLimitingCollector.Resolution);
DoTestTimeout(false, true);
// return to default and test
resolution = TimeLimitingCollector.DEFAULT_RESOLUTION;
TimeLimitingCollector.Resolution = resolution;
Assert.AreEqual(resolution, TimeLimitingCollector.Resolution);
DoTestTimeout(false, true);
}
finally
{
TimeLimitingCollector.Resolution = TimeLimitingCollector.DEFAULT_RESOLUTION;
}
}
/// <summary> Test correctness with multiple searching threads.</summary>
[Test]
public virtual void TestSearchMultiThreaded()
{
DoTestMultiThreads(false);
}
/// <summary> Test correctness with multiple searching threads.</summary>
[Test]
public virtual void TestTimeoutMultiThreaded()
{
DoTestMultiThreads(true);
}
private void DoTestMultiThreads(bool withTimeout)
{
ThreadClass[] threadArray = new ThreadClass[N_THREADS];
System.Collections.BitArray success = new System.Collections.BitArray((N_THREADS % 64 == 0?N_THREADS / 64:N_THREADS / 64 + 1) * 64);
for (int i = 0; i < threadArray.Length; ++i)
{
int num = i;
threadArray[num] = new AnonymousClassThread(withTimeout, success, num, this);
}
for (int i = 0; i < threadArray.Length; ++i)
{
threadArray[i].Start();
}
for (int i = 0; i < threadArray.Length; ++i)
{
threadArray[i].Join();
}
Assert.AreEqual(N_THREADS, BitSetSupport.Cardinality(success), "some threads failed!");
}
// counting collector that can slow down at collect().
private class MyHitCollector:Collector
{
public MyHitCollector(TestTimeLimitingCollector enclosingInstance)
{
InitBlock(enclosingInstance);
}
private void InitBlock(TestTimeLimitingCollector enclosingInstance)
{
this.enclosingInstance = enclosingInstance;
}
private TestTimeLimitingCollector enclosingInstance;
public TestTimeLimitingCollector Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
private System.Collections.BitArray bits = new System.Collections.BitArray(64);
private int slowdown = 0;
private int lastDocCollected = - 1;
private int docBase = 0;
/// <summary> amount of time to wait on each collect to simulate a long iteration</summary>
public virtual void SetSlowDown(int milliseconds)
{
slowdown = milliseconds;
}
public virtual int HitCount()
{
return BitSetSupport.Cardinality(bits);
}
public virtual int GetLastDocCollected()
{
return lastDocCollected;
}
public override void SetScorer(Scorer scorer)
{
// scorer is not needed
}
public override void Collect(int doc)
{
int docId = doc + docBase;
if (slowdown > 0)
{
try
{
System.Threading.Thread.Sleep(new System.TimeSpan((System.Int64) 10000 * slowdown));
}
catch (System.Threading.ThreadInterruptedException ie)
{
throw;
}
}
System.Diagnostics.Debug.Assert(docId >= 0, "base=" + docBase + " doc=" + doc);
bits.Length = Math.Max(bits.Length, docId + 1);
bits.Set(docId, true);
lastDocCollected = docId;
}
public override void SetNextReader(IndexReader reader, int base_Renamed)
{
docBase = base_Renamed;
}
public override bool AcceptsDocsOutOfOrder
{
get { return false; }
}
}
}
}