This is an automated email from the ASF dual-hosted git repository.
zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 8d0d5761d83 Add StatisticsCollectJobWorkerTest (#37088)
8d0d5761d83 is described below
commit 8d0d5761d836ab1489f09aa8ccf7efb35fcaf842
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Nov 13 19:48:21 2025 +0800
Add StatisticsCollectJobWorkerTest (#37088)
* Add StatisticsCollectJobWorkerTest
* Add StatisticsCollectJobWorkerTest
* Add StatisticsCollectJobWorkerTest
---
AGENTS.md | 4 +-
.../collect/StatisticsCollectJobWorkerTest.java | 142 ++++++++++++++++++++-
2 files changed, 141 insertions(+), 5 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 5cb50e0028e..3022ccd1d88 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -40,7 +40,7 @@ Mention which topology you target, the registry used, and any
compatibility cons
- **Styles to favor:** elegant, minimal solutions—express intent with the
smallest construct that works, keep methods/tests lean, and avoid incidental
complexity.
- **Patterns to lean on:** builder/factory helpers in `infra`, SPI-based
extension points, immutable DTOs for plan descriptions, explicit strategy enums
for behavior toggles.
- **Anti-patterns:** duplicating SQL parsing logic, bypassing metadata caches,
silent fallbacks when configuration is invalid, adding static singletons in
shared modules, over-engineering simple flows.
-- **Known pitfalls:** routing regressions when skipping shadow rules, timezone
drift when mocking time poorly, forgetting to validate both standalone and
cluster (`mode`) settings, missing ASF headers in new files.
+- **Known pitfalls:** routing regressions when skipping shadow rules, timezone
drift when mocking time poorly, forgetting to validate both standalone and
cluster (`mode`) settings, missing ASF headers in new files, Mockito inline
mocks failing on GraalVM or other JDKs that block self-attach (run
inline-mocking suites on HotSpot/Temurin or document the limitation),
reflexively poking private methods instead of driving logic through public APIs.
- **Success recipe:** describe why a change is needed, point to affected data
flow step, keep public APIs backwards compatible, and document defaults in
`docs`.
- **Case in point:** a prior shadow-rule regression was fixed by (1)
reproducing via `proxy` + sample config, (2) adding a `kernel` unit test
covering the skipped branch, (3) updating docs with the exact YAML flag—mirror
that discipline for new features.
@@ -67,7 +67,7 @@ Mention which topology you target, the registry used, and any
compatibility cons
## Testing Expectations
- Use JUnit 5 + Mockito; tests mirror package paths and follow the
`ClassNameTest` convention.
- Method names read `assertXxxCondition`; structure tests as
Arrange–Act–Assert sections with explicit separators/comments when clarity
drops.
-- Mock databases, time, and network boundaries; build POJOs directly.
+- Mock databases, time, and network boundaries; build POJOs directly. When
production code keeps static guards or caches, add shared setup/teardown
helpers that reset them between tests so cases stay isolated.
- When Jacoco fails, open `{module}/target/site/jacoco/index.html`, note
uncovered branches, and explain how new tests address them.
- Need a quick coverage view? Run `./mvnw -pl {module} -am -Djacoco.skip=false
test jacoco:report` and open `{module}/target/site/jacoco/index.html`.
- Jacoco is disabled by default (the top-level POM sets `jacoco.skip=true`),
so explicitly pass `-Djacoco.skip=false` when you need coverage data, then run
`jacoco:report` for the same module scope.
diff --git
a/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
b/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
index 103154035cf..1b7c4d83284 100644
---
a/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
+++
b/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
@@ -18,33 +18,76 @@
package org.apache.shardingsphere.schedule.core.job.statistics.collect;
import lombok.SneakyThrows;
+import org.apache.shardingsphere.elasticjob.api.JobConfiguration;
+import org.apache.shardingsphere.elasticjob.infra.pojo.JobConfigurationPOJO;
import
org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobBootstrap;
+import
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.operate.JobOperateAPIImpl;
+import
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.settings.JobConfigurationAPIImpl;
+import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
+import
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
+import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
+import
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationProperties;
+import
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationPropertyKey;
+import org.apache.shardingsphere.infra.util.props.PropertiesBuilder;
+import org.apache.shardingsphere.infra.util.props.PropertiesBuilder.Property;
import org.apache.shardingsphere.mode.manager.ContextManager;
+import
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+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.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.MockedConstruction;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class StatisticsCollectJobWorkerTest {
+ private static AtomicBoolean workerInitialized = new AtomicBoolean(false);
+
private StatisticsCollectJobWorker jobWorker;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ContextManager contextManager;
@BeforeEach
+ @SneakyThrows(ReflectiveOperationException.class)
void setUp() {
+ workerInitialized = (AtomicBoolean)
Plugins.getMemberAccessor().get(StatisticsCollectJobWorker.class.getDeclaredField("WORKER_INITIALIZED"),
StatisticsCollectJobWorker.class);
jobWorker = new StatisticsCollectJobWorker();
}
+ @AfterEach
+ void tearDown() {
+ workerInitialized.set(false);
+ setStaticField("scheduleJobBootstrap", null);
+ setStaticField("contextManager", null);
+ setStaticField("registryCenter", null);
+ }
+
+ @SneakyThrows(ReflectiveOperationException.class)
+ private void setStaticField(final String fieldName, final Object value) {
+
Plugins.getMemberAccessor().set(StatisticsCollectJobWorker.class.getDeclaredField(fieldName),
StatisticsCollectJobWorker.class, value);
+ }
+
@Test
void assertInitializeTwice() {
jobWorker.initialize(contextManager);
@@ -53,9 +96,58 @@ class StatisticsCollectJobWorkerTest {
}
@Test
- void assertInitializeWithNotZooKeeperRepository() {
- jobWorker.initialize(contextManager);
- assertNull(getScheduleJobBootstrap());
+ void assertInitializeWithZooKeeperRepository() {
+ Properties repositoryProps = PropertiesBuilder.build(new
Property("retryIntervalMilliseconds", 200),
+ new Property("maxRetries", 4), new
Property("timeToLiveSeconds", 5), new Property("operationTimeoutMilliseconds",
800), new Property("digest", "digest"));
+
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
+ new ModeConfiguration("Cluster", new
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace",
"127.0.0.1:2181", repositoryProps)));
+
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(
+ new
TemporaryConfigurationProperties(PropertiesBuilder.build(new
Property(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON.getKey(),
"0 0/5 * * * ?"))));
+ AtomicReference<JobConfiguration> jobConfigRef = new
AtomicReference<>();
+ try (
+ MockedConstruction<ZookeeperRegistryCenter>
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
+ MockedConstruction<ScheduleJobBootstrap>
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class,
+ (mock, context) -> jobConfigRef.set((JobConfiguration)
context.arguments().get(2)));
+ MockedConstruction<JobOperateAPIImpl>
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
+ jobWorker.initialize(contextManager);
+ verify(registryCenterConstruction.constructed().get(0)).init();
+
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
+ assertThat(jobConfigRef.get().getCron(), is("0 0/5 * * * ?"));
+
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+ }
+ }
+
+ @Test
+ void assertInitializeWithZooKeeperRepositoryUsingDefaultConfiguration() {
+ Properties repositoryProps = PropertiesBuilder.build(new
Property("timeToLiveSeconds", 0), new Property("operationTimeoutMilliseconds",
0));
+
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
+ new ModeConfiguration("Cluster", new
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace",
"127.0.0.1:2181", repositoryProps)));
+
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(new
TemporaryConfigurationProperties(new Properties()));
+ try (
+ MockedConstruction<ZookeeperRegistryCenter>
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
+ MockedConstruction<ScheduleJobBootstrap>
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class);
+ MockedConstruction<JobOperateAPIImpl>
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
+ jobWorker.initialize(contextManager);
+ verify(registryCenterConstruction.constructed().get(0)).init();
+
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
+
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+ }
+ }
+
+ @Test
+ void assertInitializeWithZooKeeperRepositoryUsingDefaultValues() {
+
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
+ new ModeConfiguration("Cluster", new
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace",
"127.0.0.1:2181", new Properties())));
+
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(new
TemporaryConfigurationProperties(new Properties()));
+ try (
+ MockedConstruction<ZookeeperRegistryCenter>
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
+ MockedConstruction<ScheduleJobBootstrap>
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class);
+ MockedConstruction<JobOperateAPIImpl>
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
+ jobWorker.initialize(contextManager);
+ verify(registryCenterConstruction.constructed().get(0)).init();
+
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
+
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+ }
}
@Test
@@ -63,6 +155,40 @@ class StatisticsCollectJobWorkerTest {
assertDoesNotThrow(() -> jobWorker.updateJobConfiguration());
}
+ @Test
+ void assertUpdateJobConfiguration() {
+ setStaticField("contextManager", contextManager);
+ CoordinatorRegistryCenter registryCenter =
mock(CoordinatorRegistryCenter.class);
+ setStaticField("registryCenter", registryCenter);
+
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(
+ new
TemporaryConfigurationProperties(PropertiesBuilder.build(new
Property(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON.getKey(),
"invalid"))));
+ AtomicReference<Object> constructorRegistryCenter = new
AtomicReference<>();
+ try (
+ MockedConstruction<JobConfigurationAPIImpl>
jobConfigurationAPIConstruction =
mockConstruction(JobConfigurationAPIImpl.class,
+ (mock, context) ->
constructorRegistryCenter.set(context.arguments().get(0)))) {
+ jobWorker.updateJobConfiguration();
+ assertThat(constructorRegistryCenter.get(), is(registryCenter));
+ ArgumentCaptor<JobConfigurationPOJO> argumentCaptor =
ArgumentCaptor.forClass(JobConfigurationPOJO.class);
+
verify(jobConfigurationAPIConstruction.constructed().get(0)).updateJobConfiguration(argumentCaptor.capture());
+ JobConfiguration jobConfiguration =
argumentCaptor.getValue().toJobConfiguration();
+ assertThat(jobConfiguration.getCron(),
is(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON.getDefaultValue()));
+ }
+ }
+
+ @Test
+ void assertUpdateJobConfigurationWithException() {
+ setStaticField("contextManager", contextManager);
+ setStaticField("registryCenter",
mock(CoordinatorRegistryCenter.class));
+
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(
+ new
TemporaryConfigurationProperties(PropertiesBuilder.build(new
Property(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON.getKey(),
"0 0/2 * * * ?"))));
+ try (
+ MockedConstruction<JobConfigurationAPIImpl>
jobConfigurationAPIConstruction =
mockConstruction(JobConfigurationAPIImpl.class,
+ (mock, context) ->
doThrow(RuntimeException.class).when(mock).updateJobConfiguration(any(JobConfigurationPOJO.class))))
{
+ assertDoesNotThrow(() -> jobWorker.updateJobConfiguration());
+
verify(jobConfigurationAPIConstruction.constructed().get(0)).updateJobConfiguration(any(JobConfigurationPOJO.class));
+ }
+ }
+
@Test
void assertDestroy() {
jobWorker.destroy();
@@ -70,6 +196,16 @@ class StatisticsCollectJobWorkerTest {
assertNull(getScheduleJobBootstrap());
}
+ @Test
+ void assertDestroyWhenInitialized() {
+ ScheduleJobBootstrap scheduleJobBootstrap =
mock(ScheduleJobBootstrap.class);
+ setStaticField("scheduleJobBootstrap", scheduleJobBootstrap);
+ workerInitialized.set(true);
+ jobWorker.destroy();
+ verify(scheduleJobBootstrap).shutdown();
+ assertNull(getScheduleJobBootstrap());
+ }
+
@SneakyThrows(ReflectiveOperationException.class)
private ScheduleJobBootstrap getScheduleJobBootstrap() {
return (ScheduleJobBootstrap)
Plugins.getMemberAccessor().get(StatisticsCollectJobWorker.class.getDeclaredField("scheduleJobBootstrap"),
StatisticsCollectJobWorker.class);