Przeglądaj źródła

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

master
cdfive Eric Zhao 6 lat temu
rodzic
commit
22e8d85a8f
3 zmienionych plików z 436 dodań i 0 usunięć
  1. +149
    -0
      sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java
  2. +85
    -0
      sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/DefaultNodeBuilderTest.java
  3. +202
    -0
      sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java

+ 149
- 0
sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java Wyświetl plik

@@ -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());
}
}

+ 85
- 0
sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/DefaultNodeBuilderTest.java Wyświetl plik

@@ -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);
}
}

+ 202
- 0
sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/StatisticNodeTest.java Wyświetl plik

@@ -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);
}
}

Ładowanie…
Anuluj
Zapisz