/* * 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.util.Map; import org.apache.lucene.util.LuceneTestCase; public class TestTwoPhaseCommitTool extends LuceneTestCase { private static class TwoPhaseCommitImpl implements TwoPhaseCommit { static boolean commitCalled = false; final boolean failOnPrepare; final boolean failOnCommit; final boolean failOnRollback; boolean rollbackCalled = false; Map<String, String> prepareCommitData = null; Map<String, String> commitData = null; public TwoPhaseCommitImpl(boolean failOnPrepare, boolean failOnCommit, boolean failOnRollback) { this.failOnPrepare = failOnPrepare; this.failOnCommit = failOnCommit; this.failOnRollback = failOnRollback; } @Override public long prepareCommit() throws IOException { return prepareCommit(null); } public long prepareCommit(Map<String, String> commitData) throws IOException { this.prepareCommitData = commitData; assertFalse("commit should not have been called before all prepareCommit were", commitCalled); if (failOnPrepare) { throw new IOException("failOnPrepare"); } return 1; } @Override public long commit() throws IOException { return commit(null); } public long commit(Map<String, String> commitData) throws IOException { this.commitData = commitData; commitCalled = true; if (failOnCommit) { throw new RuntimeException("failOnCommit"); } return 1; } @Override public void rollback() throws IOException { rollbackCalled = true; if (failOnRollback) { throw new Error("failOnRollback"); } } } @Override public void setUp() throws Exception { super.setUp(); TwoPhaseCommitImpl.commitCalled = false; // reset count before every test } public void testPrepareThenCommit() throws Exception { // tests that prepareCommit() is called on all objects before commit() TwoPhaseCommitImpl[] objects = new TwoPhaseCommitImpl[2]; for (int i = 0; i < objects.length; i++) { objects[i] = new TwoPhaseCommitImpl(false, false, false); } // following call will fail if commit() is called before all prepare() were TwoPhaseCommitTool.execute(objects); } public void testRollback() throws Exception { // tests that rollback is called if failure occurs at any stage int numObjects = random().nextInt(8) + 3; // between [3, 10] TwoPhaseCommitImpl[] objects = new TwoPhaseCommitImpl[numObjects]; for (int i = 0; i < objects.length; i++) { boolean failOnPrepare = random().nextBoolean(); // we should not hit failures on commit usually boolean failOnCommit = random().nextDouble() < 0.05; boolean railOnRollback = random().nextBoolean(); objects[i] = new TwoPhaseCommitImpl(failOnPrepare, failOnCommit, railOnRollback); } boolean anyFailure = false; try { TwoPhaseCommitTool.execute(objects); } catch (Throwable t) { anyFailure = true; } if (anyFailure) { // if any failure happened, ensure that rollback was called on all. for (TwoPhaseCommitImpl tpc : objects) { assertTrue("rollback was not called while a failure occurred during the 2-phase commit", tpc.rollbackCalled); } } } public void testNullTPCs() throws Exception { int numObjects = random().nextInt(4) + 3; // between [3, 6] TwoPhaseCommit[] tpcs = new TwoPhaseCommit[numObjects]; boolean setNull = false; for (int i = 0; i < tpcs.length; i++) { boolean isNull = random().nextDouble() < 0.3; if (isNull) { setNull = true; tpcs[i] = null; } else { tpcs[i] = new TwoPhaseCommitImpl(false, false, false); } } if (!setNull) { // none of the TPCs were picked to be null, pick one at random int idx = random().nextInt(numObjects); tpcs[idx] = null; } // following call would fail if TPCTool won't handle null TPCs properly TwoPhaseCommitTool.execute(tpcs); } }