Add some unit test for StatisticNode, ClusterNode and DefaultNodeBuilder class (#423)

This commit is contained in:
cdfive 2019-01-29 10:07:17 +08:00 committed by Eric Zhao
parent e893dd8c64
commit 22e8d85a8f
3 changed files with 436 additions and 0 deletions

View File

@ -0,0 +1,149 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.csp.sentinel.node;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.junit.Assert.*;
/**
* Test cases for {@link ClusterNode}.
*
* @author cdfive
* @date 2019-01-11
*/
public class ClusterNodeTest {
@Test
public void testGetOrCreateOriginNodeSingleThread() {
ClusterNode clusterNode = new ClusterNode();
String origin1 = "origin1";
Node originNode1 = clusterNode.getOrCreateOriginNode(origin1);
assertNotNull(originNode1);
assertEquals(1, clusterNode.getOriginCountMap().size());
String origin2 = "origin2";
Node originNode2 = clusterNode.getOrCreateOriginNode(origin2);
assertNotNull(originNode2);
assertEquals(2, clusterNode.getOriginCountMap().size());
assertNotSame(originNode1, originNode2);
// test same origin, no StatisticNode added into the originCountMap
Node tmpOriginNode = clusterNode.getOrCreateOriginNode(origin1);
assertEquals(2, clusterNode.getOriginCountMap().size());
assertSame(tmpOriginNode, originNode1);
assertTrue(clusterNode.getOriginCountMap().containsKey(origin1));
assertTrue(clusterNode.getOriginCountMap().containsKey(origin2));
}
@Test
public void testGetOrCreateOriginNodeMultiThread() {
// note: in junit4, repeat execute a test method is not very convenient
// for simple, here use a loop instead
// https://stackoverflow.com/questions/1492856/easy-way-of-running-the-same-junit-test-over-and-over
// in junit5, use @RepeatedTest(10)
int testTimes = 10;// execute 10 times, test will have chance to failed, if remove the lock in ClusterNode
for (int times = 0; times < testTimes; times++) {
final ClusterNode clusterNode = new ClusterNode();
// store all distinct nodes by calling ClusterNode#getOrCreateOriginNode
final Set<Node> createdNodes = new HashSet<Node>();
final Random random = new Random();
// 10 threads, 3 origins, 20 tasks(in total, calling 20 times of ClusterNode#getOrCreateOriginNode concurrently)
final ExecutorService es = Executors.newFixedThreadPool(10);
final List<String> origins = Arrays.asList("origin1", "origin2", "origin3");
int taskCount = 20;
List<Callable<Object>> tasks = new ArrayList<Callable<Object>>(taskCount);
for (int i = 0; i < taskCount; i++) {
tasks.add(new Callable<Object>() {
@Override
public Object call() throws Exception {
// one task call one times of ClusterNode#getOrCreateOriginNode
Node node = clusterNode.getOrCreateOriginNode(origins.get(random.nextInt(origins.size())));
// add the result node to the createdNodes set
// node: since HashSet is non-threadsafe, synchronized the ClusterNodeTest.class
synchronized (ClusterNodeTest.class) {
createdNodes.add(node);
}
return null;
}
});
}
try {
es.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
es.shutdown();
// origins.size() origins, the same count as the originCountMap
assertEquals(origins.size(), clusterNode.getOriginCountMap().size());
// not use `assertEquals(origins.size(), createdNodes.size());`, but a compare judgement for debug
if (origins.size() != createdNodes.size()) {
// for debug, we can add a breakpoint here to see the detail info of createdNodes, if remove the lock in ClusterNode
fail("originCountMap's size should " + origins.size() + ", but actual " + createdNodes.size());
}
// verify originCountMap's key
for (String origin : origins) {
assertTrue(clusterNode.getOriginCountMap().containsKey(origin));
}
}
}
@Test
public void testTrace() {
ClusterNode clusterNode = new ClusterNode();
Exception exception = new RuntimeException("test");
// test count<=0, no exceptionQps added
clusterNode.trace(exception, 0);
clusterNode.trace(exception, -1);
assertEquals(0, clusterNode.exceptionQps());
assertEquals(0, clusterNode.totalException());
// test count=1, not BlockException, 1 exceptionQps added
clusterNode.trace(exception, 1);
assertEquals(1, clusterNode.exceptionQps());
assertEquals(1, clusterNode.totalException());
// test count=1, BlockException, no exceptionQps added
FlowException flowException = new FlowException("flow");
clusterNode.trace(flowException, 1);
assertEquals(1, clusterNode.exceptionQps());
assertEquals(1, clusterNode.totalException());
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.csp.sentinel.node;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link DefaultNodeBuilder}.
*
* @author cdfive
* @date 2019-01-15
*/
public class DefaultNodeBuilderTest {
@Test
public void testBuildTreeNode() {
DefaultNodeBuilder builder = new DefaultNodeBuilder();
ResourceWrapper id = new StringResourceWrapper("resA", EntryType.IN);
ClusterNode clusterNode = new ClusterNode();
DefaultNode defaultNode = builder.buildTreeNode(id, clusterNode);
assertNotNull(defaultNode);
assertEquals(id, defaultNode.getId());
assertEquals(clusterNode, defaultNode.getClusterNode());
// verify each call returns a different instance
DefaultNode defaultNode2 = builder.buildTreeNode(id, clusterNode);
assertNotNull(defaultNode2);
assertNotSame(defaultNode, defaultNode2);
// now DefaultNode#equals(Object) is not implemented, they are not equal
assertNotEquals(defaultNode, defaultNode2);
}
@Test
public void testBuildTreeNode_NullClusterNode() {
DefaultNodeBuilder builder = new DefaultNodeBuilder();
ResourceWrapper id = new StringResourceWrapper("resA", EntryType.IN);
DefaultNode defaultNode = builder.buildTreeNode(id, null);
assertNotNull(defaultNode);
assertEquals(id, defaultNode.getId());
assertEquals(null, defaultNode.getClusterNode());
// verify each call returns a different instance
DefaultNode defaultNode2 = builder.buildTreeNode(id, null);
assertNotNull(defaultNode2);
assertNotSame(defaultNode, defaultNode2);
// now DefaultNode#equals(Object) is not implemented, they are not equal
assertNotEquals(defaultNode, defaultNode2);
}
@Test
public void testBuildClusterNode() {
DefaultNodeBuilder builder = new DefaultNodeBuilder();
ClusterNode clusterNode = builder.buildClusterNode();
assertNotNull(clusterNode);
// verify each call returns a different instance
ClusterNode clusterNode2 = builder.buildClusterNode();
assertNotNull(clusterNode2);
assertNotSame(clusterNode, clusterNode2);
// as new a ClusterNode instance in DefaultNodeBuilder#buildClusterNode(), they are not equal
assertNotEquals(clusterNode, clusterNode2);
}
}

View File

@ -0,0 +1,202 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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 com.alibaba.csp.sentinel.node;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.util.TimeUtil;
import org.junit.Test;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Test cases for {@link StatisticNode}.
*
* @author cdfive
* @date 2019-01-08
*/
public class StatisticNodeTest {
private static final String LOG_PREFIX = "[StatisticNodeTest]";
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss");
private static final Random RANDOM = new Random();
private static final int THREAD_COUNT = 20;
/**
* A simple test for statistic threadNum and qps by using StatisticNode
*
* <p>
* 20 threads, 30 tasks, every task execute 10 times of bizMethod
* one bizMthod execute within 1 second, and within 0.5 second interval to exceute next bizMthod
* so that the total time cost will be within 1 minute
* </p>
*
* <p>
* Print the statistic info of StatisticNode and verify some results
* </p>
*/
@Test
public void testStatisticThreadNumAndQps() {
long testStartTime = TimeUtil.currentTimeMillis();
int taskCount = 30;
int taskBizExecuteCount = 10;
StatisticNode node = new StatisticNode();
ExecutorService bizEs = Executors.newFixedThreadPool(THREAD_COUNT);
ExecutorService tickEs = Executors.newSingleThreadExecutor();
tickEs.submit(new TickTask(node));
List<BizTask> bizTasks = new ArrayList<BizTask>(taskBizExecuteCount);
for (int i = 0; i < taskCount; i++) {
bizTasks.add(new BizTask(node, taskBizExecuteCount));
}
try {
bizEs.invokeAll(bizTasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("====================================================");
log("all biz task done, waiting 3 second to exit");
sleep(3000);
bizEs.shutdown();
tickEs.shutdown();
// now no biz method execute, so there is no curThreadNum,passQps,successQps
assertEquals(0, node.curThreadNum());
assertEquals(0, node.passQps());
assertEquals(0, node.successQps());
// note: total time cost should be controlled within 1 minute,
// as the node.totalRequest() holding statistics of recent 60 seconds
int totalRequest = taskCount * taskBizExecuteCount;
// verify totalRequest
assertEquals(totalRequest, node.totalRequest());
// as all execute success, totalRequest should equal to totalSuccess
assertEquals(totalRequest, node.totalSuccess());
// now there are no data in time span, so the minRT should be equals to TIME_DROP_VALVE
assertEquals(node.minRt(), Constants.TIME_DROP_VALVE);
log("====================================================");
log("testStatisticThreadNumAndQps done, cost " + (TimeUtil.currentTimeMillis() - testStartTime) + "ms");
}
private static class BizTask implements Callable<Object> {
private StatisticNode node;
private Integer bizExecuteCount;
public BizTask(StatisticNode node, Integer bizExecuteCount) {
this.node = node;
this.bizExecuteCount = bizExecuteCount;
}
@Override
public Object call() throws Exception {
while (true) {
node.increaseThreadNum();
node.addPassRequest(1);
long startTime = TimeUtil.currentTimeMillis();
bizMethod();
node.decreaseThreadNum();
// decrease one ThreadNum, so curThreadNum should less than THREAD_COUNT
assertTrue(node.curThreadNum() < THREAD_COUNT);
long rt = TimeUtil.currentTimeMillis() - startTime;
node.addRtAndSuccess(rt, 1);
// wait random 0.5 second for simulate method call interval,
// otherwise the curThreadNum will always be THREAD_COUNT at the beginning
sleep(RANDOM.nextInt(500));
bizExecuteCount--;
if (bizExecuteCount <= 0) {
break;
}
}
return null;
}
}
private static void bizMethod() {
// simulate biz method call in random 1 second
sleep(RANDOM.nextInt(1000));
}
private static class TickTask implements Runnable {
private StatisticNode node;
public TickTask(StatisticNode node) {
this.node = node;
}
@Override
public void run() {
while (true) {
// print statistic info every 1 second
sleep(1000);
// the curThreadNum should not greater than THREAD_COUNT
assertTrue(node.curThreadNum() <= THREAD_COUNT);
logNode(node);
}
}
}
private static void sleep(long ms) {
try {
TimeUnit.MILLISECONDS.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void logNode(StatisticNode node) {
log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps()
+ ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps()
+ ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess()
+ ",avgRt=" + node.avgRt() + ",minRt=" + node.minRt());
}
private static void log(Object obj) {
System.out.println(LOG_PREFIX + obj);
}
}