From 63b216a8a186383d1755cf88f267902c7b186d4e Mon Sep 17 00:00:00 2001 From: kamtohung Date: Mon, 6 Jul 2026 19:50:59 +0800 Subject: [PATCH 1/6] test(NoticeManager): add unit tests for notice handling --- .../notify/manager/NoticeManagerTest.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/manager/NoticeManagerTest.java diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/manager/NoticeManagerTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/manager/NoticeManagerTest.java new file mode 100644 index 000000000..31ca8e9c4 --- /dev/null +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/manager/NoticeManagerTest.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.dromara.dynamictp.test.core.notify.manager; + +import org.dromara.dynamictp.common.em.NotifyItemEnum; +import org.dromara.dynamictp.common.entity.NotifyItem; +import org.dromara.dynamictp.common.entity.TpMainFields; +import org.dromara.dynamictp.common.pattern.filter.Invoker; +import org.dromara.dynamictp.common.pattern.filter.InvokerChain; +import org.dromara.dynamictp.core.notifier.context.BaseNotifyCtx; +import org.dromara.dynamictp.core.notifier.context.NoticeCtx; +import org.dromara.dynamictp.core.notifier.manager.NoticeManager; +import org.dromara.dynamictp.core.support.ExecutorWrapper; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * NoticeManager test. + * + * @author codex + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = NoticeManagerTest.ContextConfig.class) +@Execution(ExecutionMode.SAME_THREAD) +class NoticeManagerTest { + + private final AtomicReference noticeContext = new AtomicReference<>(); + + private final AtomicInteger noticeCount = new AtomicInteger(); + + private InvokerChain noticeInvokerChain; + + private Invoker originalHead; + + private Field headField; + + private ThreadPoolExecutor executor; + + @BeforeEach + void setUp() throws Exception { + executor = new ThreadPoolExecutor( + 1, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + noticeInvokerChain = getNoticeInvokerChain(); + headField = InvokerChain.class.getDeclaredField("head"); + headField.setAccessible(true); + originalHead = (Invoker) headField.get(noticeInvokerChain); + Invoker testInvoker = context -> { + noticeContext.set(context); + noticeCount.incrementAndGet(); + }; + headField.set(noticeInvokerChain, testInvoker); + } + + @AfterEach + void tearDown() throws Exception { + headField.set(noticeInvokerChain, originalHead); + executor.shutdownNow(); + } + + @Test + void testDoTryNoticeTriggersWhenChangeNotifyItemExists() { + NotifyItem notifyItem = notifyItem(NotifyItemEnum.CHANGE); + ExecutorWrapper wrapper = executorWrapper("notice-change", notifyItem); + TpMainFields oldFields = new TpMainFields(); + List diffKeys = Collections.singletonList("corePoolSize"); + + NoticeManager.doTryNotice(wrapper, oldFields, diffKeys); + + assertEquals(1, noticeCount.get()); + assertTrue(noticeContext.get() instanceof NoticeCtx); + NoticeCtx noticeCtx = (NoticeCtx) noticeContext.get(); + assertSame(notifyItem, noticeCtx.getNotifyItem()); + assertEquals(NotifyItemEnum.CHANGE, noticeCtx.getNotifyItemEnum()); + assertEquals("notice-change", noticeCtx.getExecutorWrapper().getThreadPoolName()); + assertSame(oldFields, noticeCtx.getOldFields()); + assertSame(diffKeys, noticeCtx.getDiffs()); + } + + @Test + void testDoTryNoticeSkipsWhenChangeNotifyItemMissing() { + ExecutorWrapper wrapper = executorWrapper("notice-missing", notifyItem(NotifyItemEnum.LIVENESS)); + + NoticeManager.doTryNotice(wrapper, new TpMainFields(), Collections.singletonList("maximumPoolSize")); + + assertNoNotice(); + } + + private void assertNoNotice() { + assertEquals(0, noticeCount.get()); + assertNull(noticeContext.get()); + } + + private NotifyItem notifyItem(NotifyItemEnum notifyItemEnum) { + NotifyItem notifyItem = new NotifyItem(); + notifyItem.setType(notifyItemEnum.getValue()); + notifyItem.setPlatformIds(Collections.singletonList("platform")); + return notifyItem; + } + + private ExecutorWrapper executorWrapper(String poolName, NotifyItem... notifyItems) { + ExecutorWrapper wrapper = new ExecutorWrapper(poolName, executor); + wrapper.setNotifyItems(Arrays.asList(notifyItems)); + wrapper.setNotifyEnabled(true); + return wrapper; + } + + private InvokerChain getNoticeInvokerChain() throws Exception { + Field field = NoticeManager.class.getDeclaredField("NOTICE_INVOKER_CHAIN"); + field.setAccessible(true); + return (InvokerChain) field.get(null); + } + + @Configuration + static class ContextConfig { + + @Bean + org.dromara.dynamictp.spring.holder.SpringContextHolder springContextHolder() { + return new org.dromara.dynamictp.spring.holder.SpringContextHolder(); + } + } +} From 5f1a4aeace2aac1d91c11791dba0b2dd5f45048b Mon Sep 17 00:00:00 2001 From: kamtohung Date: Mon, 6 Jul 2026 20:00:16 +0800 Subject: [PATCH 2/6] test(CpuMetricsCaptor): add unit tests --- .../core/system/SystemMetricsCaptorTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/system/SystemMetricsCaptorTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/system/SystemMetricsCaptorTest.java index 50c8eb285..d0fd568cf 100644 --- a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/system/SystemMetricsCaptorTest.java +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/system/SystemMetricsCaptorTest.java @@ -19,12 +19,21 @@ import org.dromara.dynamictp.core.system.CpuMetricsCaptor; import org.dromara.dynamictp.core.system.MemoryMetricsCaptor; +import org.dromara.dynamictp.core.system.OperatingSystemBeanManager; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; /** * System metrics captor test. @@ -48,6 +57,55 @@ void testCpuMetricsCaptorRunDoesNotThrow() { assertFalse(Double.isNaN(captor.getProcessCpuUsage())); } + @Test + void testCpuMetricsCaptorRunUpdatesProcessCpuUsage() throws Exception { + CpuMetricsCaptor captor = new CpuMetricsCaptor(); + long previousProcessCpuTime = TimeUnit.MILLISECONDS.toNanos(200); + long newProcessCpuTime = TimeUnit.MILLISECONDS.toNanos(260); + long previousUpTime = Math.max(0L, getRuntimeUpTime() - 100L); + setField(captor, "prevProcessCpuTime", previousProcessCpuTime); + setField(captor, "prevUpTime", previousUpTime); + + try (MockedStatic mocked = mockStatic(OperatingSystemBeanManager.class)) { + mocked.when(OperatingSystemBeanManager::getProcessCpuTime).thenReturn(newProcessCpuTime); + + captor.run(); + + mocked.verify(OperatingSystemBeanManager::getProcessCpuTime); + } + + long capturedUpTime = (long) getField(captor, "prevUpTime"); + long elapsedTime = capturedUpTime - previousUpTime; + int cpuCores = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class).getAvailableProcessors(); + double expectedUsage = (double) TimeUnit.NANOSECONDS.toMillis(newProcessCpuTime - previousProcessCpuTime) + / elapsedTime / cpuCores; + + assertTrue(elapsedTime > 0); + assertEquals(newProcessCpuTime, getField(captor, "prevProcessCpuTime")); + assertEquals(expectedUsage, captor.getProcessCpuUsage(), 0.000001); + assertTrue(Double.isFinite(captor.getProcessCpuUsage())); + } + + @Test + void testCpuMetricsCaptorRunKeepsPreviousValueWhenCpuTimeFails() throws Exception { + CpuMetricsCaptor captor = new CpuMetricsCaptor(); + double previousUsage = 0.42D; + setField(captor, "currProcessCpuUsage", previousUsage); + setField(captor, "prevProcessCpuTime", 10L); + setField(captor, "prevUpTime", 20L); + + try (MockedStatic mocked = mockStatic(OperatingSystemBeanManager.class)) { + mocked.when(OperatingSystemBeanManager::getProcessCpuTime) + .thenThrow(new IllegalStateException("cpu time unavailable")); + + assertDoesNotThrow(captor::run); + } + + assertEquals(previousUsage, captor.getProcessCpuUsage(), 0.001); + assertEquals(10L, getField(captor, "prevProcessCpuTime")); + assertEquals(20L, getField(captor, "prevUpTime")); + } + @Test void testMemoryMetricsCaptorInitialValue() { MemoryMetricsCaptor captor = new MemoryMetricsCaptor(); @@ -63,4 +121,22 @@ void testMemoryMetricsCaptorRunDoesNotThrow() { double usage = captor.getLongLivedMemoryUsage(); assertTrue(usage == -1.0 || usage >= 0.0); } + + private static long getRuntimeUpTime() { + RuntimeMXBean runtimeBean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class); + return runtimeBean.getUptime(); + } + + private static Object getField(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } + + private static void setField(Object target, String fieldName, Object value) + throws NoSuchFieldException, IllegalAccessException { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } } From bb0940dc7674449d564f492693c4a86ff50371f9 Mon Sep 17 00:00:00 2001 From: kamtohung Date: Mon, 6 Jul 2026 20:02:06 +0800 Subject: [PATCH 3/6] test(RunTimeoutTimerTask): add unit tests --- .../core/timer/RunTimeoutTimerTaskTest.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java new file mode 100644 index 000000000..73f9ed148 --- /dev/null +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.dromara.dynamictp.test.core.timer; + +import org.dromara.dynamictp.core.executor.DtpExecutor; +import org.dromara.dynamictp.core.notifier.manager.AlarmManager; +import org.dromara.dynamictp.core.support.ExecutorWrapper; +import org.dromara.dynamictp.core.support.ThreadPoolBuilder; +import org.dromara.dynamictp.core.timer.RunTimeoutTimerTask; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.dromara.dynamictp.common.em.NotifyItemEnum.RUN_TIMEOUT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mockStatic; + +/** + * RunTimeoutTimerTask test + */ +class RunTimeoutTimerTaskTest { + + private DtpExecutor dtpExecutor; + + @AfterEach + void tearDown() { + if (dtpExecutor != null) { + dtpExecutor.shutdownNow(); + } + } + + @Test + void testRunIncrementsRunTimeoutCountAndTriggersAlarm() throws Exception { + dtpExecutor = ThreadPoolBuilder.newBuilder() + .threadPoolName("run-timeout-task") + .runTimeout(100) + .buildDynamic(); + ExecutorWrapper wrapper = ExecutorWrapper.of(dtpExecutor); + Runnable runnable = () -> { }; + Thread runningThread = new Thread(); + RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + + try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { + alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) + .thenAnswer(invocation -> null); + + task.run(null); + + assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); + assertFalse(runningThread.isInterrupted()); + alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + } + } + + @Test + void testRunInterruptsThreadWhenTryInterruptEnabled() throws Exception { + dtpExecutor = ThreadPoolBuilder.newBuilder() + .threadPoolName("run-timeout-interrupt") + .runTimeout(100) + .tryInterrupt(true) + .buildDynamic(); + ExecutorWrapper wrapper = ExecutorWrapper.of(dtpExecutor); + Runnable runnable = () -> { }; + Thread runningThread = new Thread(); + RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + + try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { + alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) + .thenAnswer(invocation -> null); + + task.run(null); + + assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); + assertTrue(runningThread.isInterrupted()); + alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + } + } +} From 82f5968ea34a071a5b1d0ecf2155df109667df98 Mon Sep 17 00:00:00 2001 From: kamtohung Date: Tue, 7 Jul 2026 01:42:44 +0800 Subject: [PATCH 4/6] Skip broken SPI providers --- .../common/util/ExtensionServiceLoader.java | 21 ++++++++++++-- .../test/common/util/BrokenTestService.java | 24 ++++++++++++++++ .../util/BrokenTestServiceBrokenImpl.java | 28 +++++++++++++++++++ .../common/util/BrokenTestServiceImpl.java | 24 ++++++++++++++++ ...namictp.test.common.util.BrokenTestService | 2 ++ 5 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestService.java create mode 100644 test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceBrokenImpl.java create mode 100644 test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceImpl.java create mode 100644 test/test-common/src/test/resources/META-INF/services/org.dromara.dynamictp.test.common.util.BrokenTestService diff --git a/common/src/main/java/org/dromara/dynamictp/common/util/ExtensionServiceLoader.java b/common/src/main/java/org/dromara/dynamictp/common/util/ExtensionServiceLoader.java index 2b512a125..f736c2de3 100644 --- a/common/src/main/java/org/dromara/dynamictp/common/util/ExtensionServiceLoader.java +++ b/common/src/main/java/org/dromara/dynamictp/common/util/ExtensionServiceLoader.java @@ -17,11 +17,14 @@ package org.dromara.dynamictp.common.util; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; @@ -31,6 +34,7 @@ * @author xs.Tao * @since 1.1.4 */ +@Slf4j public class ExtensionServiceLoader { private static final Map, List> EXTENSION_MAP = new ConcurrentHashMap<>(); @@ -68,9 +72,22 @@ public static T getFirst(Class clazz) { private static List load(Class clazz) { ServiceLoader serviceLoader = ServiceLoader.load(clazz); + Iterator iterator = serviceLoader.iterator(); List services = new ArrayList<>(); - for (T service : serviceLoader) { - services.add(service); + while (true) { + try { + if (!iterator.hasNext()) { + break; + } + } catch (ServiceConfigurationError e) { + log.warn("Failed to load {} provider, skip remaining providers.", clazz.getName(), e); + break; + } + try { + services.add(iterator.next()); + } catch (ServiceConfigurationError e) { + log.warn("Failed to load {} provider, skip it.", clazz.getName(), e); + } } return services; } diff --git a/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestService.java b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestService.java new file mode 100644 index 000000000..c8346d636 --- /dev/null +++ b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestService.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.dromara.dynamictp.test.common.util; + +/** + * Broken SPI loading test service. + */ +public interface BrokenTestService { +} diff --git a/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceBrokenImpl.java b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceBrokenImpl.java new file mode 100644 index 000000000..28411ef78 --- /dev/null +++ b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceBrokenImpl.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.dromara.dynamictp.test.common.util; + +/** + * Broken SPI implementation used to verify bad providers are skipped. + */ +public class BrokenTestServiceBrokenImpl implements BrokenTestService { + + public BrokenTestServiceBrokenImpl() { + throw new IllegalStateException("broken spi provider"); + } +} diff --git a/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceImpl.java b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceImpl.java new file mode 100644 index 000000000..b8966b40a --- /dev/null +++ b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/util/BrokenTestServiceImpl.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.dromara.dynamictp.test.common.util; + +/** + * Valid SPI implementation used by ExtensionServiceLoader tests. + */ +public class BrokenTestServiceImpl implements BrokenTestService { +} diff --git a/test/test-common/src/test/resources/META-INF/services/org.dromara.dynamictp.test.common.util.BrokenTestService b/test/test-common/src/test/resources/META-INF/services/org.dromara.dynamictp.test.common.util.BrokenTestService new file mode 100644 index 000000000..943eedbb5 --- /dev/null +++ b/test/test-common/src/test/resources/META-INF/services/org.dromara.dynamictp.test.common.util.BrokenTestService @@ -0,0 +1,2 @@ +org.dromara.dynamictp.test.common.util.BrokenTestServiceBrokenImpl +org.dromara.dynamictp.test.common.util.BrokenTestServiceImpl From a72aad423a6b0e33ea12573b03efba7f88e8074b Mon Sep 17 00:00:00 2001 From: kamtohung Date: Tue, 7 Jul 2026 10:11:34 +0800 Subject: [PATCH 5/6] =?UTF-8?q?test(ContextManagerHelper):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9Spring=E4=B8=8A=E4=B8=8B=E6=96=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/ContextManagerHelperTest.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/test/test-common/src/test/java/org/dromara/dynamictp/test/common/manager/ContextManagerHelperTest.java b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/manager/ContextManagerHelperTest.java index fd47f0f03..10b28a5e0 100644 --- a/test/test-common/src/test/java/org/dromara/dynamictp/test/common/manager/ContextManagerHelperTest.java +++ b/test/test-common/src/test/java/org/dromara/dynamictp/test/common/manager/ContextManagerHelperTest.java @@ -18,22 +18,76 @@ package org.dromara.dynamictp.test.common.manager; import org.dromara.dynamictp.common.manager.ContextManagerHelper; -import org.junit.jupiter.api.Assertions; +import org.dromara.dynamictp.spring.holder.SpringContextHolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.MapPropertySource; + +import java.lang.reflect.Field; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * ContextManagerHelperTest related. */ +@Execution(ExecutionMode.SAME_THREAD) class ContextManagerHelperTest { + private static final String TEST_PROPERTY_KEY = "context.manager.test.key"; + + private GenericApplicationContext context; + + private ApplicationContext originalContext; + + @BeforeEach + void setUp() throws Exception { + originalContext = springContext(); + context = new GenericApplicationContext(); + context.getBeanFactory().registerSingleton("sampleBean", new SampleBean()); + context.getEnvironment().getPropertySources().addFirst( + new MapPropertySource("contextManagerTest", Collections.singletonMap(TEST_PROPERTY_KEY, "test-value"))); + context.refresh(); + new SpringContextHolder().setApplicationContext(context); + } + + @AfterEach + void tearDown() throws Exception { + context.close(); + setSpringContext(originalContext); + } + @Test - void testFallbackToNullContextManagerWhenNoImplementationFound() { - Assertions.assertNull(ContextManagerHelper.getBean(Object.class)); - Assertions.assertNull(ContextManagerHelper.getBean("bean", Object.class)); - Assertions.assertTrue(ContextManagerHelper.getBeansOfType(Object.class).isEmpty()); - Assertions.assertNull(ContextManagerHelper.getEnvironment()); - Assertions.assertNull(ContextManagerHelper.getEnvironmentProperty("key")); - Assertions.assertNull(ContextManagerHelper.getEnvironmentProperty("key", new Object())); - Assertions.assertNull(ContextManagerHelper.getEnvironmentProperty("key", "default")); + void testDelegateToSpringContextManagerWhenImplementationFound() { + SampleBean sampleBean = ContextManagerHelper.getBean(SampleBean.class); + + assertSame(sampleBean, ContextManagerHelper.getBean("sampleBean", SampleBean.class)); + assertTrue(ContextManagerHelper.getBeansOfType(SampleBean.class).containsValue(sampleBean)); + assertSame(context.getEnvironment(), ContextManagerHelper.getEnvironment()); + assertEquals("test-value", ContextManagerHelper.getEnvironmentProperty(TEST_PROPERTY_KEY)); + assertEquals("test-value", ContextManagerHelper.getEnvironmentProperty(TEST_PROPERTY_KEY, context.getEnvironment())); + assertEquals("default", ContextManagerHelper.getEnvironmentProperty("missing.key", "default")); + } + + private ApplicationContext springContext() throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + return (ApplicationContext) field.get(null); + } + + private void setSpringContext(ApplicationContext applicationContext) throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + field.set(null, applicationContext); + } + + private static class SampleBean { } } From 6a4405bea314d3276281c208b647beb799021e5d Mon Sep 17 00:00:00 2001 From: kamtohung Date: Tue, 7 Jul 2026 11:39:51 +0800 Subject: [PATCH 6/6] =?UTF-8?q?test(ContextManagerHelper):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9Spring=E4=B8=8A=E4=B8=8B=E6=96=87=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/notifier/alarm/AlarmCounter.java | 8 ++ .../core/notify/invoker/AlarmInvokerTest.java | 6 +- .../core/timer/QueueTimeoutTimerTaskTest.java | 41 ++++++- .../core/timer/RunTimeoutTimerTaskTest.java | 110 +++++++++++++++--- 4 files changed, 144 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/dromara/dynamictp/core/notifier/alarm/AlarmCounter.java b/core/src/main/java/org/dromara/dynamictp/core/notifier/alarm/AlarmCounter.java index bfab77e1a..9ade82fd3 100644 --- a/core/src/main/java/org/dromara/dynamictp/core/notifier/alarm/AlarmCounter.java +++ b/core/src/main/java/org/dromara/dynamictp/core/notifier/alarm/AlarmCounter.java @@ -41,6 +41,8 @@ public class AlarmCounter { private static final Map> ALARM_INFO_CACHE = new ConcurrentHashMap<>(); + private static final Map ALARM_PERIOD_CACHE = new ConcurrentHashMap<>(); + private static final Map LAST_ALARM_TIME_MAP = new ConcurrentHashMap<>(); private AlarmCounter() { } @@ -51,10 +53,16 @@ public static void initAlarmCounter(String threadPoolName, NotifyItem notifyItem } String key = buildKey(threadPoolName, notifyItem.getType()); + if (Objects.nonNull(ALARM_INFO_CACHE.get(key)) + && Objects.equals(ALARM_PERIOD_CACHE.get(key), notifyItem.getPeriod())) { + return; + } + Cache cache = CacheBuilder.newBuilder() .expireAfterWrite(notifyItem.getPeriod(), TimeUnit.SECONDS) .build(); ALARM_INFO_CACHE.put(key, cache); + ALARM_PERIOD_CACHE.put(key, notifyItem.getPeriod()); } public static AlarmInfo getAlarmInfo(String threadPoolName, String notifyType) { diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/invoker/AlarmInvokerTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/invoker/AlarmInvokerTest.java index 74ef6107d..553f85eab 100644 --- a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/invoker/AlarmInvokerTest.java +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/notify/invoker/AlarmInvokerTest.java @@ -18,6 +18,7 @@ package org.dromara.dynamictp.test.core.notify.invoker; import org.dromara.dynamictp.common.em.NotifyItemEnum; +import org.dromara.dynamictp.common.entity.AlarmInfo; import org.dromara.dynamictp.common.entity.NotifyItem; import org.dromara.dynamictp.common.entity.NotifyPlatform; import org.dromara.dynamictp.common.entity.TpMainFields; @@ -104,11 +105,14 @@ void testInvokeSendsAlarmResetsCounterAndClearsContext() { AlarmCounter.initAlarmCounter(poolName, notifyItem); AlarmCounter.incAlarmCount(poolName, notifyItem.getType()); AlarmCounter.incAlarmCount(poolName, notifyItem.getType()); + AlarmInfo alarmInfo = AlarmCounter.getAlarmInfo(poolName, notifyItem.getType()); + assertEquals(2, alarmInfo.getCount()); new AlarmInvoker().invoke(new AlarmCtx(new ExecutorWrapper(poolName, executor), notifyItem)); assertEquals(NotifyItemEnum.REJECT, notifier.notifyItemEnum); - assertEquals(0, AlarmCounter.getAlarmInfo(poolName, notifyItem.getType()).getCount()); + assertSame(alarmInfo, AlarmCounter.getAlarmInfo(poolName, notifyItem.getType())); + assertEquals(0, alarmInfo.getCount()); assertEquals(platform().getPlatformId(), notifier.platform.getPlatformId()); assertNull(DtpNotifyCtxHolder.get()); } diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/QueueTimeoutTimerTaskTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/QueueTimeoutTimerTaskTest.java index 56b922a4c..b3cd788f9 100644 --- a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/QueueTimeoutTimerTaskTest.java +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/QueueTimeoutTimerTaskTest.java @@ -22,9 +22,18 @@ import org.dromara.dynamictp.core.support.ExecutorWrapper; import org.dromara.dynamictp.core.support.ThreadPoolBuilder; import org.dromara.dynamictp.core.timer.QueueTimeoutTimerTask; +import org.dromara.dynamictp.spring.holder.SpringContextHolder; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceLock; import org.mockito.MockedStatic; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; + +import java.lang.reflect.Field; import static org.dromara.dynamictp.common.em.NotifyItemEnum.QUEUE_TIMEOUT; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,15 +43,33 @@ /** * QueueTimeoutTimerTask test */ +@Execution(ExecutionMode.SAME_THREAD) +@ResourceLock("SPRING_CONTEXT_HOLDER") class QueueTimeoutTimerTaskTest { private DtpExecutor dtpExecutor; + private GenericApplicationContext context; + + private ApplicationContext originalContext; + + @BeforeEach + void setUp() throws Exception { + originalContext = springContext(); + context = new GenericApplicationContext(); + context.refresh(); + new SpringContextHolder().setApplicationContext(context); + } + @AfterEach - void tearDown() { + void tearDown() throws Exception { if (dtpExecutor != null) { dtpExecutor.shutdownNow(); } + if (context != null) { + context.close(); + } + setSpringContext(originalContext); } @Test @@ -65,4 +92,16 @@ void testRunIncrementsQueueTimeoutCountAndTriggersAlarm() throws Exception { alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(QUEUE_TIMEOUT), same(runnable))); } } + + private ApplicationContext springContext() throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + return (ApplicationContext) field.get(null); + } + + private void setSpringContext(ApplicationContext applicationContext) throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + field.set(null, applicationContext); + } } diff --git a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java index 73f9ed148..9fbec1d81 100644 --- a/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java +++ b/test/test-core/src/test/java/org/dromara/dynamictp/test/core/timer/RunTimeoutTimerTaskTest.java @@ -22,9 +22,21 @@ import org.dromara.dynamictp.core.support.ExecutorWrapper; import org.dromara.dynamictp.core.support.ThreadPoolBuilder; import org.dromara.dynamictp.core.timer.RunTimeoutTimerTask; +import org.dromara.dynamictp.spring.holder.SpringContextHolder; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceLock; import org.mockito.MockedStatic; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; + +import java.lang.reflect.Field; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; import static org.dromara.dynamictp.common.em.NotifyItemEnum.RUN_TIMEOUT; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,15 +48,35 @@ /** * RunTimeoutTimerTask test */ +@Execution(ExecutionMode.SAME_THREAD) +@ResourceLock("SPRING_CONTEXT_HOLDER") class RunTimeoutTimerTaskTest { + private static final long WAIT_TIMEOUT_SECONDS = 1; + private DtpExecutor dtpExecutor; + private GenericApplicationContext context; + + private ApplicationContext originalContext; + + @BeforeEach + void setUp() throws Exception { + originalContext = springContext(); + context = new GenericApplicationContext(); + context.refresh(); + new SpringContextHolder().setApplicationContext(context); + } + @AfterEach - void tearDown() { + void tearDown() throws Exception { if (dtpExecutor != null) { dtpExecutor.shutdownNow(); } + if (context != null) { + context.close(); + } + setSpringContext(originalContext); } @Test @@ -55,18 +87,27 @@ void testRunIncrementsRunTimeoutCountAndTriggersAlarm() throws Exception { .buildDynamic(); ExecutorWrapper wrapper = ExecutorWrapper.of(dtpExecutor); Runnable runnable = () -> { }; - Thread runningThread = new Thread(); - RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + CountDownLatch started = new CountDownLatch(1); + CountDownLatch interrupted = new CountDownLatch(1); + Thread runningThread = newInterruptWaitingThread("run-timeout-task-worker", started, interrupted); + runningThread.start(); + assertTrue(started.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { - alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) - .thenAnswer(invocation -> null); + try { + RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { + alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) + .thenAnswer(invocation -> null); - task.run(null); + task.run(null); - assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); - assertFalse(runningThread.isInterrupted()); - alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); + assertFalse(runningThread.isInterrupted()); + alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + } + } finally { + runningThread.interrupt(); + runningThread.join(TimeUnit.SECONDS.toMillis(WAIT_TIMEOUT_SECONDS)); } } @@ -79,18 +120,49 @@ void testRunInterruptsThreadWhenTryInterruptEnabled() throws Exception { .buildDynamic(); ExecutorWrapper wrapper = ExecutorWrapper.of(dtpExecutor); Runnable runnable = () -> { }; - Thread runningThread = new Thread(); - RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + CountDownLatch started = new CountDownLatch(1); + CountDownLatch interrupted = new CountDownLatch(1); + Thread runningThread = newInterruptWaitingThread("run-timeout-interrupt-worker", started, interrupted); + runningThread.start(); + assertTrue(started.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)); - try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { - alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) - .thenAnswer(invocation -> null); + try { + RunTimeoutTimerTask task = new RunTimeoutTimerTask(wrapper, runnable, runningThread); + try (MockedStatic alarmManager = mockStatic(AlarmManager.class)) { + alarmManager.when(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))) + .thenAnswer(invocation -> null); - task.run(null); + task.run(null); - assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); - assertTrue(runningThread.isInterrupted()); - alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + assertEquals(1, wrapper.getThreadPoolStatProvider().getRunTimeoutCount()); + assertTrue(interrupted.await(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)); + alarmManager.verify(() -> AlarmManager.tryAlarmAsync(same(wrapper), same(RUN_TIMEOUT), same(runnable))); + } + } finally { + runningThread.interrupt(); + runningThread.join(TimeUnit.SECONDS.toMillis(WAIT_TIMEOUT_SECONDS)); } } + + private Thread newInterruptWaitingThread(String name, CountDownLatch started, CountDownLatch interrupted) { + return new Thread(() -> { + started.countDown(); + while (!Thread.currentThread().isInterrupted()) { + LockSupport.park(); + } + interrupted.countDown(); + }, name); + } + + private ApplicationContext springContext() throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + return (ApplicationContext) field.get(null); + } + + private void setSpringContext(ApplicationContext applicationContext) throws Exception { + Field field = SpringContextHolder.class.getDeclaredField("context"); + field.setAccessible(true); + field.set(null, applicationContext); + } }