| 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 |
| { |
| } |
| } |
| } |