/** * Copyright 2009 the original author or authors. * * Licensed 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 net.sf.katta.testutil; import java.io.File; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import net.sf.katta.master.Master; import net.sf.katta.node.Node; import net.sf.katta.protocol.InteractionProtocol; import net.sf.katta.protocol.metadata.IndexMetaData; import net.sf.katta.protocol.metadata.IndexMetaData.Shard; import net.sf.katta.util.FileUtil; import net.sf.katta.util.ZkConfiguration; import net.sf.katta.util.ZkConfiguration.PathDef; import org.I0Itec.zkclient.IDefaultNameSpace; import org.I0Itec.zkclient.ZkServer; import org.I0Itec.zkclient.exception.ZkNoNodeException; import org.apache.log4j.Logger; import org.mockito.Mockito; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Stubber; import static org.junit.Assert.assertEquals; import static org.fest.assertions.Assertions.assertThat; public class TestUtil { private static final Logger LOG = Logger.getLogger(TestUtil.class); /** * This waits until the provided {@link Callable} returns an object that is * equals to the given expected value or the timeout has been reached. In both * cases this method will return the return value of the latest * {@link Callable} execution. * * @param expectedValue * The expected value of the callable. * @param callable * The callable. * @param <T> * The return type of the callable. * @param timeUnit * The timeout timeunit. * @param timeout * The timeout. * @return the return value of the latest {@link Callable} execution. * @throws Exception * @throws InterruptedException */ public static <T> T waitUntil(T expectedValue, Callable<T> callable, TimeUnit timeUnit, long timeout) throws Exception { long startTime = System.currentTimeMillis(); do { T actual = callable.call(); if (expectedValue.equals(actual)) { return actual; } if (System.currentTimeMillis() > startTime + timeUnit.toMillis(timeout)) { return actual; } Thread.sleep(50); } while (true); } /** * This waits until a mockito verification passed (which is provided in the * runnable). This waits until the virification passed or the timeout has been * reached. If the timeout has been reached this method will rethrow the * {@link MockitoAssertionError} that comes from the mockito verification * code. * * @param runnable * The runnable containing the mockito verification. * @param timeUnit * The timeout timeunit. * @param timeout * The timeout. * @throws InterruptedException */ public static void waitUntilVerified(Runnable runnable, TimeUnit timeUnit, int timeout) throws InterruptedException { LOG.debug("Waiting for " + timeout + " " + timeUnit + " until verified."); long startTime = System.currentTimeMillis(); do { MockitoAssertionError exception = null; try { runnable.run(); } catch (MockitoAssertionError e) { exception = e; } if (exception == null) { return; } if (System.currentTimeMillis() > startTime + timeUnit.toMillis(timeout)) { LOG.debug("Timeout reached without satifying expectations."); throw exception; } Thread.sleep(50); } while (true); } public static void waitUntilNoExceptionThrown(Runnable runnable, TimeUnit timeUnit, int timeout) throws InterruptedException { long startTime = System.currentTimeMillis(); do { RuntimeException exception = null; try { runnable.run(); } catch (RuntimeException e) { exception = e; } if (exception == null) { return; } if (System.currentTimeMillis() > startTime + timeUnit.toMillis(timeout)) { throw exception; } Thread.sleep(50); } while (true); } /** * Creates a Mockito answer object that can be used for asynchronously * stubbing. For example: * * <pre> * final CountDownLatch countDownLatch = new CountDownLatch(1); * Mockito.doAnswer(TestUtil.createCountDownAnswer(countDownLatch)).when(listener).announceNode(nodeName); * mock.someMethod(someValue); * countDownLatch.await(10, TimeUnit.SECONDS); * Assert.assertEquals("expecting invocation within 10 seconds", 0, countDownLatch.getCount()); * </pre> */ public static Stubber createCountDownAnswer(final CountDownLatch countDownLatch) { return Mockito.doAnswer(new Answer<Object>() { public Object answer(InvocationOnMock invocation) throws Throwable { countDownLatch.countDown(); return null; } }); } public static ZkServer startZkServer(String testName, int port) { return startZkServer(testName, port, ZkServer.DEFAULT_TICK_TIME); } public static ZkServer startZkServer(String testName, int port, int tickTime) { String dataPath = "./build/test/" + testName + "/data"; String logPath = "./build/test/" + testName + "/log"; FileUtil.deleteFolder(new File(dataPath)); FileUtil.deleteFolder(new File(logPath)); ZkServer zkServer = new ZkServer(dataPath, logPath, Mockito.mock(IDefaultNameSpace.class), port, tickTime); zkServer.start(); return zkServer; } public static void waitUntilLeaveSafeMode(final Master master) throws Exception { waitUntil(false, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return master.isInSafeMode(); } }, TimeUnit.SECONDS, 30); assertEquals(false, master.isInSafeMode()); } public static void waitUntilBecomeMaster(final Master master) throws Exception { waitUntil(false, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return !master.isMaster(); } }, TimeUnit.SECONDS, 30); assertEquals(true, master.isMaster()); } public static void waitUntilIndexDeployed(final InteractionProtocol protocol, final String indexName) throws Exception { waitUntil(false, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return protocol.getIndexMD(indexName) == null; } }, TimeUnit.SECONDS, 30); } public static void waitUntilIndexBalanced(final InteractionProtocol protocol, final String indexName) throws Exception { waitUntil(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { IndexMetaData indexMD = protocol.getIndexMD(indexName); if (indexMD.hasDeployError()) { throw new IllegalStateException("index " + indexName + " has a deploy error"); } return protocol.getReplicationReport(indexMD).isBalanced(); } }, TimeUnit.SECONDS, 30); } public static void waitUntilShardsUndeployed(final InteractionProtocol protocol, final IndexMetaData indexMD) throws Exception { TestUtil.waitUntil(false, new Callable<Boolean>() { @Override public Boolean call() throws Exception { int nodeCount = 0; Set<Shard> shards = indexMD.getShards(); for (Shard shard : shards) { try { nodeCount += protocol.getShardNodes(shard.getName()).size(); } catch (ZkNoNodeException e) { // deleted already } } return nodeCount != 0; } }, TimeUnit.SECONDS, 30); } public static void waitUntilNodeServesShards(final InteractionProtocol protocol, final String nodeName, final int shardCount) throws Exception { waitUntil(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return protocol.getNodeShards(nodeName).size() == shardCount; } }, TimeUnit.SECONDS, 30); assertEquals(shardCount, protocol.getNodeShards(nodeName).size()); } public static void waitUntilNumberOfLiveNode(final InteractionProtocol protocol, final int nodeCount) throws Exception { waitUntil(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return protocol.getLiveNodes().size() == nodeCount; } }, TimeUnit.SECONDS, 30); assertEquals(nodeCount, protocol.getLiveNodes().size()); } public static void waitUntilEmptyOperationQueues(final InteractionProtocol protocol, final Master master, final List<Node> nodes) throws Exception { waitUntil(true, new Callable<Boolean>() { @Override public Boolean call() throws Exception { return operationQueuesEmpty(protocol, master, nodes); } }, TimeUnit.SECONDS, 30); assertThat(operationQueuesEmpty(protocol, master, nodes)).as("node operation queues are empty").isTrue(); } private static Boolean operationQueuesEmpty(final InteractionProtocol protocol, Master master, final List<Node> nodes) { ZkConfiguration zkConf = protocol.getZkConfiguration(); if (protocol.getZkClient().countChildren(zkConf.getZkPath(PathDef.MASTER_QUEUE, "operations")) > 0) { return false; } for (Node node : nodes) { if (protocol.getZkClient().countChildren(zkConf.getZkPath(PathDef.NODE_QUEUE, node.getName(), "operations")) > 0) { return false; } } return true; } }