add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / backwards / src / test-framework / org / apache / lucene / store / MockDirectoryWrapper.java
1 package org.apache.lucene.store;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
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;
30 import java.util.Map;
31 import java.util.Random;
32 import java.util.Set;
33
34 import org.apache.lucene.index.IndexReader;
35 import org.apache.lucene.util.LuceneTestCase;
36 import org.apache.lucene.util.ThrottledIndexOutput;
37 import org.apache.lucene.util._TestUtil;
38
39 /**
40  * This is a Directory Wrapper that adds methods
41  * intended to be used only by unit tests.
42  * It also adds a number of features useful for testing:
43  * <ul>
44  *   <li> Instances created by {@link LuceneTestCase#newDirectory()} are tracked 
45  *        to ensure they are closed by the test.
46  *   <li> When a MockDirectoryWrapper is closed, it will throw an exception if 
47  *        it has any open files against it (with a stacktrace indicating where 
48  *        they were opened from).
49  *   <li> When a MockDirectoryWrapper is closed, it runs CheckIndex to test if
50  *        the index was corrupted.
51  *   <li> MockDirectoryWrapper simulates some "features" of Windows, such as
52  *        refusing to write/delete to open files.
53  * </ul>
54  */
55
56 public class MockDirectoryWrapper extends Directory {
57   final Directory delegate;
58   long maxSize;
59
60   // Max actual bytes used. This is set by MockRAMOutputStream:
61   long maxUsedSize;
62   double randomIOExceptionRate;
63   Random randomState;
64   boolean noDeleteOpenFile = true;
65   boolean preventDoubleWrite = true;
66   boolean checkIndexOnClose = true;
67   boolean trackDiskUsage = false;
68   private Set<String> unSyncedFiles;
69   private Set<String> createdFiles;
70   private Set<String> openFilesForWrite = new HashSet<String>();
71   Set<String> openLocks = Collections.synchronizedSet(new HashSet<String>());
72   volatile boolean crashed;
73   private ThrottledIndexOutput throttledOutput;
74   private Throttling throttling = Throttling.SOMETIMES;
75
76   // use this for tracking files for crash.
77   // additionally: provides debugging information in case you leave one open
78   private Map<Closeable,Exception> openFileHandles = Collections.synchronizedMap(new IdentityHashMap<Closeable,Exception>());
79
80   // NOTE: we cannot initialize the Map here due to the
81   // order in which our constructor actually does this
82   // member initialization vs when it calls super.  It seems
83   // like super is called, then our members are initialized:
84   private Map<String,Integer> openFiles;
85
86   // Only tracked if noDeleteOpenFile is true: if an attempt
87   // is made to delete an open file, we enroll it here.
88   private Set<String> openFilesDeleted;
89
90   private synchronized void init() {
91     if (openFiles == null) {
92       openFiles = new HashMap<String,Integer>();
93       openFilesDeleted = new HashSet<String>();
94     }
95
96     if (createdFiles == null)
97       createdFiles = new HashSet<String>();
98     if (unSyncedFiles == null)
99       unSyncedFiles = new HashSet<String>();
100   }
101
102   public MockDirectoryWrapper(Random random, Directory delegate) {
103     this.delegate = delegate;
104     // must make a private random since our methods are
105     // called from different threads; else test failures may
106     // not be reproducible from the original seed
107     this.randomState = new Random(random.nextInt());
108     this.throttledOutput = new ThrottledIndexOutput(ThrottledIndexOutput
109         .mBitsToBytes(40 + randomState.nextInt(10)), 5 + randomState.nextInt(5), null);
110     // force wrapping of lockfactory
111     try {
112       setLockFactory(new MockLockFactoryWrapper(this, delegate.getLockFactory()));
113     } catch (IOException e) {
114       throw new RuntimeException(e);
115     }
116     init();
117   }
118
119   public void setTrackDiskUsage(boolean v) {
120     trackDiskUsage = v;
121   }
122
123   /** If set to true, we throw an IOException if the same
124    *  file is opened by createOutput, ever. */
125   public void setPreventDoubleWrite(boolean value) {
126     preventDoubleWrite = value;
127   }
128
129   @Deprecated
130   @Override
131   public void sync(String name) throws IOException {
132     maybeYield();
133     maybeThrowDeterministicException();
134     if (crashed)
135       throw new IOException("cannot sync after crash");
136     unSyncedFiles.remove(name);
137     delegate.sync(name);
138   }
139   
140   public static enum Throttling {
141     /** always emulate a slow hard disk. could be very slow! */
142     ALWAYS,
143     /** sometimes (2% of the time) emulate a slow hard disk. */
144     SOMETIMES,
145     /** never throttle output */
146     NEVER
147   }
148   
149   public void setThrottling(Throttling throttling) {
150     this.throttling = throttling;
151   }
152
153   @Override
154   public synchronized void sync(Collection<String> names) throws IOException {
155     maybeYield();
156     for (String name : names)
157       maybeThrowDeterministicException();
158     if (crashed)
159       throw new IOException("cannot sync after crash");
160     unSyncedFiles.removeAll(names);
161     delegate.sync(names);
162   }
163   
164   @Override
165   public String toString() {
166     maybeYield();
167     return "MockDirWrapper(" + delegate + ")";
168   }
169
170   public synchronized final long sizeInBytes() throws IOException {
171     if (delegate instanceof RAMDirectory)
172       return ((RAMDirectory) delegate).sizeInBytes();
173     else {
174       // hack
175       long size = 0;
176       for (String file : delegate.listAll())
177         size += delegate.fileLength(file);
178       return size;
179     }
180   }
181
182   /** Simulates a crash of OS or machine by overwriting
183    *  unsynced files. */
184   public synchronized void crash() throws IOException {
185     crashed = true;
186     openFiles = new HashMap<String,Integer>();
187     openFilesForWrite = new HashSet<String>();
188     openFilesDeleted = new HashSet<String>();
189     Iterator<String> it = unSyncedFiles.iterator();
190     unSyncedFiles = new HashSet<String>();
191     // first force-close all files, so we can corrupt on windows etc.
192     // clone the file map, as these guys want to remove themselves on close.
193     Map<Closeable,Exception> m = new IdentityHashMap<Closeable,Exception>(openFileHandles);
194     for (Closeable f : m.keySet())
195       try {
196         f.close();
197       } catch (Exception ignored) {}
198     
199     int count = 0;
200     while(it.hasNext()) {
201       String name = it.next();
202       if (count % 3 == 0) {
203         deleteFile(name, true);
204       } else if (count % 3 == 1) {
205         // Zero out file entirely
206         long length = fileLength(name);
207         byte[] zeroes = new byte[256];
208         long upto = 0;
209         IndexOutput out = delegate.createOutput(name);
210         while(upto < length) {
211           final int limit = (int) Math.min(length-upto, zeroes.length);
212           out.writeBytes(zeroes, 0, limit);
213           upto += limit;
214         }
215         out.close();
216       } else if (count % 3 == 2) {
217         // Truncate the file:
218         IndexOutput out = delegate.createOutput(name);
219         out.setLength(fileLength(name)/2);
220         out.close();
221       }
222       count++;
223     }
224   }
225
226   public synchronized void clearCrash() throws IOException {
227     crashed = false;
228     openLocks.clear();
229   }
230
231   public void setMaxSizeInBytes(long maxSize) {
232     this.maxSize = maxSize;
233   }
234   public long getMaxSizeInBytes() {
235     return this.maxSize;
236   }
237
238   /**
239    * Returns the peek actual storage used (bytes) in this
240    * directory.
241    */
242   public long getMaxUsedSizeInBytes() {
243     return this.maxUsedSize;
244   }
245   public void resetMaxUsedSizeInBytes() throws IOException {
246     this.maxUsedSize = getRecomputedActualSizeInBytes();
247   }
248
249   /**
250    * Emulate windows whereby deleting an open file is not
251    * allowed (raise IOException).
252   */
253   public void setNoDeleteOpenFile(boolean value) {
254     this.noDeleteOpenFile = value;
255   }
256   public boolean getNoDeleteOpenFile() {
257     return noDeleteOpenFile;
258   }
259
260   /**
261    * Set whether or not checkindex should be run
262    * on close
263    */
264   public void setCheckIndexOnClose(boolean value) {
265     this.checkIndexOnClose = value;
266   }
267   
268   public boolean getCheckIndexOnClose() {
269     return checkIndexOnClose;
270   }
271   /**
272    * If 0.0, no exceptions will be thrown.  Else this should
273    * be a double 0.0 - 1.0.  We will randomly throw an
274    * IOException on the first write to an OutputStream based
275    * on this probability.
276    */
277   public void setRandomIOExceptionRate(double rate) {
278     randomIOExceptionRate = rate;
279   }
280   public double getRandomIOExceptionRate() {
281     return randomIOExceptionRate;
282   }
283
284   void maybeThrowIOException() throws IOException {
285     if (randomIOExceptionRate > 0.0) {
286       int number = Math.abs(randomState.nextInt() % 1000);
287       if (number < randomIOExceptionRate*1000) {
288         if (LuceneTestCase.VERBOSE) {
289           System.out.println(Thread.currentThread().getName() + ": MockDirectoryWrapper: now throw random exception");
290           new Throwable().printStackTrace(System.out);
291         }
292         throw new IOException("a random IOException");
293       }
294     }
295   }
296
297   @Override
298   public synchronized void deleteFile(String name) throws IOException {
299     maybeYield();
300     deleteFile(name, false);
301   }
302
303   // sets the cause of the incoming ioe to be the stack
304   // trace when the offending file name was opened
305   private synchronized IOException fillOpenTrace(IOException ioe, String name, boolean input) {
306     for(Map.Entry<Closeable,Exception> ent : openFileHandles.entrySet()) {
307       if (input && ent.getKey() instanceof MockIndexInputWrapper && ((MockIndexInputWrapper) ent.getKey()).name.equals(name)) {
308         ioe.initCause(ent.getValue());
309         break;
310       } else if (!input && ent.getKey() instanceof MockIndexOutputWrapper && ((MockIndexOutputWrapper) ent.getKey()).name.equals(name)) {
311         ioe.initCause(ent.getValue());
312         break;
313       }
314     }
315     return ioe;
316   }
317
318   private void maybeYield() {
319     if (randomState.nextBoolean()) {
320       Thread.yield();
321     }
322   }
323
324   private synchronized void deleteFile(String name, boolean forced) throws IOException {
325     maybeYield();
326
327     maybeThrowDeterministicException();
328
329     if (crashed && !forced)
330       throw new IOException("cannot delete after crash");
331
332     if (unSyncedFiles.contains(name))
333       unSyncedFiles.remove(name);
334     if (!forced && noDeleteOpenFile) {
335       if (openFiles.containsKey(name)) {
336         openFilesDeleted.add(name);
337         throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot delete"), name, true);
338       } else {
339         openFilesDeleted.remove(name);
340       }
341     }
342     delegate.deleteFile(name);
343   }
344
345   public synchronized Set<String> getOpenDeletedFiles() {
346     return new HashSet<String>(openFilesDeleted);
347   }
348
349   @Override
350   public synchronized IndexOutput createOutput(String name) throws IOException {
351     maybeYield();
352     if (crashed)
353       throw new IOException("cannot createOutput after crash");
354     init();
355     synchronized(this) {
356       if (preventDoubleWrite && createdFiles.contains(name) && !name.equals("segments.gen"))
357         throw new IOException("file \"" + name + "\" was already written to");
358     }
359     if (noDeleteOpenFile && openFiles.containsKey(name))
360       throw new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
361     
362     if (crashed)
363       throw new IOException("cannot createOutput after crash");
364     unSyncedFiles.add(name);
365     createdFiles.add(name);
366     
367     if (delegate instanceof RAMDirectory) {
368       RAMDirectory ramdir = (RAMDirectory) delegate;
369       RAMFile file = new RAMFile(ramdir);
370       RAMFile existing = ramdir.fileMap.get(name);
371     
372       // Enforce write once:
373       if (existing!=null && !name.equals("segments.gen") && preventDoubleWrite)
374         throw new IOException("file " + name + " already exists");
375       else {
376         if (existing!=null) {
377           ramdir.sizeInBytes.getAndAdd(-existing.sizeInBytes);
378           existing.directory = null;
379         }
380         ramdir.fileMap.put(name, file);
381       }
382     }
383     
384     //System.out.println(Thread.currentThread().getName() + ": MDW: create " + name);
385     IndexOutput io = new MockIndexOutputWrapper(this, delegate.createOutput(name), name);
386     addFileHandle(io, name, false);
387     openFilesForWrite.add(name);
388     
389     // throttling REALLY slows down tests, so don't do it very often for SOMETIMES.
390     if (throttling == Throttling.ALWAYS || 
391         (throttling == Throttling.SOMETIMES && randomState.nextInt(50) == 0)) {
392       if (LuceneTestCase.VERBOSE) {
393         System.out.println("MockDirectoryWrapper: throttling indexOutput");
394       }
395       return throttledOutput.newFromDelegate(io);
396     } else {
397       return io;
398     }
399   }
400
401   private void addFileHandle(Closeable c, String name, boolean input) {
402     Integer v = openFiles.get(name);
403     if (v != null) {
404       v = Integer.valueOf(v.intValue()+1);
405       openFiles.put(name, v);
406     } else {
407       openFiles.put(name, Integer.valueOf(1));
408     }
409     
410     openFileHandles.put(c, new RuntimeException("unclosed Index" + (input ? "Input" : "Output") + ": " + name));
411   }
412   
413   @Override
414   public synchronized IndexInput openInput(String name) throws IOException {
415     maybeYield();
416     if (!delegate.fileExists(name))
417       throw new FileNotFoundException(name);
418
419     // cannot open a file for input if it's still open for
420     // output, except for segments.gen and segments_N
421     if (openFilesForWrite.contains(name) && !name.startsWith("segments")) {
422       throw fillOpenTrace(new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open for writing"), name, false);
423     }
424
425     IndexInput ii = new MockIndexInputWrapper(this, name, delegate.openInput(name));
426     addFileHandle(ii, name, true);
427     return ii;
428   }
429
430   /** Provided for testing purposes.  Use sizeInBytes() instead. */
431   public synchronized final long getRecomputedSizeInBytes() throws IOException {
432     if (!(delegate instanceof RAMDirectory))
433       return sizeInBytes();
434     long size = 0;
435     for(final RAMFile file: ((RAMDirectory)delegate).fileMap.values()) {
436       size += file.getSizeInBytes();
437     }
438     return size;
439   }
440
441   /** Like getRecomputedSizeInBytes(), but, uses actual file
442    * lengths rather than buffer allocations (which are
443    * quantized up to nearest
444    * RAMOutputStream.BUFFER_SIZE (now 1024) bytes.
445    */
446
447   public final synchronized long getRecomputedActualSizeInBytes() throws IOException {
448     if (!(delegate instanceof RAMDirectory))
449       return sizeInBytes();
450     long size = 0;
451     for (final RAMFile file : ((RAMDirectory)delegate).fileMap.values())
452       size += file.length;
453     return size;
454   }
455
456   @Override
457   public synchronized void close() throws IOException {
458     maybeYield();
459     if (openFiles == null) {
460       openFiles = new HashMap<String,Integer>();
461       openFilesDeleted = new HashSet<String>();
462     }
463     if (noDeleteOpenFile && openFiles.size() > 0) {
464       // print the first one as its very verbose otherwise
465       Exception cause = null;
466       Iterator<Exception> stacktraces = openFileHandles.values().iterator();
467       if (stacktraces.hasNext())
468         cause = stacktraces.next();
469       // RuntimeException instead of IOException because
470       // super() does not throw IOException currently:
471       throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open files: " + openFiles, cause);
472     }
473     if (noDeleteOpenFile && openLocks.size() > 0) {
474       throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open locks: " + openLocks);
475     }
476     open = false;
477     if (checkIndexOnClose && IndexReader.indexExists(this)) {
478       if (LuceneTestCase.VERBOSE) {
479         System.out.println("\nNOTE: MockDirectoryWrapper: now run CheckIndex");
480       } 
481       _TestUtil.checkIndex(this);
482     }
483     delegate.close();
484   }
485
486   private synchronized void removeOpenFile(Closeable c, String name) {
487     Integer v = openFiles.get(name);
488     // Could be null when crash() was called
489     if (v != null) {
490       if (v.intValue() == 1) {
491         openFiles.remove(name);
492         openFilesDeleted.remove(name);
493       } else {
494         v = Integer.valueOf(v.intValue()-1);
495         openFiles.put(name, v);
496       }
497     }
498
499     openFileHandles.remove(c);
500   }
501   
502   public synchronized void removeIndexOutput(IndexOutput out, String name) {
503     openFilesForWrite.remove(name);
504     removeOpenFile(out, name);
505   }
506   
507   public synchronized void removeIndexInput(IndexInput in, String name) {
508     removeOpenFile(in, name);
509   }
510   
511   boolean open = true;
512   
513   public synchronized boolean isOpen() {
514     return open;
515   }
516   
517   /**
518    * Objects that represent fail-able conditions. Objects of a derived
519    * class are created and registered with the mock directory. After
520    * register, each object will be invoked once for each first write
521    * of a file, giving the object a chance to throw an IOException.
522    */
523   public static class Failure {
524     /**
525      * eval is called on the first write of every new file.
526      */
527     public void eval(MockDirectoryWrapper dir) throws IOException { }
528
529     /**
530      * reset should set the state of the failure to its default
531      * (freshly constructed) state. Reset is convenient for tests
532      * that want to create one failure object and then reuse it in
533      * multiple cases. This, combined with the fact that Failure
534      * subclasses are often anonymous classes makes reset difficult to
535      * do otherwise.
536      *
537      * A typical example of use is
538      * Failure failure = new Failure() { ... };
539      * ...
540      * mock.failOn(failure.reset())
541      */
542     public Failure reset() { return this; }
543
544     protected boolean doFail;
545
546     public void setDoFail() {
547       doFail = true;
548     }
549
550     public void clearDoFail() {
551       doFail = false;
552     }
553   }
554
555   ArrayList<Failure> failures;
556
557   /**
558    * add a Failure object to the list of objects to be evaluated
559    * at every potential failure point
560    */
561   synchronized public void failOn(Failure fail) {
562     if (failures == null) {
563       failures = new ArrayList<Failure>();
564     }
565     failures.add(fail);
566   }
567
568   /**
569    * Iterate through the failures list, giving each object a
570    * chance to throw an IOE
571    */
572   synchronized void maybeThrowDeterministicException() throws IOException {
573     if (failures != null) {
574       for(int i = 0; i < failures.size(); i++) {
575         failures.get(i).eval(this);
576       }
577     }
578   }
579
580   @Override
581   public synchronized String[] listAll() throws IOException {
582     maybeYield();
583     return delegate.listAll();
584   }
585
586   @Override
587   public synchronized boolean fileExists(String name) throws IOException {
588     maybeYield();
589     return delegate.fileExists(name);
590   }
591
592   @Override
593   public synchronized long fileModified(String name) throws IOException {
594     maybeYield();
595     return delegate.fileModified(name);
596   }
597
598   @Override
599   @Deprecated
600   /*  @deprecated Lucene never uses this API; it will be
601    *  removed in 4.0. */
602   public synchronized void touchFile(String name) throws IOException {
603     maybeYield();
604     delegate.touchFile(name);
605   }
606
607   @Override
608   public synchronized long fileLength(String name) throws IOException {
609     maybeYield();
610     return delegate.fileLength(name);
611   }
612
613   @Override
614   public synchronized Lock makeLock(String name) {
615     maybeYield();
616     return delegate.makeLock(name);
617   }
618
619   @Override
620   public synchronized void clearLock(String name) throws IOException {
621     maybeYield();
622     delegate.clearLock(name);
623   }
624
625   @Override
626   public synchronized void setLockFactory(LockFactory lockFactory) throws IOException {
627     maybeYield();
628     delegate.setLockFactory(lockFactory);
629   }
630
631   @Override
632   public synchronized LockFactory getLockFactory() {
633     maybeYield();
634     return delegate.getLockFactory();
635   }
636
637   @Override
638   public synchronized String getLockID() {
639     maybeYield();
640     return delegate.getLockID();
641   }
642
643   @Override
644   public synchronized void copy(Directory to, String src, String dest) throws IOException {
645     maybeYield();
646     delegate.copy(to, src, dest);
647   }
648 }