pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / test-framework / java / 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 import java.util.concurrent.atomic.AtomicInteger;
34
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;
39
40 /**
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:
44  * <ul>
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.
54  * </ul>
55  */
56
57 public class MockDirectoryWrapper extends Directory {
58   final Directory delegate;
59   long maxSize;
60
61   // Max actual bytes used. This is set by MockRAMOutputStream:
62   long maxUsedSize;
63   double randomIOExceptionRate;
64   Random randomState;
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;
76
77   final AtomicInteger inputCloneCount = new AtomicInteger();
78
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>());
82
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;
88
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;
92
93   private synchronized void init() {
94     if (openFiles == null) {
95       openFiles = new HashMap<String,Integer>();
96       openFilesDeleted = new HashSet<String>();
97     }
98
99     if (createdFiles == null)
100       createdFiles = new HashSet<String>();
101     if (unSyncedFiles == null)
102       unSyncedFiles = new HashSet<String>();
103   }
104
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
114     try {
115       setLockFactory(new MockLockFactoryWrapper(this, delegate.getLockFactory()));
116     } catch (IOException e) {
117       throw new RuntimeException(e);
118     }
119     init();
120   }
121
122   public int getInputCloneCount() {
123     return inputCloneCount.get();
124   }
125
126   public void setTrackDiskUsage(boolean v) {
127     trackDiskUsage = v;
128   }
129
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;
134   }
135
136   @Deprecated
137   @Override
138   public void sync(String name) throws IOException {
139     maybeYield();
140     maybeThrowDeterministicException();
141     if (crashed)
142       throw new IOException("cannot sync after crash");
143     unSyncedFiles.remove(name);
144     delegate.sync(name);
145   }
146   
147   public static enum Throttling {
148     /** always emulate a slow hard disk. could be very slow! */
149     ALWAYS,
150     /** sometimes (2% of the time) emulate a slow hard disk. */
151     SOMETIMES,
152     /** never throttle output */
153     NEVER
154   }
155   
156   public void setThrottling(Throttling throttling) {
157     this.throttling = throttling;
158   }
159
160   @Override
161   public synchronized void sync(Collection<String> names) throws IOException {
162     maybeYield();
163     maybeThrowDeterministicException();
164     if (crashed)
165       throw new IOException("cannot sync after crash");
166     unSyncedFiles.removeAll(names);
167     delegate.sync(names);
168   }
169   
170   @Override
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
175     // maybeYield();
176     return "MockDirWrapper(" + delegate + ")";
177   }
178
179   public synchronized final long sizeInBytes() throws IOException {
180     if (delegate instanceof RAMDirectory)
181       return ((RAMDirectory) delegate).sizeInBytes();
182     else {
183       // hack
184       long size = 0;
185       for (String file : delegate.listAll())
186         size += delegate.fileLength(file);
187       return size;
188     }
189   }
190
191   /** Simulates a crash of OS or machine by overwriting
192    *  unsynced files. */
193   public synchronized void crash() throws IOException {
194     crashed = true;
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())
204       try {
205         f.close();
206       } catch (Exception ignored) {}
207     
208     int count = 0;
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];
217         long upto = 0;
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);
222           upto += limit;
223         }
224         out.close();
225       } else if (count % 3 == 2) {
226         // Truncate the file:
227         IndexOutput out = delegate.createOutput(name);
228         out.setLength(fileLength(name)/2);
229         out.close();
230       }
231       count++;
232     }
233   }
234
235   public synchronized void clearCrash() throws IOException {
236     crashed = false;
237     openLocks.clear();
238   }
239
240   public void setMaxSizeInBytes(long maxSize) {
241     this.maxSize = maxSize;
242   }
243   public long getMaxSizeInBytes() {
244     return this.maxSize;
245   }
246
247   /**
248    * Returns the peek actual storage used (bytes) in this
249    * directory.
250    */
251   public long getMaxUsedSizeInBytes() {
252     return this.maxUsedSize;
253   }
254   public void resetMaxUsedSizeInBytes() throws IOException {
255     this.maxUsedSize = getRecomputedActualSizeInBytes();
256   }
257
258   /**
259    * Emulate windows whereby deleting an open file is not
260    * allowed (raise IOException).
261   */
262   public void setNoDeleteOpenFile(boolean value) {
263     this.noDeleteOpenFile = value;
264   }
265   public boolean getNoDeleteOpenFile() {
266     return noDeleteOpenFile;
267   }
268
269   /**
270    * Set whether or not checkindex should be run
271    * on close
272    */
273   public void setCheckIndexOnClose(boolean value) {
274     this.checkIndexOnClose = value;
275   }
276   
277   public boolean getCheckIndexOnClose() {
278     return checkIndexOnClose;
279   }
280   /**
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.
285    */
286   public void setRandomIOExceptionRate(double rate) {
287     randomIOExceptionRate = rate;
288   }
289   public double getRandomIOExceptionRate() {
290     return randomIOExceptionRate;
291   }
292
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);
300         }
301         throw new IOException("a random IOException");
302       }
303     }
304   }
305
306   @Override
307   public synchronized void deleteFile(String name) throws IOException {
308     maybeYield();
309     deleteFile(name, false);
310   }
311
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());
318         break;
319       } else if (!input && ent.getKey() instanceof MockIndexOutputWrapper && ((MockIndexOutputWrapper) ent.getKey()).name.equals(name)) {
320         ioe.initCause(ent.getValue());
321         break;
322       }
323     }
324     return ioe;
325   }
326
327   private void maybeYield() {
328     if (randomState.nextBoolean()) {
329       Thread.yield();
330     }
331   }
332
333   private synchronized void deleteFile(String name, boolean forced) throws IOException {
334     maybeYield();
335
336     maybeThrowDeterministicException();
337
338     if (crashed && !forced)
339       throw new IOException("cannot delete after crash");
340
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);
347       } else {
348         openFilesDeleted.remove(name);
349       }
350     }
351     delegate.deleteFile(name);
352   }
353
354   public synchronized Set<String> getOpenDeletedFiles() {
355     return new HashSet<String>(openFilesDeleted);
356   }
357
358   private boolean failOnCreateOutput = true;
359
360   public void setFailOnCreateOutput(boolean v) {
361     failOnCreateOutput = v;
362   }
363
364   @Override
365   public synchronized IndexOutput createOutput(String name) throws IOException {
366     maybeYield();
367     if (failOnCreateOutput) {
368       maybeThrowDeterministicException();
369     }
370     if (crashed)
371       throw new IOException("cannot createOutput after crash");
372     init();
373     synchronized(this) {
374       if (preventDoubleWrite && createdFiles.contains(name) && !name.equals("segments.gen"))
375         throw new IOException("file \"" + name + "\" was already written to");
376     }
377     if (noDeleteOpenFile && openFiles.containsKey(name))
378       throw new IOException("MockDirectoryWrapper: file \"" + name + "\" is still open: cannot overwrite");
379     
380     if (crashed)
381       throw new IOException("cannot createOutput after crash");
382     unSyncedFiles.add(name);
383     createdFiles.add(name);
384     
385     if (delegate instanceof RAMDirectory) {
386       RAMDirectory ramdir = (RAMDirectory) delegate;
387       RAMFile file = new RAMFile(ramdir);
388       RAMFile existing = ramdir.fileMap.get(name);
389     
390       // Enforce write once:
391       if (existing!=null && !name.equals("segments.gen") && preventDoubleWrite)
392         throw new IOException("file " + name + " already exists");
393       else {
394         if (existing!=null) {
395           ramdir.sizeInBytes.getAndAdd(-existing.sizeInBytes);
396           existing.directory = null;
397         }
398         ramdir.fileMap.put(name, file);
399       }
400     }
401     
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);
406     
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");
412       }
413       return throttledOutput.newFromDelegate(io);
414     } else {
415       return io;
416     }
417   }
418
419   synchronized void addFileHandle(Closeable c, String name, boolean input) {
420     Integer v = openFiles.get(name);
421     if (v != null) {
422       v = Integer.valueOf(v.intValue()+1);
423       openFiles.put(name, v);
424     } else {
425       openFiles.put(name, Integer.valueOf(1));
426     }
427     
428     openFileHandles.put(c, new RuntimeException("unclosed Index" + (input ? "Input" : "Output") + ": " + name));
429   }
430   
431   private boolean failOnOpenInput = true;
432
433   public void setFailOnOpenInput(boolean v) {
434     failOnOpenInput = v;
435   }
436
437   @Override
438   public synchronized IndexInput openInput(String name) throws IOException {
439     maybeYield();
440     if (failOnOpenInput) {
441       maybeThrowDeterministicException();
442     }
443     if (!delegate.fileExists(name))
444       throw new FileNotFoundException(name);
445
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);
450     }
451
452     IndexInput ii = new MockIndexInputWrapper(this, name, delegate.openInput(name));
453     addFileHandle(ii, name, true);
454     return ii;
455   }
456
457   /** Provided for testing purposes.  Use sizeInBytes() instead. */
458   public synchronized final long getRecomputedSizeInBytes() throws IOException {
459     if (!(delegate instanceof RAMDirectory))
460       return sizeInBytes();
461     long size = 0;
462     for(final RAMFile file: ((RAMDirectory)delegate).fileMap.values()) {
463       size += file.getSizeInBytes();
464     }
465     return size;
466   }
467
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.
472    */
473
474   public final synchronized long getRecomputedActualSizeInBytes() throws IOException {
475     if (!(delegate instanceof RAMDirectory))
476       return sizeInBytes();
477     long size = 0;
478     for (final RAMFile file : ((RAMDirectory)delegate).fileMap.values())
479       size += file.length;
480     return size;
481   }
482
483   @Override
484   public synchronized void close() throws IOException {
485     maybeYield();
486     if (openFiles == null) {
487       openFiles = new HashMap<String,Integer>();
488       openFilesDeleted = new HashSet<String>();
489     }
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);
499     }
500     if (noDeleteOpenFile && openLocks.size() > 0) {
501       throw new RuntimeException("MockDirectoryWrapper: cannot close: there are still open locks: " + openLocks);
502     }
503     open = false;
504     if (checkIndexOnClose && IndexReader.indexExists(this)) {
505       if (LuceneTestCase.VERBOSE) {
506         System.out.println("\nNOTE: MockDirectoryWrapper: now run CheckIndex");
507       } 
508       _TestUtil.checkIndex(this);
509     }
510     delegate.close();
511   }
512
513   synchronized void removeOpenFile(Closeable c, String name) {
514     Integer v = openFiles.get(name);
515     // Could be null when crash() was called
516     if (v != null) {
517       if (v.intValue() == 1) {
518         openFiles.remove(name);
519         openFilesDeleted.remove(name);
520       } else {
521         v = Integer.valueOf(v.intValue()-1);
522         openFiles.put(name, v);
523       }
524     }
525
526     openFileHandles.remove(c);
527   }
528   
529   public synchronized void removeIndexOutput(IndexOutput out, String name) {
530     openFilesForWrite.remove(name);
531     removeOpenFile(out, name);
532   }
533   
534   public synchronized void removeIndexInput(IndexInput in, String name) {
535     removeOpenFile(in, name);
536   }
537   
538   boolean open = true;
539   
540   public synchronized boolean isOpen() {
541     return open;
542   }
543   
544   /**
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.
549    */
550   public static class Failure {
551     /**
552      * eval is called on the first write of every new file.
553      */
554     public void eval(MockDirectoryWrapper dir) throws IOException { }
555
556     /**
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
562      * do otherwise.
563      *
564      * A typical example of use is
565      * Failure failure = new Failure() { ... };
566      * ...
567      * mock.failOn(failure.reset())
568      */
569     public Failure reset() { return this; }
570
571     protected boolean doFail;
572
573     public void setDoFail() {
574       doFail = true;
575     }
576
577     public void clearDoFail() {
578       doFail = false;
579     }
580   }
581
582   ArrayList<Failure> failures;
583
584   /**
585    * add a Failure object to the list of objects to be evaluated
586    * at every potential failure point
587    */
588   synchronized public void failOn(Failure fail) {
589     if (failures == null) {
590       failures = new ArrayList<Failure>();
591     }
592     failures.add(fail);
593   }
594
595   /**
596    * Iterate through the failures list, giving each object a
597    * chance to throw an IOE
598    */
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);
603       }
604     }
605   }
606
607   @Override
608   public synchronized String[] listAll() throws IOException {
609     maybeYield();
610     return delegate.listAll();
611   }
612
613   @Override
614   public synchronized boolean fileExists(String name) throws IOException {
615     maybeYield();
616     return delegate.fileExists(name);
617   }
618
619   @Override
620   public synchronized long fileModified(String name) throws IOException {
621     maybeYield();
622     return delegate.fileModified(name);
623   }
624
625   @Override
626   @Deprecated
627   /*  @deprecated Lucene never uses this API; it will be
628    *  removed in 4.0. */
629   public synchronized void touchFile(String name) throws IOException {
630     maybeYield();
631     delegate.touchFile(name);
632   }
633
634   @Override
635   public synchronized long fileLength(String name) throws IOException {
636     maybeYield();
637     return delegate.fileLength(name);
638   }
639
640   @Override
641   public synchronized Lock makeLock(String name) {
642     maybeYield();
643     return delegate.makeLock(name);
644   }
645
646   @Override
647   public synchronized void clearLock(String name) throws IOException {
648     maybeYield();
649     delegate.clearLock(name);
650   }
651
652   @Override
653   public synchronized void setLockFactory(LockFactory lockFactory) throws IOException {
654     maybeYield();
655     delegate.setLockFactory(lockFactory);
656   }
657
658   @Override
659   public synchronized LockFactory getLockFactory() {
660     maybeYield();
661     return delegate.getLockFactory();
662   }
663
664   @Override
665   public synchronized String getLockID() {
666     maybeYield();
667     return delegate.getLockID();
668   }
669
670   @Override
671   public synchronized void copy(Directory to, String src, String dest) throws IOException {
672     maybeYield();
673     delegate.copy(to, src, dest);
674   }
675 }