1 package org.apache.lucene.store;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.io.Closeable;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.IdentityHashMap;
29 import java.util.Iterator;
31 import java.util.Random;
33 import java.util.concurrent.atomic.AtomicInteger;
35 import org.apache.lucene.index.IndexReader;
36 import org.apache.lucene.util.LuceneTestCase;
37 import org.apache.lucene.util.ThrottledIndexOutput;
38 import org.apache.lucene.util._TestUtil;
41 * This is a Directory Wrapper that adds methods
42 * intended to be used only by unit tests.
43 * It also adds a number of features useful for testing:
45 * <li> Instances created by {@link LuceneTestCase#newDirectory()} are tracked
46 * to ensure they are closed by the test.
47 * <li> When a MockDirectoryWrapper is closed, it will throw an exception if
48 * it has any open files against it (with a stacktrace indicating where
49 * they were opened from).
50 * <li> When a MockDirectoryWrapper is closed, it runs CheckIndex to test if
51 * the index was corrupted.
52 * <li> MockDirectoryWrapper simulates some "features" of Windows, such as
53 * refusing to write/delete to open files.
57 public class MockDirectoryWrapper extends Directory {
58 final Directory delegate;
61 // Max actual bytes used. This is set by MockRAMOutputStream:
63 double randomIOExceptionRate;
65 boolean noDeleteOpenFile = true;
66 boolean preventDoubleWrite = true;
67 boolean checkIndexOnClose = true;
68 boolean trackDiskUsage = false;
69 private Set<String> unSyncedFiles;
70 private Set<String> createdFiles;
71 private Set<String> openFilesForWrite = new HashSet<String>();
72 Set<String> openLocks = Collections.synchronizedSet(new HashSet<String>());
73 volatile boolean crashed;
74 private ThrottledIndexOutput throttledOutput;
75 private Throttling throttling = Throttling.SOMETIMES;
77 final AtomicInteger inputCloneCount = new AtomicInteger();
79 // use this for tracking files for crash.
80 // additionally: provides debugging information in case you leave one open
81 private Map<Closeable,Exception> openFileHandles = Collections.synchronizedMap(new IdentityHashMap<Closeable,Exception>());
83 // NOTE: we cannot initialize the Map here due to the
84 // order in which our constructor actually does this
85 // member initialization vs when it calls super. It seems
86 // like super is called, then our members are initialized:
87 private Map<String,Integer> openFiles;
89 // Only tracked if noDeleteOpenFile is true: if an attempt
90 // is made to delete an open file, we enroll it here.
91 private Set<String> openFilesDeleted;
93 private synchronized void init() {
94 if (openFiles == null) {
95 openFiles = new HashMap<String,Integer>();
96 openFilesDeleted = new HashSet<String>();
99 if (createdFiles == null)
100 createdFiles = new HashSet<String>();
101 if (unSyncedFiles == null)
102 unSyncedFiles = new HashSet<String>();
105 public MockDirectoryWrapper(Random random, Directory delegate) {
106 this.delegate = delegate;
107 // must make a private random since our methods are
108 // called from different threads; else test failures may
109 // not be reproducible from the original seed
110 this.randomState = new Random(random.nextInt());
111 this.throttledOutput = new ThrottledIndexOutput(ThrottledIndexOutput
112 .mBitsToBytes(40 + randomState.nextInt(10)), 5 + randomState.nextInt(5), null);
113 // force wrapping of lockfactory
115 setLockFactory(new MockLockFactoryWrapper(this, delegate.getLockFactory()));
116 } catch (IOException e) {
117 throw new RuntimeException(e);
122 public int getInputCloneCount() {
123 return inputCloneCount.get();
126 public void setTrackDiskUsage(boolean v) {
130 /** If set to true, we throw an IOException if the same
131 * file is opened by createOutput, ever. */
132 public void setPreventDoubleWrite(boolean value) {
133 preventDoubleWrite = value;
138 public void sync(String name) throws IOException {
140 maybeThrowDeterministicException();
142 throw new IOException("cannot sync after crash");
143 unSyncedFiles.remove(name);
147 public static enum Throttling {
148 /** always emulate a slow hard disk. could be very slow! */
150 /** sometimes (2% of the time) emulate a slow hard disk. */
152 /** never throttle output */
156 public void setThrottling(Throttling throttling) {
157 this.throttling = throttling;
161 public synchronized void sync(Collection<String> names) throws IOException {
163 maybeThrowDeterministicException();
165 throw new IOException("cannot sync after crash");
166 unSyncedFiles.removeAll(names);
167 delegate.sync(names);
171 public String toString() {
172 // NOTE: do not maybeYield here, since it consumes
173 // randomness and can thus (unexpectedly during
174 // debugging) change the behavior of a seed
176 return "MockDirWrapper(" + delegate + ")";
179 public synchronized final long sizeInBytes() throws IOException {
180 if (delegate instanceof RAMDirectory)
181 return ((RAMDirectory) delegate).sizeInBytes();
185 for (String file : delegate.listAll())
186 size += delegate.fileLength(file);
191 /** Simulates a crash of OS or machine by overwriting
193 public synchronized void crash() throws IOException {
195 openFiles = new HashMap<String,Integer>();
196 openFilesForWrite = new HashSet<String>();
197 openFilesDeleted = new HashSet<String>();
198 Iterator<String> it = unSyncedFiles.iterator();
199 unSyncedFiles = new HashSet<String>();
200 // first force-close all files, so we can corrupt on windows etc.
201 // clone the file map, as these guys want to remove themselves on close.
202 Map<Closeable,Exception> m = new IdentityHashMap<Closeable,Exception>(openFileHandles);
203 for (Closeable f : m.keySet())
206 } catch (Exception ignored) {}
209 while(it.hasNext()) {
210 String name = it.next();
211 if (count % 3 == 0) {
212 deleteFile(name, true);
213 } else if (count % 3 == 1) {
214 // Zero out file entirely
215 long length = fileLength(name);
216 byte[] zeroes = new byte[256];
218 IndexOutput out = delegate.createOutput(name);
219 while(upto < length) {
220 final int limit = (int) Math.min(length-upto, zeroes.length);
221 out.writeBytes(zeroes, 0, limit);
225 } else if (count % 3 == 2) {
226 // Truncate the file:
227 IndexOutput out = delegate.createOutput(name);
228 out.setLength(fileLength(name)/2);
235 public synchronized void clearCrash() throws IOException {
240 public void setMaxSizeInBytes(long maxSize) {
241 this.maxSize = maxSize;
243 public long getMaxSizeInBytes() {
248 * Returns the peek actual storage used (bytes) in this
251 public long getMaxUsedSizeInBytes() {
252 return this.maxUsedSize;
254 public void resetMaxUsedSizeInBytes() throws IOException {
255 this.maxUsedSize = getRecomputedActualSizeInBytes();
259 * Emulate windows whereby deleting an open file is not
260 * allowed (raise IOException).
262 public void setNoDeleteOpenFile(boolean value) {
263 this.noDeleteOpenFile = value;
265 public boolean getNoDeleteOpenFile() {
266 return noDeleteOpenFile;
270 * Set whether or not checkindex should be run
273 public void setCheckIndexOnClose(boolean value) {
274 this.checkIndexOnClose = value;
277 public boolean getCheckIndexOnClose() {
278 return checkIndexOnClose;
281 * If 0.0, no exceptions will be thrown. Else this should
282 * be a double 0.0 - 1.0. We will randomly throw an
283 * IOException on the first write to an OutputStream based
284 * on this probability.
286 public void setRandomIOExceptionRate(double rate) {
287 randomIOExceptionRate = rate;
289 public double getRandomIOExceptionRate() {
290 return randomIOExceptionRate;
293 void maybeThrowIOException() throws IOException {
294 if (randomIOExceptionRate > 0.0) {
295 int number = Math.abs(randomState.nextInt() % 1000);
296 if (number < randomIOExceptionRate*1000) {
297 if (LuceneTestCase.VERBOSE) {
298 System.out.println(Thread.currentThread().getName() + ": MockDirectoryWrapper: now throw random exception");
299 new Throwable().printStackTrace(System.out);
301 throw new IOException("a random IOException");
307 public synchronized void deleteFile(String name) throws IOException {
309 deleteFile(name, false);
312 // sets the cause of the incoming ioe to be the stack
313 // trace when the offending file name was opened
314 private synchronized IOException fillOpenTrace(IOException ioe, String name, boolean input) {
315 for(Map.Entry<Closeable,Exception> ent : openFileHandles.entrySet()) {
316 if (input && ent.getKey() instanceof MockIndexInputWrapper && ((MockIndexInputWrapper) ent.getKey()).name.equals(name)) {
317 ioe.initCause(ent.getValue());
319 } else if (!input && ent.getKey() instanceof MockIndexOutputWrapper && ((MockIndexOutputWrapper) ent.getKey()).name.equals(name)) {
320 ioe.initCause(ent.getValue());
327 private void maybeYield() {
328 if (randomState.nextBoolean()) {
333 private synchronized void deleteFile(String name, boolean forced) throws IOException {
336 maybeThrowDeterministicException();
338 if (crashed && !forced)
339 throw new IOException("cannot delete after crash");
341 if (unSyncedFiles.contains(name))
342 unSyncedFiles.remove(name);
343 if (!forced && noDeleteOpenFile) {
344 if (openFiles.containsKey(name)) {
345 openFilesDeleted.add(name);
346 throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot delete"), name, true);
348 openFilesDeleted.remove(name);
351 delegate.deleteFile(name);
354 public synchronized Set<String> getOpenDeletedFiles() {
355 return new HashSet<String>(openFilesDeleted);
358 private boolean failOnCreateOutput = true;
360 public void setFailOnCreateOutput(boolean v) {
361 failOnCreateOutput = v;
365 public synchronized IndexOutput createOutput(String name) throws IOException {
367 if (failOnCreateOutput) {
368 maybeThrowDeterministicException();
371 throw new IOException("cannot createOutput after crash");
374 if (preventDoubleWrite && createdFiles.contains(name) && !name.equals("segments.gen"))
375 throw new IOException("file \"" + name + "\" was already written to");
377 if (noDeleteOpenFile && openFiles.containsKey(name))
378 throw new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
381 throw new IOException("cannot createOutput after crash");
382 unSyncedFiles.add(name);
383 createdFiles.add(name);
385 if (delegate instanceof RAMDirectory) {
386 RAMDirectory ramdir = (RAMDirectory) delegate;
387 RAMFile file = new RAMFile(ramdir);
388 RAMFile existing = ramdir.fileMap.get(name);
390 // Enforce write once:
391 if (existing!=null && !name.equals("segments.gen") && preventDoubleWrite)
392 throw new IOException("file " + name + " already exists");
394 if (existing!=null) {
395 ramdir.sizeInBytes.getAndAdd(-existing.sizeInBytes);
396 existing.directory = null;
398 ramdir.fileMap.put(name, file);
402 //System.out.println(Thread.currentThread().getName() + ": MDW: create " + name);
403 IndexOutput io = new MockIndexOutputWrapper(this, delegate.createOutput(name), name);
404 addFileHandle(io, name, false);
405 openFilesForWrite.add(name);
407 // throttling REALLY slows down tests, so don't do it very often for SOMETIMES.
408 if (throttling == Throttling.ALWAYS ||
409 (throttling == Throttling.SOMETIMES && randomState.nextInt(50) == 0)) {
410 if (LuceneTestCase.VERBOSE) {
411 System.out.println("MockDirectoryWrapper: throttling indexOutput");
413 return throttledOutput.newFromDelegate(io);
419 synchronized void addFileHandle(Closeable c, String name, boolean input) {
420 Integer v = openFiles.get(name);
422 v = Integer.valueOf(v.intValue()+1);
423 openFiles.put(name, v);
425 openFiles.put(name, Integer.valueOf(1));
428 openFileHandles.put(c, new RuntimeException("unclosed Index" + (input ? "Input" : "Output") + ": " + name));
431 private boolean failOnOpenInput = true;
433 public void setFailOnOpenInput(boolean v) {
438 public synchronized IndexInput openInput(String name) throws IOException {
440 if (failOnOpenInput) {
441 maybeThrowDeterministicException();
443 if (!delegate.fileExists(name))
444 throw new FileNotFoundException(name);
446 // cannot open a file for input if it's still open for
447 // output, except for segments.gen and segments_N
448 if (openFilesForWrite.contains(name) && !name.startsWith("segments")) {
449 throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false);
452 IndexInput ii = new MockIndexInputWrapper(this, name, delegate.openInput(name));
453 addFileHandle(ii, name, true);
457 /** Provided for testing purposes. Use sizeInBytes() instead. */
458 public synchronized final long getRecomputedSizeInBytes() throws IOException {
459 if (!(delegate instanceof RAMDirectory))
460 return sizeInBytes();
462 for(final RAMFile file: ((RAMDirectory)delegate).fileMap.values()) {
463 size += file.getSizeInBytes();
468 /** Like getRecomputedSizeInBytes(), but, uses actual file
469 * lengths rather than buffer allocations (which are
470 * quantized up to nearest
471 * RAMOutputStream.BUFFER_SIZE (now 1024) bytes.
474 public final synchronized long getRecomputedActualSizeInBytes() throws IOException {
475 if (!(delegate instanceof RAMDirectory))
476 return sizeInBytes();
478 for (final RAMFile file : ((RAMDirectory)delegate).fileMap.values())
484 public synchronized void close() throws IOException {
486 if (openFiles == null) {
487 openFiles = new HashMap<String,Integer>();
488 openFilesDeleted = new HashSet<String>();
490 if (noDeleteOpenFile && openFiles.size() > 0) {
491 // print the first one as its very verbose otherwise
492 Exception cause = null;
493 Iterator<Exception> stacktraces = openFileHandles.values().iterator();
494 if (stacktraces.hasNext())
495 cause = stacktraces.next();
496 // RuntimeException instead of IOException because
497 // super() does not throw IOException currently:
498 throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open files: " + openFiles, cause);
500 if (noDeleteOpenFile && openLocks.size() > 0) {
501 throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open locks: " + openLocks);
504 if (checkIndexOnClose && IndexReader.indexExists(this)) {
505 if (LuceneTestCase.VERBOSE) {
506 System.out.println("\nNOTE: MockDirectoryWrapper: now run CheckIndex");
508 _TestUtil.checkIndex(this);
513 synchronized void removeOpenFile(Closeable c, String name) {
514 Integer v = openFiles.get(name);
515 // Could be null when crash() was called
517 if (v.intValue() == 1) {
518 openFiles.remove(name);
519 openFilesDeleted.remove(name);
521 v = Integer.valueOf(v.intValue()-1);
522 openFiles.put(name, v);
526 openFileHandles.remove(c);
529 public synchronized void removeIndexOutput(IndexOutput out, String name) {
530 openFilesForWrite.remove(name);
531 removeOpenFile(out, name);
534 public synchronized void removeIndexInput(IndexInput in, String name) {
535 removeOpenFile(in, name);
540 public synchronized boolean isOpen() {
545 * Objects that represent fail-able conditions. Objects of a derived
546 * class are created and registered with the mock directory. After
547 * register, each object will be invoked once for each first write
548 * of a file, giving the object a chance to throw an IOException.
550 public static class Failure {
552 * eval is called on the first write of every new file.
554 public void eval(MockDirectoryWrapper dir) throws IOException { }
557 * reset should set the state of the failure to its default
558 * (freshly constructed) state. Reset is convenient for tests
559 * that want to create one failure object and then reuse it in
560 * multiple cases. This, combined with the fact that Failure
561 * subclasses are often anonymous classes makes reset difficult to
564 * A typical example of use is
565 * Failure failure = new Failure() { ... };
567 * mock.failOn(failure.reset())
569 public Failure reset() { return this; }
571 protected boolean doFail;
573 public void setDoFail() {
577 public void clearDoFail() {
582 ArrayList<Failure> failures;
585 * add a Failure object to the list of objects to be evaluated
586 * at every potential failure point
588 synchronized public void failOn(Failure fail) {
589 if (failures == null) {
590 failures = new ArrayList<Failure>();
596 * Iterate through the failures list, giving each object a
597 * chance to throw an IOE
599 synchronized void maybeThrowDeterministicException() throws IOException {
600 if (failures != null) {
601 for(int i = 0; i < failures.size(); i++) {
602 failures.get(i).eval(this);
608 public synchronized String[] listAll() throws IOException {
610 return delegate.listAll();
614 public synchronized boolean fileExists(String name) throws IOException {
616 return delegate.fileExists(name);
620 public synchronized long fileModified(String name) throws IOException {
622 return delegate.fileModified(name);
627 /* @deprecated Lucene never uses this API; it will be
629 public synchronized void touchFile(String name) throws IOException {
631 delegate.touchFile(name);
635 public synchronized long fileLength(String name) throws IOException {
637 return delegate.fileLength(name);
641 public synchronized Lock makeLock(String name) {
643 return delegate.makeLock(name);
647 public synchronized void clearLock(String name) throws IOException {
649 delegate.clearLock(name);
653 public synchronized void setLockFactory(LockFactory lockFactory) throws IOException {
655 delegate.setLockFactory(lockFactory);
659 public synchronized LockFactory getLockFactory() {
661 return delegate.getLockFactory();
665 public synchronized String getLockID() {
667 return delegate.getLockID();
671 public synchronized void copy(Directory to, String src, String dest) throws IOException {
673 delegate.copy(to, src, dest);