/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Jun 2, 2010
*/
package com.bigdata.quorum;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import junit.framework.AssertionFailedError;
import com.bigdata.io.TestCase3;
import com.bigdata.quorum.MockQuorumFixture.MockQuorum;
import com.bigdata.quorum.MockQuorumFixture.MockQuorum.MockQuorumActor;
import com.bigdata.quorum.MockQuorumFixture.MockQuorumMember;
/**
* Abstract base class for testing using a {@link MockQuorumFixture}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
abstract public class AbstractQuorumTestCase extends TestCase3 {
public AbstractQuorumTestCase() {
}
public AbstractQuorumTestCase(String name) {
super(name);
}
/** The service replication factor (this must be set in {@link #setupUp()}). */
protected int k;
/** The per-client quorum objects. */
protected MockQuorum[] quorums;
/** The clients. */
protected MockQuorumMember[] clients;
/**
* The per-client quorum actor objects. The unit tests send events on the
* behalf of the clients using these actor objects.
*/
protected MockQuorumActor[] actors;
/** The mock shared quorum state object. */
protected MockQuorumFixture fixture;
@Override
protected void setUp() throws Exception {
super.setUp();
if(log.isInfoEnabled())
log.info(getName());
if (k == 0)
throw new AssertionError("k is not set");
quorums = new MockQuorum[k];
clients = new MockQuorumMember[k];
actors = new MockQuorumActor[k];
fixture = new MockQuorumFixture();
fixture.start();
/*
* Setup the client quorums.
*/
final String logicalServiceId = "testLogicalService1";
for (int i = 0; i < k; i++) {
quorums[i] = new MockQuorum(k,fixture);
clients[i] = new MockQuorumMember(logicalServiceId,fixture);
quorums[i].start(clients[i]);
actors [i] = quorums[i].getActor();
}
}
protected void tearDown() throws Exception {
if(log.isInfoEnabled())
log.info(getName());
if (quorums != null) {
for (int i = 0; i < k; i++) {
if (quorums[i] != null) {
quorums[i].terminate();
quorums[i] = null;
}
}
}
if (fixture != null) {
fixture.terminate();
}
quorums = null;
clients = null;
actors = null;
fixture = null;
}
/**
* Wait up to a timeout until some condition succeeds.
* <p>
* Whenever more than one {@link AbstractQuorum} is under test there will be
* concurrent indeterminism concerning the precise ordering and timing as
* updates propagate from the {@link AbstractQuorum} which takes some action
* (castVote(), pipelineAdd(), etc.) to the other quorums attached to the
* same {@link MockQuorumFixture}. This uncertainty about the ordering and
* timing state changes is not dissimilar from the uncertainty we face in a
* real distributed system.
* <p>
* While there are times when this uncertainty does not affect the behavior
* of the tests, there are other times when we must have a guarantee that a
* specific vote order or pipeline order was established. For those cases,
* this method may be used to await an arbitrary condition. This method
* simply retries until the condition becomes true, sleeping a little after
* each failure.
* <p>
* Actions executed in the main thread of the unit test will directly update
* the internal state of the {@link MockQuorumFixture}, which is shared
* across the {@link MockQuorum}s. However, uncertainty about ordering can
* arise as a result of the interleaving of the actions taken by the
* {@link QuorumWatcher}s in response to both top-level actions and actions
* taken by other {@link QuorumWatcher}s. For example, the vote order or the
* pipeline order are fully determined based on sequence such as the
* following:
*
* <pre>
* actor0.pipelineAdd();
* actor2.pipelineAdd();
* actor1.pipelineAdd();
* </pre>
*
* When in doubt, or when a unit test displays stochastic behavior, you can
* use this method to wait until the quorum state has been correctly
* replicated to the {@link Quorum}s under test.
*
* @param cond
* The condition, which must throw an
* {@link AssertionFailedError} if it does not succeed.
* @param timeout
* The timeout.
* @param unit
*
* @throws AssertionFailedError
* if the condition does not succeed within the timeout.
*/
static public void assertCondition(final Runnable cond,
final long timeout, final TimeUnit units) {
TestCase3.assertCondition(cond, timeout, units);
// final long begin = System.nanoTime();
// final long nanos = units.toNanos(timeout);
// long remaining = nanos;
// // remaining = nanos - (now - begin) [aka elapsed]
// remaining = nanos - (System.nanoTime() - begin);
// while (true) {
// try {
// // try the condition
// cond.run();
// // success.
// return;
// } catch (final AssertionFailedError e) {
// remaining = nanos - (System.nanoTime() - begin);
// if (remaining < 0) {
// // Timeout - rethrow the failed assertion.
// throw e;
// }
// // Sleep up to 10ms or the remaining nanos, which ever is less.
// final int millis = (int) Math.min(
// TimeUnit.NANOSECONDS.toMillis(remaining), 10);
// if (millis > 0) {
// // sleep and retry.
// try {
// Thread.sleep(millis);
// } catch (InterruptedException e1) {
// // propagate the interrupt.
// Thread.currentThread().interrupt();
// return;
// }
// remaining = nanos - (System.nanoTime() - begin);
// if (remaining < 0) {
// // Timeout - rethrow the failed assertion.
// throw e;
// }
// }
// }
// }
}
// /**
// * Waits up to 5 seconds for the condition to succeed.
// *
// * @param cond
// * The condition, which must throw an
// * {@link AssertionFailedError} if it does not succeed.
// *
// * @throws AssertionFailedError
// * if the condition does not succeed within the timeout.
// *
// * @see #assertCondition(Runnable, long, TimeUnit)
// */
// static public void assertCondition(final Runnable cond) {
//
// assertCondition(cond, 5, TimeUnit.SECONDS);
//
// }
/**
* Helper method provides nice rendering of a votes snapshot.
* <p>
* Note: The snapshot uses a {@link UUID}[] rather than a collection for
* each <code>lastCommitTime</code> key. However, by default toString() for
* an array does not provide a nice rendering.
*
* @param votes
* The votes.
* @return The human readable representation.
*/
public static String toString(final Map<Long, UUID[]> votes) {
// put things into a ordered Collection. toString() for the Collection is nice.
final Map<Long, LinkedHashSet<UUID>> m = new LinkedHashMap<Long, LinkedHashSet<UUID>>();
for(Map.Entry<Long,UUID[]> e : votes.entrySet()) {
final Long commitTime = e.getKey();
final UUID[] a = e.getValue();
LinkedHashSet<UUID> votesForCommitTime = m.get(commitTime);
if(votesForCommitTime == null) {
votesForCommitTime = new LinkedHashSet<UUID>();
m.put(commitTime, votesForCommitTime);
}
for (UUID uuid : a) {
votesForCommitTime.add(uuid);
}
}
return m.toString();
}
}