1   package com.atlassian.bonnie;
2   
3   import junit.framework.TestCase;
4   import org.apache.lucene.analysis.standard.StandardAnalyzer;
5   import org.apache.lucene.document.Document;
6   import org.apache.lucene.document.Field;
7   import org.apache.lucene.index.IndexReader;
8   import org.apache.lucene.index.IndexWriter;
9   import org.apache.lucene.index.Term;
10  import org.apache.lucene.search.Hits;
11  import org.apache.lucene.search.IndexSearcher;
12  import org.apache.lucene.search.TermQuery;
13  import org.apache.lucene.store.FSDirectory;
14  import org.apache.lucene.store.RAMDirectory;
15  
16  import java.io.File;
17  import java.io.IOException;
18  
19  public class TestLuceneConnection extends TestCase
20  {
21      LuceneConnection lcon;
22  
23      private File tempDir;
24  
25      private static final String FIELD_NAME = "content.space";
26  
27      private static final String FIELD_VALUE = "space";
28  
29      protected void setUp() throws Exception
30      {
31          super.setUp();
32          tempDir = File.createTempFile("lucene", "conn");
33          tempDir.delete();
34          tempDir.mkdirs();
35          lcon = new LuceneConnection(tempDir, new StandardAnalyzer());
36      }
37  
38      protected void tearDown() throws Exception
39      {
40          super.tearDown();
41      }
42  
43      public void testSetReindexing() throws IOException
44      {
45          // make sure things run smoothly before docs are added
46          assertFalse(new File(tempDir, LuceneConnection.REINDEXING_FILENAME).exists());
47  
48          lcon.setReIndexing(true);
49          assertTrue(new File(tempDir, LuceneConnection.REINDEXING_FILENAME).exists());
50  
51          lcon.setReIndexing(false);
52          assertFalse(new File(tempDir, LuceneConnection.REINDEXING_FILENAME).exists());
53  
54          // now actually try adding documents
55          final int numDocs = 20;
56          lcon.getNumDocs(); // just initialize the reader here so we have
57          // pre-write reader version
58          lcon.setReIndexing(true);
59          addDocsToIndex(numDocs);
60  
61          assertEquals(0, lcon.getNumDocs());
62  
63          lcon.setReIndexing(false);
64          assertEquals(numDocs, lcon.getNumDocs());
65      }
66  
67      public void testUnlockIfNeeded() throws IOException
68      {
69          // create the index
70          lcon.withWriter(new LuceneConnection.WriterAction()
71          {
72              public void perform(IndexWriter writer) throws IOException
73              {}
74          });
75  
76          // grab lucene write lock
77          IndexWriter writer = new IndexWriter(tempDir, new StandardAnalyzer(), false);
78          writer.addDocument(createDocument());
79  
80          lcon.setAlwaysUnlock(false);
81          try
82          {
83              lcon.withWriter(new LuceneConnection.WriterAction()
84              {
85                  public void perform(IndexWriter writer) throws IOException
86                  {}
87              });
88              fail("Something wrong with testUnlockIfNeeded testcase");
89          }
90          catch (LuceneException e)
91          {}
92  
93          lcon.setAlwaysUnlock(true);
94          lcon.withWriter(new LuceneConnection.WriterAction()
95          {
96              public void perform(IndexWriter writer) throws IOException
97              {}
98          });
99      }
100 
101     /**
102      * NOTE: this test requires the use of a FSDirectory and multifile index format to test that close is properly being
103      * called. Use of a RAMDirectory or compound index file format cause this test to fail.
104      */
105     public void testSearch() throws IOException
106     {
107         lcon = new LuceneConnection(FSDirectory.getDirectory(tempDir), new StandardAnalyzer(), new LuceneConnection.Configuration()
108         {
109 
110             public int getBatchMaxBufferedDocs()
111             {
112                 return 100;
113             }
114 
115             public int getBatchMaxMergeDocs()
116             {
117                 return 200;
118             }
119 
120             public int getBatchMergeFactor()
121             {
122                 return 300;
123             }
124 
125             public int getInteractiveMaxBufferedDocs()
126             {
127                 return 10;
128             }
129 
130             public int getInteractiveMaxMergeDocs()
131             {
132                 return 20;
133             }
134 
135             public int getInteractiveMergeFactor()
136             {
137                 return 30;
138             }
139 
140             public int getMaxFieldLength()
141             {
142                 return 1000000;
143             }
144 
145             // must be false, otherwise we don't get an exception when we try to
146             // use the closed searcher
147             public boolean isCompoundIndexFileFormat()
148             {
149                 return false;
150             }
151         });
152         final int numDocs = 5;
153         addDocsToIndex(numDocs);
154 
155         // ensure the searcher is closed
156         final IndexSearcher[] s = new IndexSearcher[] { null };
157         lcon.withSearch(new LuceneConnection.SearcherAction()
158         {
159             public void perform(IndexSearcher searcher) throws IOException
160             {
161                 s[0] = searcher;
162             }
163         });
164         try
165         {
166             s[0].search(new TermQuery(new Term(FIELD_NAME, FIELD_VALUE)));
167             fail("Searcher not being closed.");
168         }
169         catch (Exception e)
170         {}
171     }
172 
173     private void addDocsToIndex(final int numDocs)
174     {
175         lcon.withWriter(new LuceneConnection.WriterAction()
176         {
177             public void perform(IndexWriter writer) throws IOException
178             {
179                 for (int i = 0; i < numDocs; ++i)
180                     writer.addDocument(createDocument());
181             }
182         });
183     }
184 
185     public void testMultiThreaded() throws InterruptedException
186     {
187         addDocsToIndex(5);
188         final Thread[] t = new Thread[1];
189 
190         final boolean[] innerThreadExecuted = new boolean[]
191         { false };
192 
193         // test withWrite, then simultaneously, withReadAndDeletes
194         lcon.withWriter(new LuceneConnection.WriterAction()
195         {
196             public void perform(final IndexWriter writer) throws IOException
197             {
198                 writer.addDocument(createDocument());
199 
200                 t[0] = new Thread()
201                 {
202                     public void run()
203                     {
204                         // this should only be run _after_ the WriterAction
205                         // completes
206                         lcon.withReaderAndDeletes(new LuceneConnection.ReaderAction()
207                         {
208                             public Object perform(IndexReader reader) throws IOException
209                             {
210                                 innerThreadExecuted[0] = true;
211                                 assertEquals("threaded withWriter: Added document not detected from thread", 6, reader.numDocs());
212                                 reader.deleteDocument(0);
213                                 return null;
214                             }
215                         });
216                     }
217                 };
218                 t[0].start();
219                 Thread.yield();
220                 assertFalse(innerThreadExecuted[0]);
221             }
222         });
223         t[0].join();
224         assertTrue(innerThreadExecuted[0]);
225         assertEquals("threaded withWriter: Document not deleted in thread", 5, lcon.getNumDocs());
226 
227         innerThreadExecuted[0] = false;
228 
229         // test withReaderAndDeletes, then simultaneously, withWrite
230         lcon.withReaderAndDeletes(new LuceneConnection.ReaderAction()
231         {
232             public Object perform(final IndexReader reader) throws IOException
233             {
234                 reader.deleteDocument(1); // document 0 was deleted above
235                 t[0] = new Thread()
236                 {
237                     public void run()
238                     {
239                         lcon.withReader(new LuceneConnection.ReaderAction()
240                         {
241                             public Object perform(IndexReader reader) throws IOException
242                             {
243                                 assertEquals("threaded withReaderAndDeletes: deleted document not detected", 4, reader.numDocs());
244                                 return null;
245                             }
246                         });
247 
248                         // this should only be run _after_ the ReaderAction
249                         // completes
250                         lcon.withWriter(new LuceneConnection.WriterAction()
251                         {
252                             public void perform(IndexWriter writer) throws IOException
253                             {
254                                 innerThreadExecuted[0] = true;
255                                 writer.addDocument(createDocument());
256                             }
257                         });
258                     }
259                 };
260                 t[0].start();
261                 Thread.yield();
262                 assertFalse(innerThreadExecuted[0]);
263                 return null;
264             }
265         });
266         t[0].join();
267         assertTrue(innerThreadExecuted[0]);
268         assertEquals("threaded withReaderAndDeletes: Document not added in thread", 5, lcon.getNumDocs());
269     }
270 
271     /**
272      * Test that Bonnie does its weird stuff with the int flags that are passed to withWriter(Action, int). Modify this
273      * test to match the required defaults.
274      * 
275      * @throws Exception
276      */
277     public void testDefaultConfiguration() throws Exception
278     {
279         lcon.withWriter(new LuceneConnection.WriterAction()
280         {
281             public void perform(IndexWriter writer) throws IOException
282             {
283                 assertEquals(4, writer.getMergeFactor());
284                 assertEquals(300, writer.getMaxBufferedDocs());
285                 assertEquals(5000, writer.getMaxMergeDocs());
286                 assertTrue(writer.getUseCompoundFile());
287             }
288         }, LuceneConnection.WRITER_INTERACTIVE);
289 
290         lcon.withWriter(new LuceneConnection.WriterAction()
291         {
292             public void perform(IndexWriter writer) throws IOException
293             {
294                 assertEquals(50, writer.getMergeFactor());
295                 assertEquals(300, writer.getMaxBufferedDocs());
296                 assertEquals(Integer.MAX_VALUE, writer.getMaxMergeDocs());
297                 assertTrue(writer.getUseCompoundFile());
298             }
299         }, LuceneConnection.WRITER_BATCH);
300     }
301 
302     /**
303      * Test that Bonnie does its weird stuff with the int flags that are passed to withWriter(Action, int). Modify this
304      * test to match the required defaults.
305      * 
306      * @throws Exception
307      */
308     public void testCustomConfiguration() throws Exception
309     {
310         LuceneConnection.Configuration config = new LuceneConnection.Configuration()
311         {
312 
313             public int getBatchMaxBufferedDocs()
314             {
315                 return 100;
316             }
317 
318             public int getBatchMaxMergeDocs()
319             {
320                 return 200;
321             }
322 
323             public int getBatchMergeFactor()
324             {
325                 return 300;
326             }
327 
328             public int getInteractiveMaxBufferedDocs()
329             {
330                 return 10;
331             }
332 
333             public int getInteractiveMaxMergeDocs()
334             {
335                 return 20;
336             }
337 
338             public int getInteractiveMergeFactor()
339             {
340                 return 30;
341             }
342 
343             public int getMaxFieldLength()
344             {
345                 return 1000000;
346             }
347 
348             public boolean isCompoundIndexFileFormat()
349             {
350                 return false;
351             }
352         };
353         assertLuceneConnectionConfig(new LuceneConnection(new RAMDirectory(), new StandardAnalyzer(), config));
354         assertLuceneConnectionConfig(new LuceneConnection(tempDir, new StandardAnalyzer(), config));
355     }
356 
357     private void assertLuceneConnectionConfig(LuceneConnection lcon)
358     {
359         lcon.withWriter(new LuceneConnection.WriterAction()
360         {
361             public void perform(IndexWriter writer) throws IOException
362             {
363                 assertEquals(30, writer.getMergeFactor());
364                 assertEquals(10, writer.getMaxBufferedDocs());
365                 assertEquals(20, writer.getMaxMergeDocs());
366                 assertFalse(writer.getUseCompoundFile());
367             }
368         }, LuceneConnection.WRITER_INTERACTIVE);
369 
370         lcon.withWriter(new LuceneConnection.WriterAction()
371         {
372             public void perform(IndexWriter writer) throws IOException
373             {
374                 assertEquals(300, writer.getMergeFactor());
375                 assertEquals(100, writer.getMaxBufferedDocs());
376                 assertEquals(200, writer.getMaxMergeDocs());
377                 assertFalse(writer.getUseCompoundFile());
378             }
379         }, LuceneConnection.WRITER_BATCH);
380     }
381 
382     private Document createDocument()
383     {
384         Document d = new Document();
385         d.add(new Field(FIELD_NAME, FIELD_VALUE, Field.Store.YES, Field.Index.TOKENIZED));
386         return d;
387     }
388 }