| /* |
| * 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. |
| */ |
| package org.apache.lucene.index; |
| |
| |
| import java.io.IOException; |
| import java.nio.file.Path; |
| |
| import org.apache.lucene.analysis.MockAnalyzer; |
| import org.apache.lucene.document.Document; |
| import org.apache.lucene.document.Field; |
| import org.apache.lucene.search.IndexSearcher; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.search.TopDocs; |
| import org.apache.lucene.store.Directory; |
| import org.apache.lucene.store.FSDirectory; |
| import org.apache.lucene.store.FilterDirectory; |
| import org.apache.lucene.store.IOContext; |
| import org.apache.lucene.store.IndexOutput; |
| import org.apache.lucene.util.LuceneTestCase; |
| |
| public class TestCrashCausesCorruptIndex extends LuceneTestCase { |
| |
| Path path; |
| |
| /** |
| * LUCENE-3627: This test fails. |
| */ |
| public void testCrashCorruptsIndexing() throws Exception { |
| path = createTempDir("testCrashCorruptsIndexing"); |
| |
| indexAndCrashOnCreateOutputSegments2(); |
| |
| searchForFleas(2); |
| |
| indexAfterRestart(); |
| |
| searchForFleas(3); |
| } |
| |
| /** |
| * index 1 document and commit. |
| * prepare for crashing. |
| * index 1 more document, and upon commit, creation of segments_2 will crash. |
| */ |
| private void indexAndCrashOnCreateOutputSegments2() throws IOException { |
| Directory realDirectory = FSDirectory.open(path); |
| CrashAfterCreateOutput crashAfterCreateOutput = new CrashAfterCreateOutput(realDirectory); |
| |
| // NOTE: cannot use RandomIndexWriter because it |
| // sometimes commits: |
| IndexWriter indexWriter = new IndexWriter(crashAfterCreateOutput, |
| newIndexWriterConfig(new MockAnalyzer(random()))); |
| |
| indexWriter.addDocument(getDocument()); |
| // writes segments_1: |
| indexWriter.commit(); |
| |
| crashAfterCreateOutput.setCrashAfterCreateOutput("pending_segments_2"); |
| indexWriter.addDocument(getDocument()); |
| // tries to write segments_2 but hits fake exc: |
| expectThrows(CrashingException.class, () -> { |
| indexWriter.commit(); |
| }); |
| |
| // writes segments_3 |
| indexWriter.close(); |
| assertFalse(slowFileExists(realDirectory, "segments_2")); |
| crashAfterCreateOutput.close(); |
| } |
| |
| /** |
| * Attempts to index another 1 document. |
| */ |
| private void indexAfterRestart() throws IOException { |
| Directory realDirectory = newFSDirectory(path); |
| |
| // LUCENE-3627 (before the fix): this line fails because |
| // it doesn't know what to do with the created but empty |
| // segments_2 file |
| IndexWriter indexWriter = new IndexWriter(realDirectory, |
| newIndexWriterConfig(new MockAnalyzer(random()))); |
| |
| // currently the test fails above. |
| // however, to test the fix, the following lines should pass as well. |
| indexWriter.addDocument(getDocument()); |
| indexWriter.close(); |
| assertFalse(slowFileExists(realDirectory, "segments_2")); |
| realDirectory.close(); |
| } |
| |
| /** |
| * Run an example search. |
| */ |
| private void searchForFleas(final int expectedTotalHits) throws IOException { |
| Directory realDirectory = newFSDirectory(path); |
| IndexReader indexReader = DirectoryReader.open(realDirectory); |
| IndexSearcher indexSearcher = newSearcher(indexReader); |
| TopDocs topDocs = indexSearcher.search(new TermQuery(new Term(TEXT_FIELD, "fleas")), 10); |
| assertNotNull(topDocs); |
| assertEquals(expectedTotalHits, topDocs.totalHits.value); |
| indexReader.close(); |
| realDirectory.close(); |
| } |
| |
| private static final String TEXT_FIELD = "text"; |
| |
| /** |
| * Gets a document with content "my dog has fleas". |
| */ |
| private Document getDocument() { |
| Document document = new Document(); |
| document.add(newTextField(TEXT_FIELD, "my dog has fleas", Field.Store.NO)); |
| return document; |
| } |
| |
| /** |
| * The marker RuntimeException that we use in lieu of an |
| * actual machine crash. |
| */ |
| private static class CrashingException extends RuntimeException { |
| public CrashingException(String msg) { |
| super(msg); |
| } |
| } |
| |
| /** |
| * This test class provides direct access to "simulating" a crash right after |
| * realDirectory.createOutput(..) has been called on a certain specified name. |
| */ |
| private static class CrashAfterCreateOutput extends FilterDirectory { |
| |
| private String crashAfterCreateOutput; |
| |
| public CrashAfterCreateOutput(Directory realDirectory) throws IOException { |
| super(realDirectory); |
| } |
| |
| public void setCrashAfterCreateOutput(String name) { |
| this.crashAfterCreateOutput = name; |
| } |
| |
| @Override |
| public IndexOutput createOutput(String name, IOContext cxt) throws IOException { |
| IndexOutput indexOutput = in.createOutput(name, cxt); |
| if (null != crashAfterCreateOutput && name.equals(crashAfterCreateOutput)) { |
| // CRASH! |
| indexOutput.close(); |
| if (VERBOSE) { |
| System.out.println("TEST: now crash"); |
| new Throwable().printStackTrace(System.out); |
| } |
| throw new CrashingException("crashAfterCreateOutput "+crashAfterCreateOutput); |
| } |
| return indexOutput; |
| } |
| |
| } |
| |
| } |