blob: fc3069e6093b9e94d553d14762496eb3541f2264 [file] [log] [blame]
using Lucene.Net.Analysis;
using Lucene.Net.Codecs;
using Lucene.Net.Diagnostics;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using Console = Lucene.Net.Util.SystemConsole;
namespace Lucene.Net.Index
{
/*
* 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>
/// Silly class that randomizes the indexing experience. EG
/// it may swap in a different merge policy/scheduler; may
/// commit periodically; may or may not forceMerge in the end,
/// may flush by doc count instead of RAM, etc.
/// </summary>
public class RandomIndexWriter : IDisposable
{
public IndexWriter IndexWriter { get; set; } // LUCENENET: Renamed from w to IndexWriter to make it clear what this is.
private readonly Random r;
internal int docCount;
internal int flushAt;
private double flushAtFactor = 1.0;
private bool getReaderCalled;
private readonly Codec codec; // sugar
public static IndexWriter MockIndexWriter(Directory dir, IndexWriterConfig conf, Random r)
{
// Randomly calls Thread.yield so we mixup thread scheduling
Random random = new Random(r.Next());
return MockIndexWriter(dir, conf, new TestPointAnonymousClass(random));
}
private class TestPointAnonymousClass : ITestPoint
{
private readonly Random random;
public TestPointAnonymousClass(Random random)
{
this.random = random;
}
public virtual void Apply(string message)
{
if (random.Next(4) == 2)
{
System.Threading.Thread.Sleep(0);
}
}
}
public static IndexWriter MockIndexWriter(Directory dir, IndexWriterConfig conf, ITestPoint testPoint)
{
conf.SetInfoStream(new TestPointInfoStream(conf.InfoStream, testPoint));
return new IndexWriter(dir, conf);
}
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
/// <summary>
/// Create a <see cref="RandomIndexWriter"/> with a random config: Uses <see cref="LuceneTestCase.TEST_VERSION_CURRENT"/> and <see cref="MockAnalyzer"/>.
/// </summary>
public RandomIndexWriter(Random r, Directory dir)
: this(r, dir, LuceneTestCase.NewIndexWriterConfig(r, LuceneTestCase.TEST_VERSION_CURRENT, new MockAnalyzer(r)))
{
}
/// <summary>
/// Create a <see cref="RandomIndexWriter"/> with a random config: Uses <see cref="LuceneTestCase.TEST_VERSION_CURRENT"/>.
/// </summary>
public RandomIndexWriter(Random r, Directory dir, Analyzer a)
: this(r, dir, LuceneTestCase.NewIndexWriterConfig(r, LuceneTestCase.TEST_VERSION_CURRENT, a))
{
}
/// <summary>
/// Creates a <see cref="RandomIndexWriter"/> with a random config.
/// </summary>
public RandomIndexWriter(Random r, Directory dir, LuceneVersion v, Analyzer a)
: this(r, dir, LuceneTestCase.NewIndexWriterConfig(r, v, a))
{
}
#else
/// <summary>
/// Create a <see cref="RandomIndexWriter"/> with a random config: Uses <see cref="LuceneTestCase.TEST_VERSION_CURRENT"/> and <see cref="MockAnalyzer"/>.
/// </summary>
/// <param name="luceneTestCase">The current test instance.</param>
/// <param name="r"></param>
/// <param name="dir"></param>
// LUCENENET specific
// Similarity and TimeZone parameters allow a RandomIndexWriter to be
// created without adding a dependency on
// <see cref="LuceneTestCase.ClassEnv.Similarity"/> and
// <see cref="LuceneTestCase.ClassEnv.TimeZone"/>
public RandomIndexWriter(LuceneTestCase luceneTestCase, Random r, Directory dir)
: this(r, dir, luceneTestCase.NewIndexWriterConfig(r, LuceneTestCase.TEST_VERSION_CURRENT, new MockAnalyzer(r)))
{
}
/// <summary>
/// Create a <see cref="RandomIndexWriter"/> with a random config: Uses <see cref="LuceneTestCase.TEST_VERSION_CURRENT"/>.
/// </summary>
/// <param name="luceneTestCase">The current test instance.</param>
/// <param name="r"></param>
/// <param name="dir"></param>
/// <param name="a"></param>
// LUCENENET specific
// Similarity and TimeZone parameters allow a RandomIndexWriter to be
// created without adding a dependency on
// <see cref="LuceneTestCase.ClassEnv.Similarity"/> and
// <see cref="LuceneTestCase.ClassEnv.TimeZone"/>
public RandomIndexWriter(LuceneTestCase luceneTestCase, Random r, Directory dir, Analyzer a)
: this(r, dir, luceneTestCase.NewIndexWriterConfig(r, LuceneTestCase.TEST_VERSION_CURRENT, a))
{
}
/// <summary>
/// Creates a <see cref="RandomIndexWriter"/> with a random config.
/// </summary>
/// <param name="luceneTestCase">The current test instance.</param>
/// <param name="r"></param>
/// <param name="dir"></param>
/// <param name="v"></param>
/// <param name="a"></param>
// LUCENENET specific
// Similarity and TimeZone parameters allow a RandomIndexWriter to be
// created without adding a dependency on
// <see cref="LuceneTestCase.ClassEnv.Similarity"/> and
// <see cref="LuceneTestCase.ClassEnv.TimeZone"/>
public RandomIndexWriter(LuceneTestCase luceneTestCase, Random r, Directory dir, LuceneVersion v, Analyzer a)
: this(r, dir, luceneTestCase.NewIndexWriterConfig(r, v, a))
{
}
#endif
/// <summary>
/// Creates a <see cref="RandomIndexWriter"/> with the provided config </summary>
public RandomIndexWriter(Random r, Directory dir, IndexWriterConfig c)
{
// TODO: this should be solved in a different way; Random should not be shared (!).
this.r = new Random(r.Next());
IndexWriter = MockIndexWriter(dir, c, r);
flushAt = TestUtil.NextInt32(r, 10, 1000);
codec = IndexWriter.Config.Codec;
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW dir=" + dir + " config=" + IndexWriter.Config);
Console.WriteLine("codec default=" + codec.Name);
}
// Make sure we sometimes test indices that don't get
// any forced merges:
doRandomForceMerge = !(c.MergePolicy is NoMergePolicy) && r.NextBoolean();
}
/// <summary>
/// Adds a Document. </summary>
/// <seealso cref="IndexWriter.AddDocument(IEnumerable{IIndexableField})"/>
public virtual void AddDocument(IEnumerable<IIndexableField> doc)
{
AddDocument(doc, IndexWriter.Analyzer);
}
public virtual void AddDocument(IEnumerable<IIndexableField> doc, Analyzer a)
{
if (r.Next(5) == 3)
{
// TODO: maybe, we should simply buffer up added docs
// (but we need to clone them), and only when
// getReader, commit, etc. are called, we do an
// addDocuments? Would be better testing.
IndexWriter.AddDocuments(new IterableAnonymousClass<IIndexableField>(doc), a);
}
else
{
IndexWriter.AddDocument(doc, a);
}
MaybeCommit();
}
private class IterableAnonymousClass<IndexableField> : IEnumerable<IEnumerable<IndexableField>>
{
private readonly IEnumerable<IndexableField> doc;
public IterableAnonymousClass(IEnumerable<IndexableField> doc)
{
this.doc = doc;
}
public IEnumerator<IEnumerable<IndexableField>> GetEnumerator()
{
return new IteratorAnonymousClass(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class IteratorAnonymousClass : IEnumerator<IEnumerable<IndexableField>>
{
private readonly IterableAnonymousClass<IndexableField> outerInstance;
public IteratorAnonymousClass(IterableAnonymousClass<IndexableField> outerInstance)
{
this.outerInstance = outerInstance;
}
internal bool done;
private IEnumerable<IndexableField> current;
public bool MoveNext()
{
if (done)
{
return false;
}
done = true;
current = outerInstance.doc;
return true;
}
public IEnumerable<IndexableField> Current => current;
object IEnumerator.Current => Current;
public void Reset()
=> throw new NotImplementedException();
public void Dispose()
{
}
}
}
private void MaybeCommit()
{
if (docCount++ == flushAt)
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW.add/updateDocument: now doing a commit at docCount=" + docCount);
}
IndexWriter.Commit();
flushAt += TestUtil.NextInt32(r, (int)(flushAtFactor * 10), (int)(flushAtFactor * 1000));
if (flushAtFactor < 2e6)
{
// gradually but exponentially increase time b/w flushes
flushAtFactor *= 1.05;
}
}
}
public virtual void AddDocuments(IEnumerable<IEnumerable<IIndexableField>> docs)
{
IndexWriter.AddDocuments(docs);
MaybeCommit();
}
public virtual void UpdateDocuments(Term delTerm, IEnumerable<IEnumerable<IIndexableField>> docs)
{
IndexWriter.UpdateDocuments(delTerm, docs);
MaybeCommit();
}
/// <summary>
/// Updates a document. </summary>
/// <see cref="IndexWriter.UpdateDocument(Term, IEnumerable{IIndexableField})"/>
public virtual void UpdateDocument(Term t, IEnumerable<IIndexableField> doc)
{
if (r.Next(5) == 3)
{
IndexWriter.UpdateDocuments(t, new IterableAnonymousClass2(doc));
}
else
{
IndexWriter.UpdateDocument(t, doc);
}
MaybeCommit();
}
private class IterableAnonymousClass2 : IEnumerable<IEnumerable<IIndexableField>>
{
private readonly IEnumerable<IIndexableField> doc;
public IterableAnonymousClass2(IEnumerable<IIndexableField> doc)
{
this.doc = doc;
}
public IEnumerator<IEnumerable<IIndexableField>> GetEnumerator()
{
return new IteratorAnonymousClass2(this);
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
private class IteratorAnonymousClass2 : IEnumerator<IEnumerable<IIndexableField>>
{
private readonly IterableAnonymousClass2 outerInstance;
public IteratorAnonymousClass2(IterableAnonymousClass2 outerInstance)
{
this.outerInstance = outerInstance;
}
internal bool done;
private IEnumerable<IIndexableField> current;
public bool MoveNext()
{
if (done)
{
return false;
}
done = true;
current = outerInstance.doc;
return true;
}
public IEnumerable<IIndexableField> Current => current;
object IEnumerator.Current => Current;
public virtual void Reset()
=> throw new NotImplementedException();
public void Dispose()
{
}
}
}
public virtual void AddIndexes(params Directory[] dirs)
=> IndexWriter.AddIndexes(dirs);
public virtual void AddIndexes(params IndexReader[] readers)
=> IndexWriter.AddIndexes(readers);
public virtual void UpdateNumericDocValue(Term term, string field, long? value)
=> IndexWriter.UpdateNumericDocValue(term, field, value);
public virtual void UpdateBinaryDocValue(Term term, string field, BytesRef value)
=> IndexWriter.UpdateBinaryDocValue(term, field, value);
public virtual void DeleteDocuments(Term term)
=> IndexWriter.DeleteDocuments(term);
public virtual void DeleteDocuments(Query q)
=> IndexWriter.DeleteDocuments(q);
public virtual void Commit()
=> IndexWriter.Commit();
public virtual int NumDocs
=> IndexWriter.NumDocs;
public virtual int MaxDoc
=> IndexWriter.MaxDoc;
public virtual void DeleteAll()
=> IndexWriter.DeleteAll();
public virtual DirectoryReader GetReader()
=> GetReader(true);
private bool doRandomForceMerge = true;
private bool doRandomForceMergeAssert = true;
public virtual void ForceMergeDeletes(bool doWait)
=> IndexWriter.ForceMergeDeletes(doWait);
public virtual void ForceMergeDeletes()
=> IndexWriter.ForceMergeDeletes();
public virtual bool DoRandomForceMerge
{
get => doRandomForceMerge; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => doRandomForceMerge = value;
}
public virtual bool DoRandomForceMergeAssert
{
get => doRandomForceMergeAssert; // LUCENENET specific - added getter (to follow MSDN property guidelines)
set => doRandomForceMergeAssert = value;
}
#pragma warning disable IDE1006 // Naming Styles
private void _DoRandomForceMerge() // LUCENENET specific - added leading underscore to keep this from colliding with the DoRandomForceMerge property
#pragma warning restore IDE1006 // Naming Styles
{
if (doRandomForceMerge)
{
int segCount = IndexWriter.SegmentCount;
if (r.NextBoolean() || segCount == 0)
{
// full forceMerge
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW: doRandomForceMerge(1)");
}
IndexWriter.ForceMerge(1);
}
else
{
// partial forceMerge
int limit = TestUtil.NextInt32(r, 1, segCount);
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW: doRandomForceMerge(" + limit + ")");
}
IndexWriter.ForceMerge(limit);
if (Debugging.AssertsEnabled) Debugging.Assert(!doRandomForceMergeAssert || IndexWriter.SegmentCount <= limit,"limit={0} actual={1}", limit, IndexWriter.SegmentCount);
}
}
}
public virtual DirectoryReader GetReader(bool applyDeletions)
{
getReaderCalled = true;
if (r.Next(20) == 2)
{
_DoRandomForceMerge();
}
// If we are writing with PreFlexRW, force a full
// IndexReader.open so terms are sorted in codepoint
// order during searching:
if (!applyDeletions || !codec.Name.Equals("Lucene3x", StringComparison.Ordinal) && r.NextBoolean())
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW.getReader: use NRT reader");
}
if (r.Next(5) == 1)
{
IndexWriter.Commit();
}
return IndexWriter.GetReader(applyDeletions);
}
else
{
if (LuceneTestCase.Verbose)
{
Console.WriteLine("RIW.getReader: open new reader");
}
IndexWriter.Commit();
if (r.NextBoolean())
{
return DirectoryReader.Open(IndexWriter.Directory, TestUtil.NextInt32(r, 1, 10));
}
else
{
return IndexWriter.GetReader(applyDeletions);
}
}
}
// LUCENENET specific: Implemented dispose pattern
/// <summary>
/// Dispose this writer. </summary>
/// <seealso cref="IndexWriter.Dispose()"/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose this writer. </summary>
/// <seealso cref="IndexWriter.Dispose(bool)"/>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// if someone isn't using getReader() API, we want to be sure to
// forceMerge since presumably they might open a reader on the dir.
if (getReaderCalled == false && r.Next(8) == 2)
{
_DoRandomForceMerge();
}
IndexWriter.Dispose();
}
}
/// <summary>
/// Forces a forceMerge.
/// <para/>
/// NOTE: this should be avoided in tests unless absolutely necessary,
/// as it will result in less test coverage. </summary>
/// <seealso cref="IndexWriter.ForceMerge(int)"/>
public virtual void ForceMerge(int maxSegmentCount)
{
IndexWriter.ForceMerge(maxSegmentCount);
}
// LUCENENET specific - de-nested TestPointInfoStream
// LUCENENET specific - de-nested ITestPoint
}
public sealed class TestPointInfoStream : InfoStream
{
private readonly InfoStream @delegate;
private readonly ITestPoint testPoint;
public TestPointInfoStream(InfoStream @delegate, ITestPoint testPoint)
{
this.@delegate = @delegate ?? new NullInfoStream();
this.testPoint = testPoint;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
@delegate.Dispose();
}
}
public override void Message(string component, string message)
{
if ("TP".Equals(component, StringComparison.Ordinal))
{
testPoint.Apply(message);
}
if (@delegate.IsEnabled(component))
{
@delegate.Message(component, message);
}
}
public override bool IsEnabled(string component)
{
return "TP".Equals(component, StringComparison.Ordinal) || @delegate.IsEnabled(component);
}
}
/// <summary>
/// Simple interface that is executed for each <c>TP</c> <see cref="InfoStream"/> component
/// message. See also <see cref="RandomIndexWriter.MockIndexWriter(Directory, IndexWriterConfig, ITestPoint)"/>.
/// </summary>
public interface ITestPoint
{
void Apply(string message);
}
}