blob: d2208333cd69e256a27b9b9efa26fa1eebaa710d [file] [log] [blame]
using System.Collections.Concurrent;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace Lucene.Net.Store
{
using Lucene.Net.Randomized.Generators;
using Lucene.Net.Support;
using System.IO;
/*
* 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 DirectoryReader = Lucene.Net.Index.DirectoryReader;
using IndexWriter = Lucene.Net.Index.IndexWriter;
using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
using NoDeletionPolicy = Lucene.Net.Index.NoDeletionPolicy;
using SegmentInfos = Lucene.Net.Index.SegmentInfos;
using TestUtil = Lucene.Net.Util.TestUtil;
using ThrottledIndexOutput = Lucene.Net.Util.ThrottledIndexOutput;
/// <summary>
/// this is a Directory Wrapper that adds methods
/// intended to be used only by unit tests.
/// It also adds a number of features useful for testing:
/// <ul>
/// <li> Instances created by <seealso cref="LuceneTestCase#newDirectory()"/> are tracked
/// to ensure they are closed by the test.</li>
/// <li> When a MockDirectoryWrapper is closed, it will throw an exception if
/// it has any open files against it (with a stacktrace indicating where
/// they were opened from).</li>
/// <li> When a MockDirectoryWrapper is closed, it runs CheckIndex to test if
/// the index was corrupted.</li>
/// <li> MockDirectoryWrapper simulates some "features" of Windows, such as
/// refusing to write/delete to open files.</li>
/// </ul>
/// </summary>
public class MockDirectoryWrapper : BaseDirectoryWrapper
{
internal long MaxSize;
// Max actual bytes used. this is set by MockRAMOutputStream:
internal long MaxUsedSize;
internal double RandomIOExceptionRate_Renamed;
internal double RandomIOExceptionRateOnOpen_Renamed;
internal Random RandomState;
internal bool NoDeleteOpenFile_Renamed = true;
internal bool assertNoDeleteOpenFile = false;
internal bool PreventDoubleWrite_Renamed = true;
internal bool TrackDiskUsage_Renamed = false;
internal bool WrapLockFactory_Renamed = true;
internal bool AllowRandomFileNotFoundException_Renamed = true;
internal bool AllowReadingFilesStillOpenForWrite_Renamed = false;
private ISet<string> UnSyncedFiles;
private ISet<string> CreatedFiles;
private ISet<string> OpenFilesForWrite = new HashSet<string>();
internal ISet<string> OpenLocks = new ConcurrentHashSet<string>();
internal volatile bool Crashed;
private ThrottledIndexOutput ThrottledOutput;
private Throttling_e throttling = Throttling_e.SOMETIMES;
protected internal LockFactory LockFactory_Renamed;
internal readonly AtomicLong InputCloneCount_Renamed = new AtomicLong();
// use this for tracking files for crash.
// additionally: provides debugging information in case you leave one open
private readonly ConcurrentDictionary<IDisposable, Exception> OpenFileHandles = new ConcurrentDictionary<IDisposable, Exception>();
// NOTE: we cannot initialize the Map here due to the
// order in which our constructor actually does this
// member initialization vs when it calls super. It seems
// like super is called, then our members are initialized:
private IDictionary<string, int> OpenFiles;
// Only tracked if noDeleteOpenFile is true: if an attempt
// is made to delete an open file, we enroll it here.
private ISet<string> OpenFilesDeleted;
private void Init()
{
lock (this)
{
if (OpenFiles == null)
{
OpenFiles = new Dictionary<string, int>();
OpenFilesDeleted = new HashSet<string>();
}
if (CreatedFiles == null)
{
CreatedFiles = new HashSet<string>();
}
if (UnSyncedFiles == null)
{
UnSyncedFiles = new HashSet<string>();
}
}
}
public MockDirectoryWrapper(Random random, Directory @delegate)
: base(@delegate)
{
// must make a private random since our methods are
// called from different threads; else test failures may
// not be reproducible from the original seed
this.RandomState = new Random(random.Next());
this.ThrottledOutput = new ThrottledIndexOutput(ThrottledIndexOutput.MBitsToBytes(40 + RandomState.Next(10)), 5 + RandomState.Next(5), null);
// force wrapping of lockfactory
this.LockFactory_Renamed = new MockLockFactoryWrapper(this, @delegate.LockFactory);
Init();
}
public virtual int InputCloneCount
{
get
{
return (int)InputCloneCount_Renamed.Get();
}
}
public virtual bool TrackDiskUsage
{
set
{
TrackDiskUsage_Renamed = value;
}
}
/// <summary>
/// If set to true, we throw anSystem.IO.IOException if the same
/// file is opened by createOutput, ever.
/// </summary>
public virtual bool PreventDoubleWrite
{
set
{
PreventDoubleWrite_Renamed = value;
}
}
/// <summary>
/// If set to true (the default), when we throw random
/// System.IO.IOException on openInput or createOutput, we may
/// sometimes throw FileNotFoundException or
/// NoSuchFileException.
/// </summary>
public virtual bool AllowRandomFileNotFoundException
{
set
{
AllowRandomFileNotFoundException_Renamed = value;
}
}
/// <summary>
/// If set to true, you can open an inputstream on a file
/// that is still open for writes.
/// </summary>
public virtual bool AllowReadingFilesStillOpenForWrite
{
set
{
AllowReadingFilesStillOpenForWrite_Renamed = value;
}
}
/// <summary>
/// Enum for controlling hard disk throttling.
/// Set via <seealso cref="MockDirectoryWrapper #setThrottling(Throttling)"/>
/// <p>
/// WARNING: can make tests very slow.
/// </summary>
public enum Throttling_e
{
/// <summary>
/// always emulate a slow hard disk. could be very slow! </summary>
ALWAYS,
/// <summary>
/// sometimes (2% of the time) emulate a slow hard disk. </summary>
SOMETIMES,
/// <summary>
/// never throttle output </summary>
NEVER
}
public virtual Throttling_e Throttling
{
set
{
this.throttling = value;
}
}
/// <summary>
/// Returns true if <seealso cref="#in"/> must sync its files.
/// Currently, only <seealso cref="NRTCachingDirectory"/> requires sync'ing its files
/// because otherwise they are cached in an internal <seealso cref="RAMDirectory"/>. If
/// other directories require that too, they should be added to this method.
/// </summary>
private bool MustSync()
{
Directory @delegate = @in;
while (@delegate is FilterDirectory)
{
@delegate = ((FilterDirectory)@delegate).Delegate;
}
return @delegate is NRTCachingDirectory;
}
public override void Sync(ICollection<string> names)
{
lock (this)
{
MaybeYield();
MaybeThrowDeterministicException();
if (Crashed)
{
throw new System.IO.IOException("cannot sync after crash");
}
// don't wear out our hardware so much in tests.
if (LuceneTestCase.Rarely(RandomState) || MustSync())
{
foreach (string name in names)
{
// randomly fail with IOE on any file
MaybeThrowIOException(name);
@in.Sync(new[] { name });
UnSyncedFiles.Remove(name);
}
}
else
{
UnSyncedFiles.RemoveAll(names);
}
}
}
public long SizeInBytes()
{
lock (this)
{
if (@in is RAMDirectory)
{
return ((RAMDirectory)@in).SizeInBytes();
}
else
{
// hack
long size = 0;
foreach (string file in @in.ListAll())
{
size += @in.FileLength(file);
}
return size;
}
}
}
/// <summary>
/// Simulates a crash of OS or machine by overwriting
/// unsynced files.
/// </summary>
public void Crash()
{
lock (this)
{
Crashed = true;
OpenFiles = new Dictionary<string, int>();
OpenFilesForWrite = new HashSet<string>();
OpenFilesDeleted = new HashSet<string>();
IEnumerator<string> it = UnSyncedFiles.GetEnumerator();
UnSyncedFiles = new HashSet<string>();
// first force-close all files, so we can corrupt on windows etc.
// clone the file map, as these guys want to remove themselves on close.
var m = OpenFileHandles.Keys.ToArray();
foreach (IDisposable f in m)
{
try
{
f.Dispose();
}
catch (Exception)
{
}
}
while (it.MoveNext())
{
string name = it.Current;
int damage = RandomState.Next(5);
string action = null;
if (damage == 0)
{
action = "deleted";
DeleteFile(name, true);
}
else if (damage == 1)
{
action = "zeroed";
// Zero out file entirely
long length = FileLength(name);
var zeroes = new byte[256]; // LUCENENET TODO: Don't we want to fill the array before writing from it?
long upto = 0;
IndexOutput @out = @in.CreateOutput(name, LuceneTestCase.NewIOContext(RandomState));
while (upto < length)
{
var limit = (int)Math.Min(length - upto, zeroes.Length);
@out.WriteBytes(zeroes, 0, limit);
upto += limit;
}
@out.Dispose();
}
else if (damage == 2)
{
action = "partially truncated";
// Partially Truncate the file:
// First, make temp file and copy only half this
// file over:
string tempFileName;
while (true)
{
tempFileName = "" + RandomState.Next();
if (!LuceneTestCase.SlowFileExists(@in, tempFileName))
{
break;
}
}
IndexOutput tempOut = @in.CreateOutput(tempFileName, LuceneTestCase.NewIOContext(RandomState));
IndexInput ii = @in.OpenInput(name, LuceneTestCase.NewIOContext(RandomState));
tempOut.CopyBytes(ii, ii.Length() / 2);
tempOut.Dispose();
ii.Dispose();
// Delete original and copy bytes back:
DeleteFile(name, true);
IndexOutput @out = @in.CreateOutput(name, LuceneTestCase.NewIOContext(RandomState));
ii = @in.OpenInput(tempFileName, LuceneTestCase.NewIOContext(RandomState));
@out.CopyBytes(ii, ii.Length());
@out.Dispose();
ii.Dispose();
DeleteFile(tempFileName, true);
}
else if (damage == 3)
{
// The file survived intact:
action = "didn't change";
}
else
{
action = "fully truncated";
// Totally truncate the file to zero bytes
DeleteFile(name, true);
IndexOutput @out = @in.CreateOutput(name, LuceneTestCase.NewIOContext(RandomState));
@out.Length = 0;
@out.Dispose();
}
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MockDirectoryWrapper: " + action + " unsynced file: " + name);
}
}
}
}
public virtual void ClearCrash()
{
lock (this)
{
Crashed = false;
OpenLocks.Clear();
}
}
public virtual long MaxSizeInBytes
{
set
{
this.MaxSize = value;
}
get
{
return this.MaxSize;
}
}
/// <summary>
/// Returns the peek actual storage used (bytes) in this
/// directory.
/// </summary>
public virtual long MaxUsedSizeInBytes
{
get
{
return this.MaxUsedSize;
}
}
public virtual void ResetMaxUsedSizeInBytes()
{
this.MaxUsedSize = RecomputedActualSizeInBytes;
}
/// <summary>
/// Emulate windows whereby deleting an open file is not
/// allowed (raiseSystem.IO.IOException).
/// </summary>
public virtual bool NoDeleteOpenFile
{
set
{
this.NoDeleteOpenFile_Renamed = value;
}
get
{
return NoDeleteOpenFile_Renamed;
}
}
/// <summary>
/// Trip a test assert if there is an attempt
/// to delete an open file.
/// </summary>
public virtual bool AssertNoDeleteOpenFile
{
set
{
this.assertNoDeleteOpenFile = value;
}
get
{
return assertNoDeleteOpenFile;
}
}
/// <summary>
/// If 0.0, no exceptions will be thrown. Else this should
/// be a double 0.0 - 1.0. We will randomly throw an
///System.IO.IOException on the first write to an OutputStream based
/// on this probability.
/// </summary>
public virtual double RandomIOExceptionRate
{
set
{
RandomIOExceptionRate_Renamed = value;
}
get
{
return RandomIOExceptionRate_Renamed;
}
}
/// <summary>
/// If 0.0, no exceptions will be thrown during openInput
/// and createOutput. Else this should
/// be a double 0.0 - 1.0 and we will randomly throw an
///System.IO.IOException in openInput and createOutput with
/// this probability.
/// </summary>
public virtual double RandomIOExceptionRateOnOpen
{
set
{
RandomIOExceptionRateOnOpen_Renamed = value;
}
get
{
return RandomIOExceptionRateOnOpen_Renamed;
}
}
internal virtual void MaybeThrowIOException(string message)
{
if (RandomState.NextDouble() < RandomIOExceptionRate_Renamed)
{
/*if (LuceneTestCase.VERBOSE)
{
Console.WriteLine(Thread.CurrentThread.Name + ": MockDirectoryWrapper: now throw random exception" + (message == null ? "" : " (" + message + ")"));
(new Exception()).printStackTrace(System.out);
}*/
throw new System.IO.IOException("a randomSystem.IO.IOException" + (message == null ? "" : " (" + message + ")"));
}
}
internal virtual void MaybeThrowIOExceptionOnOpen(string name)
{
if (RandomState.NextDouble() < RandomIOExceptionRateOnOpen_Renamed)
{
/*if (LuceneTestCase.VERBOSE)
{
Console.WriteLine(Thread.CurrentThread.Name + ": MockDirectoryWrapper: now throw random exception during open file=" + name);
(new Exception()).printStackTrace(System.out);
}*/
if (AllowRandomFileNotFoundException_Renamed == false || RandomState.NextBoolean())
{
throw new System.IO.IOException("a randomSystem.IO.IOException (" + name + ")");
}
else
{
throw RandomState.NextBoolean() ? new FileNotFoundException("a randomSystem.IO.IOException (" + name + ")") : new FileNotFoundException("a randomSystem.IO.IOException (" + name + ")");
}
}
}
public override void DeleteFile(string name)
{
lock (this)
{
MaybeYield();
DeleteFile(name, false);
}
}
// if there are any exceptions in OpenFileHandles
// capture those as inner exceptions
private Exception WithAdditionalErrorInformation(Exception t, string name, bool input)
{
lock (this)
{
foreach (var ent in OpenFileHandles)
{
if (input && ent.Key is MockIndexInputWrapper && ((MockIndexInputWrapper)ent.Key).Name.Equals(name))
{
t = CreateException(t, ent.Value);
break;
}
else if (!input && ent.Key is MockIndexOutputWrapper && ((MockIndexOutputWrapper)ent.Key).Name.Equals(name))
{
t = CreateException(t, ent.Value);
break;
}
}
return t;
}
}
private Exception CreateException(Exception exception, Exception innerException)
{
return (Exception)Activator.CreateInstance(exception.GetType(), exception.Message, innerException);
}
private void MaybeYield()
{
if (RandomState.NextBoolean())
{
Thread.@Yield();
}
}
private void DeleteFile(string name, bool forced)
{
lock (this)
{
MaybeYield();
MaybeThrowDeterministicException();
if (Crashed && !forced)
{
throw new System.IO.IOException("cannot delete after crash");
}
if (UnSyncedFiles.Contains(name))
{
UnSyncedFiles.Remove(name);
}
if (!forced && (NoDeleteOpenFile_Renamed || assertNoDeleteOpenFile))
{
if (OpenFiles.ContainsKey(name))
{
OpenFilesDeleted.Add(name);
if (!assertNoDeleteOpenFile)
{
throw WithAdditionalErrorInformation(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot delete"), name, true);
}
else
{
throw WithAdditionalErrorInformation(new AssertionException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot delete"), name, true);
}
}
else
{
OpenFilesDeleted.Remove(name);
}
}
@in.DeleteFile(name);
}
}
public virtual ISet<string> OpenDeletedFiles
{
get
{
lock (this)
{
return new HashSet<string>(OpenFilesDeleted);
}
}
}
private bool FailOnCreateOutput_Renamed = true;
public virtual bool FailOnCreateOutput
{
set
{
FailOnCreateOutput_Renamed = value;
}
}
public override IndexOutput CreateOutput(string name, IOContext context)
{
lock (this)
{
MaybeThrowDeterministicException();
MaybeThrowIOExceptionOnOpen(name);
MaybeYield();
if (FailOnCreateOutput_Renamed)
{
MaybeThrowDeterministicException();
}
if (Crashed)
{
throw new System.IO.IOException("cannot createOutput after crash");
}
Init();
lock (this)
{
if (PreventDoubleWrite_Renamed && CreatedFiles.Contains(name) && !name.Equals("segments.gen"))
{
throw new System.IO.IOException("file \"" + name + "\" was already written to");
}
}
if ((NoDeleteOpenFile_Renamed || assertNoDeleteOpenFile) && OpenFiles.ContainsKey(name))
{
if (!assertNoDeleteOpenFile)
{
throw new System.IO.IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
}
else
{
throw new InvalidOperationException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
}
}
if (Crashed)
{
throw new System.IO.IOException("cannot createOutput after crash");
}
UnSyncedFiles.Add(name);
CreatedFiles.Add(name);
if (@in is RAMDirectory)
{
RAMDirectory ramdir = (RAMDirectory)@in;
RAMFile file = new RAMFile(ramdir);
RAMFile existing = ramdir.GetNameFromFileMap_Nunit(name);
// Enforce write once:
if (existing != null && !name.Equals("segments.gen") && PreventDoubleWrite_Renamed)
{
throw new System.IO.IOException("file " + name + " already exists");
}
else
{
if (existing != null)
{
ramdir.GetAndAddSizeInBytes_Nunit(-existing.SizeInBytes);
existing.SetDirectory_Nunit(null);
}
ramdir.SetNameForFileMap_Nunit(name, file);
}
}
//System.out.println(Thread.currentThread().getName() + ": MDW: create " + name);
IndexOutput delegateOutput = @in.CreateOutput(name, LuceneTestCase.NewIOContext(RandomState, context));
if (RandomState.Next(10) == 0)
{
// once in a while wrap the IO in a Buffered IO with random buffer sizes
delegateOutput = new BufferedIndexOutputWrapper(this, 1 + RandomState.Next(BufferedIndexOutput.DEFAULT_BUFFER_SIZE), delegateOutput);
}
IndexOutput io = new MockIndexOutputWrapper(this, delegateOutput, name);
AddFileHandle(io, name, Handle.Output);
OpenFilesForWrite.Add(name);
// throttling REALLY slows down tests, so don't do it very often for SOMETIMES.
if (throttling == Throttling_e.ALWAYS || (throttling == Throttling_e.SOMETIMES && RandomState.Next(50) == 0) && !(@in is RateLimitedDirectoryWrapper))
{
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MockDirectoryWrapper: throttling indexOutput (" + name + ")");
}
return ThrottledOutput.NewFromDelegate(io);
}
else
{
return io;
}
}
}
internal enum Handle
{
Input,
Output,
Slice
}
internal void AddFileHandle(IDisposable c, string name, Handle handle)
{
//Trace.TraceInformation("Add {0} {1}", c, name);
lock (this)
{
int v;
if (OpenFiles.TryGetValue(name, out v))
{
v++;
OpenFiles[name] = v;
}
else
{
OpenFiles[name] = 1;
OpenFileHandles[c] = new Exception("unclosed Index" + handle.ToString() + ": " + name);
}
}
}
private bool FailOnOpenInput_Renamed = true;
public virtual bool FailOnOpenInput
{
set
{
FailOnOpenInput_Renamed = value;
}
}
public override IndexInput OpenInput(string name, IOContext context)
{
lock (this)
{
MaybeThrowDeterministicException();
MaybeThrowIOExceptionOnOpen(name);
MaybeYield();
if (FailOnOpenInput_Renamed)
{
MaybeThrowDeterministicException();
}
if (!LuceneTestCase.SlowFileExists(@in, name))
{
throw new FileNotFoundException(name + " in dir=" + @in);
}
// cannot open a file for input if it's still open for
// output, except for segments.gen and segments_N
if (!AllowReadingFilesStillOpenForWrite_Renamed && OpenFilesForWrite.Contains(name) && !name.StartsWith("segments"))
{
throw WithAdditionalErrorInformation(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false);
}
IndexInput delegateInput = @in.OpenInput(name, LuceneTestCase.NewIOContext(RandomState, context));
IndexInput ii;
int randomInt = RandomState.Next(500);
if (randomInt == 0)
{
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MockDirectoryWrapper: using SlowClosingMockIndexInputWrapper for file " + name);
}
ii = new SlowClosingMockIndexInputWrapper(this, name, delegateInput);
}
else if (randomInt == 1)
{
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MockDirectoryWrapper: using SlowOpeningMockIndexInputWrapper for file " + name);
}
ii = new SlowOpeningMockIndexInputWrapper(this, name, delegateInput);
}
else
{
ii = new MockIndexInputWrapper(this, name, delegateInput);
}
AddFileHandle(ii, name, Handle.Input);
return ii;
}
}
/// <summary>
/// Provided for testing purposes. Use sizeInBytes() instead. </summary>
public long RecomputedSizeInBytes
{
get
{
lock (this)
{
if (!(@in is RAMDirectory))
{
return SizeInBytes();
}
long size = 0;
foreach (RAMFile file in ((RAMDirectory)@in).GetFileMapValues_Nunit())
{
size += file.SizeInBytes;
}
return size;
}
}
}
/// <summary>
/// Like getRecomputedSizeInBytes(), but, uses actual file
/// lengths rather than buffer allocations (which are
/// quantized up to nearest
/// RAMOutputStream.BUFFER_SIZE (now 1024) bytes.
/// </summary>
public long RecomputedActualSizeInBytes
{
get
{
lock (this)
{
if (!(@in is RAMDirectory))
{
return SizeInBytes();
}
long size = 0;
foreach (RAMFile file in ((RAMDirectory)@in).GetFileMapValues_Nunit())
{
size += file.Length;
}
return size;
}
}
}
// NOTE: this is off by default; see LUCENE-5574
private bool AssertNoUnreferencedFilesOnClose;
public virtual bool AssertNoUnrefencedFilesOnClose
{
set
{
AssertNoUnreferencedFilesOnClose = value;
}
}
/// <summary>
/// Set to false if you want to return the pure lockfactory
/// and not wrap it with MockLockFactoryWrapper.
/// <p>
/// Be careful if you turn this off: MockDirectoryWrapper might
/// no longer be able to detect if you forget to close an IndexWriter,
/// and spit out horribly scary confusing exceptions instead of
/// simply telling you that.
/// </summary>
public virtual bool WrapLockFactory
{
set
{
this.WrapLockFactory_Renamed = value;
}
}
public override void Dispose()
{
lock (this)
{
// files that we tried to delete, but couldn't because readers were open.
// all that matters is that we tried! (they will eventually go away)
ISet<string> pendingDeletions = new HashSet<string>(OpenFilesDeleted);
MaybeYield();
if (OpenFiles == null)
{
OpenFiles = new Dictionary<string, int>();
OpenFilesDeleted = new HashSet<string>();
}
if (OpenFiles.Count > 0)
{
// print the first one as its very verbose otherwise
Exception cause = null;
IEnumerator<Exception> stacktraces = OpenFileHandles.Values.GetEnumerator();
if (stacktraces.MoveNext())
{
cause = stacktraces.Current;
}
// RuntimeException instead ofSystem.IO.IOException because
// super() does not throwSystem.IO.IOException currently:
throw new Exception("MockDirectoryWrapper: cannot close: there are still open files: "
+ String.Join(" ,", OpenFiles.ToArray().Select(x => x.Key)), cause);
}
if (OpenLocks.Count > 0)
{
throw new Exception("MockDirectoryWrapper: cannot close: there are still open locks: "
+ String.Join(" ,", OpenLocks.ToArray()));
}
IsOpen = false;
if (CheckIndexOnClose)
{
RandomIOExceptionRate_Renamed = 0.0;
RandomIOExceptionRateOnOpen_Renamed = 0.0;
if (DirectoryReader.IndexExists(this))
{
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("\nNOTE: MockDirectoryWrapper: now crush");
}
Crash(); // corrupt any unsynced-files
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("\nNOTE: MockDirectoryWrapper: now run CheckIndex");
}
TestUtil.CheckIndex(this, CrossCheckTermVectorsOnClose);
// TODO: factor this out / share w/ TestIW.assertNoUnreferencedFiles
if (AssertNoUnreferencedFilesOnClose)
{
// now look for unreferenced files: discount ones that we tried to delete but could not
HashSet<string> allFiles = new HashSet<string>(Arrays.AsList(ListAll()));
allFiles.RemoveAll(pendingDeletions);
string[] startFiles = allFiles.ToArray(/*new string[0]*/);
IndexWriterConfig iwc = new IndexWriterConfig(LuceneTestCase.TEST_VERSION_CURRENT, null);
iwc.SetIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
(new IndexWriter(@in, iwc)).Rollback();
string[] endFiles = @in.ListAll();
ISet<string> startSet = new SortedSet<string>(Arrays.AsList(startFiles));
ISet<string> endSet = new SortedSet<string>(Arrays.AsList(endFiles));
if (pendingDeletions.Contains("segments.gen") && endSet.Contains("segments.gen"))
{
// this is possible if we hit an exception while writing segments.gen, we try to delete it
// and it ends out in pendingDeletions (but IFD wont remove this).
startSet.Add("segments.gen");
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring segments.gen that we could not delete.");
}
}
// its possible we cannot delete the segments_N on windows if someone has it open and
// maybe other files too, depending on timing. normally someone on windows wouldnt have
// an issue (IFD would nuke this stuff eventually), but we pass NoDeletionPolicy...
foreach (string file in pendingDeletions)
{
if (file.StartsWith("segments") && !file.Equals("segments.gen") && endSet.Contains(file))
{
startSet.Add(file);
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring segments file: " + file + " that we could not delete.");
}
SegmentInfos sis = new SegmentInfos();
try
{
sis.Read(@in, file);
}
catch (System.IO.IOException ioe)
{
// OK: likely some of the .si files were deleted
}
try
{
ISet<string> ghosts = new HashSet<string>(sis.Files(@in, false));
foreach (string s in ghosts)
{
if (endSet.Contains(s) && !startSet.Contains(s))
{
Debug.Assert(pendingDeletions.Contains(s));
if (LuceneTestCase.VERBOSE)
{
Console.WriteLine("MDW: Unreferenced check: Ignoring referenced file: " + s + " " + "from " + file + " that we could not delete.");
}
startSet.Add(s);
}
}
}
catch (Exception t)
{
Console.Error.WriteLine("ERROR processing leftover segments file " + file + ":");
Console.WriteLine(t.ToString());
Console.Write(t.StackTrace);
}
}
}
startFiles = startSet.ToArray(/*new string[0]*/);
endFiles = endSet.ToArray(/*new string[0]*/);
if (!Arrays.Equals(startFiles, endFiles))
{
IList<string> removed = new List<string>();
foreach (string fileName in startFiles)
{
if (!endSet.Contains(fileName))
{
removed.Add(fileName);
}
}
IList<string> added = new List<string>();
foreach (string fileName in endFiles)
{
if (!startSet.Contains(fileName))
{
added.Add(fileName);
}
}
string extras;
if (removed.Count != 0)
{
extras = "\n\nThese files were removed: " + removed;
}
else
{
extras = "";
}
if (added.Count != 0)
{
extras += "\n\nThese files were added (waaaaaaaaaat!): " + added;
}
if (pendingDeletions.Count != 0)
{
extras += "\n\nThese files we had previously tried to delete, but couldn't: " + pendingDeletions;
}
Debug.Assert(false, "unreferenced files: before delete:\n " + Arrays.ToString(startFiles) + "\n after delete:\n " + Arrays.ToString(endFiles) + extras);
}
DirectoryReader ir1 = DirectoryReader.Open(this);
int numDocs1 = ir1.NumDocs;
ir1.Dispose();
(new IndexWriter(this, new IndexWriterConfig(LuceneTestCase.TEST_VERSION_CURRENT, null))).Dispose();
DirectoryReader ir2 = DirectoryReader.Open(this);
int numDocs2 = ir2.NumDocs;
ir2.Dispose();
Debug.Assert(numDocs1 == numDocs2, "numDocs changed after opening/closing IW: before=" + numDocs1 + " after=" + numDocs2);
}
}
}
@in.Dispose();
}
}
internal virtual void RemoveOpenFile(IDisposable c, string name)
{
//Trace.TraceInformation("Rem {0} {1}", c, name);
lock (this)
{
int v;
if (OpenFiles.TryGetValue(name, out v))
{
if (v == 1)
{
OpenFiles.Remove(name);
Exception _;
OpenFileHandles.TryRemove(c, out _);
}
else
{
v--;
OpenFiles[name] = v;
}
}
}
}
public virtual void RemoveIndexOutput(IndexOutput @out, string name)
{
lock (this)
{
OpenFilesForWrite.Remove(name);
RemoveOpenFile(@out, name);
}
}
public virtual void RemoveIndexInput(IndexInput @in, string name)
{
lock (this)
{
RemoveOpenFile(@in, name);
}
}
/// <summary>
/// Objects that represent fail-able conditions. Objects of a derived
/// class are created and registered with the mock directory. After
/// register, each object will be invoked once for each first write
/// of a file, giving the object a chance to throw anSystem.IO.IOException.
/// </summary>
public class Failure
{
/// <summary>
/// eval is called on the first write of every new file.
/// </summary>
public virtual void Eval(MockDirectoryWrapper dir)
{
}
/// <summary>
/// reset should set the state of the failure to its default
/// (freshly constructed) state. Reset is convenient for tests
/// that want to create one failure object and then reuse it in
/// multiple cases. this, combined with the fact that Failure
/// subclasses are often anonymous classes makes reset difficult to
/// do otherwise.
///
/// A typical example of use is
/// Failure failure = new Failure() { ... };
/// ...
/// mock.failOn(failure.reset())
/// </summary>
public virtual Failure Reset()
{
return this;
}
protected internal bool DoFail;
public virtual void SetDoFail()
{
DoFail = true;
}
public virtual void ClearDoFail()
{
DoFail = false;
}
}
internal List<Failure> Failures;
/// <summary>
/// add a Failure object to the list of objects to be evaluated
/// at every potential failure point
/// </summary>
public virtual void FailOn(Failure fail)
{
lock (this)
{
if (Failures == null)
{
Failures = new List<Failure>();
}
Failures.Add(fail);
}
}
/// <summary>
/// Iterate through the failures list, giving each object a
/// chance to throw an IOE
/// </summary>
internal virtual void MaybeThrowDeterministicException()
{
lock (this)
{
if (Failures != null)
{
for (int i = 0; i < Failures.Count; i++)
{
Failures[i].Eval(this);
}
}
}
}
public override string[] ListAll()
{
lock (this)
{
MaybeYield();
return @in.ListAll();
}
}
public override bool FileExists(string name)
{
lock (this)
{
MaybeYield();
return @in.FileExists(name);
}
}
public override long FileLength(string name)
{
lock (this)
{
MaybeYield();
return @in.FileLength(name);
}
}
public override Lock MakeLock(string name)
{
lock (this)
{
MaybeYield();
return LockFactory.MakeLock(name);
}
}
public override void ClearLock(string name)
{
lock (this)
{
MaybeYield();
LockFactory.ClearLock(name);
}
}
public override LockFactory LockFactory
{
set
{
lock (this)
{
MaybeYield();
// sneaky: we must pass the original this way to the dir, because
// some impls (e.g. FSDir) do instanceof here.
@in.LockFactory = value;
// now set our wrapped factory here
this.LockFactory_Renamed = new MockLockFactoryWrapper(this, value);
}
}
get
{
lock (this)
{
MaybeYield();
if (WrapLockFactory_Renamed)
{
return LockFactory_Renamed;
}
else
{
return @in.LockFactory;
}
}
}
}
public override string LockID
{
get
{
lock (this)
{
MaybeYield();
return @in.LockID;
}
}
}
public override void Copy(Directory to, string src, string dest, IOContext context)
{
lock (this)
{
MaybeYield();
// randomize the IOContext here?
@in.Copy(to, src, dest, context);
}
}
public override IndexInputSlicer CreateSlicer(string name, IOContext context)
{
MaybeYield();
if (!LuceneTestCase.SlowFileExists(@in, name))
{
throw RandomState.NextBoolean() ? new FileNotFoundException(name) : new FileNotFoundException(name);
}
// cannot open a file for input if it's still open for
// output, except for segments.gen and segments_N
if (OpenFilesForWrite.Contains(name) && !name.StartsWith("segments"))
{
throw WithAdditionalErrorInformation(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false);
}
IndexInputSlicer delegateHandle = @in.CreateSlicer(name, context);
IndexInputSlicer handle = new IndexInputSlicerAnonymousInnerClassHelper(this, name, delegateHandle);
AddFileHandle(handle, name, Handle.Slice);
return handle;
}
private class IndexInputSlicerAnonymousInnerClassHelper : IndexInputSlicer
{
private readonly MockDirectoryWrapper OuterInstance;
private string Name;
private IndexInputSlicer DelegateHandle;
public IndexInputSlicerAnonymousInnerClassHelper(MockDirectoryWrapper outerInstance, string name, IndexInputSlicer delegateHandle)
: base(outerInstance)
{
this.OuterInstance = outerInstance;
this.Name = name;
this.DelegateHandle = delegateHandle;
}
private int disposed = 0;
public override void Dispose(bool disposing)
{
if (0 == Interlocked.CompareExchange(ref this.disposed, 1, 0))
{
if (disposing)
{
DelegateHandle.Dispose();
OuterInstance.RemoveOpenFile(this, Name);
}
}
}
public override IndexInput OpenSlice(string sliceDescription, long offset, long length)
{
OuterInstance.MaybeYield();
IndexInput ii = new MockIndexInputWrapper(OuterInstance, Name, DelegateHandle.OpenSlice(sliceDescription, offset, length));
OuterInstance.AddFileHandle(ii, Name, Handle.Input);
return ii;
}
public override IndexInput OpenFullSlice()
{
OuterInstance.MaybeYield();
IndexInput ii = new MockIndexInputWrapper(OuterInstance, Name, DelegateHandle.OpenFullSlice());
OuterInstance.AddFileHandle(ii, Name, Handle.Input);
return ii;
}
}
internal sealed class BufferedIndexOutputWrapper : BufferedIndexOutput
{
private readonly MockDirectoryWrapper OuterInstance;
internal readonly IndexOutput Io;
public BufferedIndexOutputWrapper(MockDirectoryWrapper outerInstance, int bufferSize, IndexOutput io)
: base(bufferSize)
{
this.OuterInstance = outerInstance;
this.Io = io;
}
public override long Length
{
get
{
return Io.Length;
}
}
protected override void FlushBuffer(byte[] b, int offset, int len)
{
Io.WriteBytes(b, offset, len);
}
public override void Seek(long pos)
{
Flush();
Io.Seek(pos);
}
public override void Flush()
{
try
{
base.Flush();
}
finally
{
Io.Flush();
}
}
public override void Dispose()
{
try
{
base.Dispose();
}
finally
{
Io.Dispose();
}
}
}
/// <summary>
/// Use this when throwing fake {@codeSystem.IO.IOException},
/// e.g. from <seealso cref="MockDirectoryWrapper.Failure"/>.
/// </summary>
public class FakeIOException : System.IO.IOException
{
}
}
}