diff --git a/dubbo-remoting/dubbo-remoting-api/pom.xml b/dubbo-remoting/dubbo-remoting-api/pom.xml index 73e160136e7..a083b2051ca 100644 --- a/dubbo-remoting/dubbo-remoting-api/pom.xml +++ b/dubbo-remoting/dubbo-remoting-api/pom.xml @@ -58,5 +58,11 @@ log4j-slf4j-impl test + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + diff --git a/dubbo-remoting/dubbo-remoting-http12/pom.xml b/dubbo-remoting/dubbo-remoting-http12/pom.xml index 4d16603471c..64725c11e0e 100644 --- a/dubbo-remoting/dubbo-remoting-http12/pom.xml +++ b/dubbo-remoting/dubbo-remoting-http12/pom.xml @@ -85,5 +85,11 @@ log4j-slf4j-impl test + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + diff --git a/dubbo-remoting/dubbo-remoting-http3/pom.xml b/dubbo-remoting/dubbo-remoting-http3/pom.xml index 4955a9ed08c..e9d2041e2f5 100644 --- a/dubbo-remoting/dubbo-remoting-http3/pom.xml +++ b/dubbo-remoting/dubbo-remoting-http3/pom.xml @@ -52,5 +52,11 @@ log4j-slf4j-impl test + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + diff --git a/dubbo-remoting/dubbo-remoting-netty/pom.xml b/dubbo-remoting/dubbo-remoting-netty/pom.xml index 94f3924efa7..b5b43f59aae 100644 --- a/dubbo-remoting/dubbo-remoting-netty/pom.xml +++ b/dubbo-remoting/dubbo-remoting-netty/pom.xml @@ -57,5 +57,11 @@ ${project.parent.version} test + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + diff --git a/dubbo-remoting/dubbo-remoting-netty4/pom.xml b/dubbo-remoting/dubbo-remoting-netty4/pom.xml index 06d548d1bdf..82aa4b2a0c9 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/pom.xml +++ b/dubbo-remoting/dubbo-remoting-netty4/pom.xml @@ -68,6 +68,12 @@ ${project.parent.version} test + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + org.apache.logging.log4j log4j-slf4j-impl diff --git a/dubbo-remoting/dubbo-remoting-websocket/pom.xml b/dubbo-remoting/dubbo-remoting-websocket/pom.xml index b27869d056a..551e8f195a5 100644 --- a/dubbo-remoting/dubbo-remoting-websocket/pom.xml +++ b/dubbo-remoting/dubbo-remoting-websocket/pom.xml @@ -37,5 +37,11 @@ dubbo-remoting-http12 ${project.parent.version} + + org.apache.dubbo + dubbo-test-check + ${project.parent.version} + test + diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml index 54de827c327..5556f236b56 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml +++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml @@ -39,13 +39,6 @@ ${project.parent.version} - - org.apache.dubbo - dubbo-test-common - ${project.parent.version} - test - - org.apache.curator curator-framework diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/test/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClientTest.java b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/test/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClientTest.java index e515fc6504b..4e784ee3e0f 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/test/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClientTest.java +++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/src/test/java/org/apache/dubbo/remoting/zookeeper/curator5/Curator5ZookeeperClientTest.java @@ -18,48 +18,153 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.configcenter.ConfigItem; +import org.apache.dubbo.remoting.zookeeper.curator5.Curator5ZookeeperClient.CuratorWatcherImpl; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import com.google.common.collect.Lists; +import org.apache.curator.CuratorZookeeperClient; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.framework.WatcherRemoveCuratorFramework; +import org.apache.curator.framework.api.CreateBuilder; +import org.apache.curator.framework.api.CuratorWatcher; +import org.apache.curator.framework.api.DeleteBuilder; +import org.apache.curator.framework.api.ExistsBuilder; +import org.apache.curator.framework.api.GetChildrenBuilder; +import org.apache.curator.framework.api.GetDataBuilder; +import org.apache.curator.framework.api.SetDataBuilder; +import org.apache.curator.framework.listen.StandardListenerManager; +import org.apache.curator.framework.recipes.cache.NodeCache; +import org.apache.zookeeper.KeeperException.NodeExistsException; import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher.Event; +import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.data.Stat; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.stubbing.Answer; import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstructionWithAnswer; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; class Curator5ZookeeperClientTest { private static Curator5ZookeeperClient curatorClient; private static CuratorFramework client = null; - private static int zookeeperServerPort1; + private static int zookeeperServerMockPort1; private static String zookeeperConnectionAddress1; + @Spy + CuratorFrameworkFactory.Builder spyBuilder = CuratorFrameworkFactory.builder(); + + private CuratorFramework mockCuratorFramework; + private CreateBuilder mockCreateBuilder; + private ExistsBuilder mockExistsBuilder; + private GetChildrenBuilder mockGetChildrenBuilder; + private DeleteBuilder mockDeleteBuilder; + private GetDataBuilder mockGetDataBuilder; + private SetDataBuilder mockSetDataBuilder; + private CuratorZookeeperClient mockCuratorZookeeperClient; + private WatcherRemoveCuratorFramework mockWatcherRemoveCuratorFramework; + @BeforeAll public static void setUp() throws Exception { - zookeeperConnectionAddress1 = System.getProperty("zookeeper.connection.address.1"); - zookeeperServerPort1 = Integer.parseInt( - zookeeperConnectionAddress1.substring(zookeeperConnectionAddress1.lastIndexOf(":") + 1)); + zookeeperServerMockPort1 = 2181; + zookeeperConnectionAddress1 = "zookeeper://localhost:" + zookeeperServerMockPort1; + + // mock begin + // create mock bean begin + CuratorFrameworkFactory.Builder realBuilder = CuratorFrameworkFactory.builder(); + CuratorFrameworkFactory.Builder spyBuilder = spy(realBuilder); + + MockedStatic curatorFrameworkFactoryMockedStatic = mockStatic(CuratorFrameworkFactory.class); + curatorFrameworkFactoryMockedStatic.when(CuratorFrameworkFactory::builder).thenReturn(spyBuilder); + } + + @BeforeEach + public void init() throws Exception { + mockCreateBuilder = mock(CreateBuilder.class); + mockExistsBuilder = mock(ExistsBuilder.class); + mockDeleteBuilder = mock(DeleteBuilder.class); + mockCuratorFramework = mock(CuratorFramework.class); + mockGetChildrenBuilder = mock(GetChildrenBuilder.class); + mockGetDataBuilder = mock(GetDataBuilder.class); + mockCuratorZookeeperClient = mock(CuratorZookeeperClient.class); + mockWatcherRemoveCuratorFramework = mock(WatcherRemoveCuratorFramework.class); + mockSetDataBuilder = mock(SetDataBuilder.class); + doReturn(mockCuratorFramework).when(spyBuilder).build(); + when(mockCuratorFramework.blockUntilConnected(anyInt(), any())).thenReturn(true); + when(mockCuratorFramework.getConnectionStateListenable()).thenReturn(StandardListenerManager.standard()); + when(mockCuratorFramework.create()).thenReturn(mockCreateBuilder); + when(mockCuratorFramework.checkExists()).thenReturn(mockExistsBuilder); + when(mockCuratorFramework.getChildren()).thenReturn(mockGetChildrenBuilder); + when(mockCuratorFramework.getZookeeperClient()).thenReturn(mockCuratorZookeeperClient); + when(mockCuratorFramework.newWatcherRemoveCuratorFramework()).thenReturn(mockWatcherRemoveCuratorFramework); + when(mockCuratorZookeeperClient.isConnected()).thenReturn(true); + when(mockCuratorFramework.delete()).thenReturn(mockDeleteBuilder); + when(mockCreateBuilder.withMode(any())).thenReturn(mockCreateBuilder); + when(mockDeleteBuilder.deletingChildrenIfNeeded()).thenReturn(mockDeleteBuilder); + when(mockDeleteBuilder.forPath(any())).then((Answer) invocationOnMock -> null); + when(mockCuratorFramework.getData()).thenReturn(mockGetDataBuilder); + when(mockCuratorFramework.setData()).thenReturn(mockSetDataBuilder); + List paths = new ArrayList<>(); + when(mockCreateBuilder.forPath(anyString())).thenAnswer(i-> { + String param = i.getArgument(0); + if (paths.contains(param)) { + throw new NodeExistsException("node existed: " + param); + } + paths.add(i.getArgument(0)); + return i.getArgument(0); + }); + when(mockCreateBuilder.forPath(anyString(), any())).thenAnswer(i-> { + String param = i.getArgument(0); + if (paths.contains(param)) { + throw new NodeExistsException("node existed: " + param); + } + paths.add(i.getArgument(0)); + return i.getArgument(0); + }); + when(mockExistsBuilder.forPath(anyString())).thenAnswer(i-> { + if (paths.contains(i.getArgument(0))) { + return new Stat(); + } + return null; + }); + when(mockDeleteBuilder.forPath(anyString())).thenAnswer(i-> { + if (paths.contains(i.getArgument(0))) { + paths.remove(i.getArgument(0)); + } + return null; + }); + + curatorClient = new Curator5ZookeeperClient( URL.valueOf(zookeeperConnectionAddress1 + "/org.apache.dubbo.registry.RegistryService")); - client = CuratorFrameworkFactory.newClient( - "127.0.0.1:" + zookeeperServerPort1, new ExponentialBackoffRetry(1000, 3)); - client.start(); } @Test @@ -71,7 +176,8 @@ void testCheckExists() { } @Test - void testChildrenPath() { + void testChildrenPath() throws Exception { + when(mockGetChildrenBuilder.forPath(any())).thenReturn(Lists.newArrayList("provider1", "provider2")); String path = "/dubbo/org.apache.dubbo.demo.DemoService/providers"; curatorClient.create(path, false, true); curatorClient.create(path + "/provider1", false, true); @@ -83,17 +189,21 @@ void testChildrenPath() { @Test @Timeout(value = 2) - public void testChildrenListener() throws InterruptedException { + public void testChildrenListener() throws Exception { String path = "/dubbo/org.apache.dubbo.demo.DemoListenerService/providers"; curatorClient.create(path, false, true); final CountDownLatch countDownLatch = new CountDownLatch(1); - curatorClient.addTargetChildListener(path, new Curator5ZookeeperClient.CuratorWatcherImpl() { + when(mockGetChildrenBuilder.usingWatcher(any(CuratorWatcher.class))).thenReturn(mockGetChildrenBuilder); + when(mockGetChildrenBuilder.forPath(any())).thenReturn(Lists.newArrayList("providers")); + CuratorWatcherImpl watcher = new CuratorWatcherImpl() { @Override public void process(WatchedEvent watchedEvent) { countDownLatch.countDown(); } - }); + }; + curatorClient.addTargetChildListener(path, watcher); + watcher.process(new WatchedEvent(Event.EventType.NodeDeleted, KeeperState.Closed, "providers")); curatorClient.createPersistent(path + "/provider1", true); countDownLatch.await(); } @@ -107,8 +217,10 @@ void testWithInvalidServer() { } @Test - void testRemoveChildrenListener() { + void testRemoveChildrenListener() throws Exception { ChildListener childListener = mock(ChildListener.class); + when(mockGetChildrenBuilder.usingWatcher(any(CuratorWatcher.class))).thenReturn(mockGetChildrenBuilder); + when(mockGetChildrenBuilder.forPath(any())).thenReturn(Lists.newArrayList("children")); curatorClient.addChildListener("/children", childListener); curatorClient.removeChildListener("/children", childListener); } @@ -127,26 +239,28 @@ void testConnectedStatus() { } @Test - void testCreateContent4Persistent() { + void testCreateContent4Persistent() throws Exception { String path = "/curatorTest4CrContent/content.data"; String content = "createContentTest"; curatorClient.delete(path); assertThat(curatorClient.checkExists(path), is(false)); assertNull(curatorClient.getContent(path)); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> content.getBytes()); curatorClient.createOrUpdate(path, content, false); assertThat(curatorClient.checkExists(path), is(true)); assertEquals(curatorClient.getContent(path), content); } @Test - void testCreateContent4Temp() { + void testCreateContent4Temp() throws Exception{ String path = "/curatorTest4CrContent/content.data"; String content = "createContentTest"; curatorClient.delete(path); assertThat(curatorClient.checkExists(path), is(false)); assertNull(curatorClient.getContent(path)); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> content.getBytes()); curatorClient.createOrUpdate(path, content, true); assertThat(curatorClient.checkExists(path), is(true)); assertEquals(curatorClient.getContent(path), content); @@ -197,27 +311,41 @@ void testAddTargetDataListener() throws Exception { String value = "vav"; curatorClient.createOrUpdate(path + "/d.json", value, true); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> value.getBytes()); String valueFromCache = curatorClient.getContent(path + "/d.json"); Assertions.assertEquals(value, valueFromCache); final AtomicInteger atomicInteger = new AtomicInteger(0); - curatorClient.addTargetDataListener(path + "/d.json", new Curator5ZookeeperClient.NodeCacheListenerImpl() { + NodeCache mockNodeCache = mock(NodeCache.class); + mockConstructionWithAnswer(NodeCache.class, invocationOnMock -> invocationOnMock.getMethod().invoke(mockNodeCache, invocationOnMock.getArguments())); + when(mockNodeCache.getListenable()).thenReturn(StandardListenerManager.standard()); + Curator5ZookeeperClient.NodeCacheListenerImpl nodeCacheListener = new Curator5ZookeeperClient.NodeCacheListenerImpl() { @Override public void nodeChanged() { atomicInteger.incrementAndGet(); } - }); + }; + curatorClient.addTargetDataListener(path + "/d.json", nodeCacheListener); valueFromCache = curatorClient.getContent(path + "/d.json"); Assertions.assertNotNull(valueFromCache); int currentCount1 = atomicInteger.get(); + when(mockSetDataBuilder.forPath(any(), any())).then(invocationOnMock -> { + nodeCacheListener.nodeChanged(); + return null; + }); curatorClient.getClient().setData().forPath(path + "/d.json", "foo".getBytes()); await().until(() -> atomicInteger.get() > currentCount1); int currentCount2 = atomicInteger.get(); curatorClient.getClient().setData().forPath(path + "/d.json", "bar".getBytes()); await().until(() -> atomicInteger.get() > currentCount2); int currentCount3 = atomicInteger.get(); + when(mockDeleteBuilder.forPath(any())).then(invocationOnMock -> { + nodeCacheListener.nodeChanged(); + return null; + }); curatorClient.delete(path + "/d.json"); + when(mockGetDataBuilder.forPath(any())).thenReturn(null); valueFromCache = curatorClient.getContent(path + "/d.json"); Assertions.assertNull(valueFromCache); await().until(() -> atomicInteger.get() > currentCount3); @@ -256,10 +384,10 @@ protected void update(String path, String data, int version) { throw new RuntimeException(e); } }); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> "version x".getBytes()); Assertions.assertThrows( IllegalStateException.class, () -> curatorClient.createOrUpdate(path, "version 1", false, 0)); Assertions.assertEquals("version x", curatorClient.getContent(path)); - client.setData().forPath(path, "version 1".getBytes(StandardCharsets.UTF_8)); ConfigItem configItem = curatorClient.getConfigItem(path); @@ -273,12 +401,14 @@ protected void update(String path, String data, int version) { int version1 = ((Stat) configItem.getTicket()).getVersion(); Assertions.assertThrows( IllegalStateException.class, () -> curatorClient.createOrUpdate(path, "version 2", false, version1)); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> "version x".getBytes()); Assertions.assertEquals("version x", curatorClient.getContent(path)); runnable.set(null); configItem = curatorClient.getConfigItem(path); int version2 = ((Stat) configItem.getTicket()).getVersion(); curatorClient.createOrUpdate(path, "version 2", false, version2); + when(mockGetDataBuilder.forPath(any())).then(invocationOnMock -> "version 2".getBytes()); Assertions.assertEquals("version 2", curatorClient.getContent(path)); curatorClient.close(); diff --git a/dubbo-remoting/pom.xml b/dubbo-remoting/pom.xml index 2546529bda7..3114ba96b28 100644 --- a/dubbo-remoting/pom.xml +++ b/dubbo-remoting/pom.xml @@ -40,12 +40,5 @@ false - - - org.apache.dubbo - dubbo-test-check - ${project.parent.version} - test - - +