blob: 9ef5a3094dfd947448aa011ab5031cfc4ff677df [file] [log] [blame]
/*
* 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);
}
}