This is an automated email from the ASF dual-hosted git repository.
davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 152c410882 [Fix][seatunnel-translation-base] Remove connector-specific
test dependencies from translation base (#11075)
152c410882 is described below
commit 152c4108825a9bf614db912033c305360fb1dd03
Author: JeremyXin <[email protected]>
AuthorDate: Mon Jun 22 18:36:13 2026 +0800
[Fix][seatunnel-translation-base] Remove connector-specific test
dependencies from translation base (#11075)
---
.../seatunnel-translation-base/pom.xml | 13 -
.../translation/source/ParallelSourceTest.java | 343 +++++++++++++--------
2 files changed, 221 insertions(+), 135 deletions(-)
diff --git a/seatunnel-translation/seatunnel-translation-base/pom.xml
b/seatunnel-translation/seatunnel-translation-base/pom.xml
index 6c0cf4359e..87636af0bb 100644
--- a/seatunnel-translation/seatunnel-translation-base/pom.xml
+++ b/seatunnel-translation/seatunnel-translation-base/pom.xml
@@ -31,18 +31,5 @@
<artifactId>seatunnel-api</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>org.apache.seatunnel</groupId>
- <artifactId>connector-file-base</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.apache.seatunnel</groupId>
- <artifactId>connector-doris</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
</dependencies>
</project>
diff --git
a/seatunnel-translation/seatunnel-translation-base/src/test/java/org/apache/seatunnel/translation/source/ParallelSourceTest.java
b/seatunnel-translation/seatunnel-translation-base/src/test/java/org/apache/seatunnel/translation/source/ParallelSourceTest.java
index 1a2a3b98a7..71e28ad9a6 100644
---
a/seatunnel-translation/seatunnel-translation-base/src/test/java/org/apache/seatunnel/translation/source/ParallelSourceTest.java
+++
b/seatunnel-translation/seatunnel-translation-base/src/test/java/org/apache/seatunnel/translation/source/ParallelSourceTest.java
@@ -17,178 +17,277 @@
package org.apache.seatunnel.translation.source;
-import org.apache.seatunnel.shade.com.google.common.collect.Maps;
-
-import org.apache.seatunnel.api.table.catalog.TablePath;
-import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig;
-import org.apache.seatunnel.connectors.doris.rest.PartitionDefinition;
-import org.apache.seatunnel.connectors.doris.rest.RestService;
-import org.apache.seatunnel.connectors.doris.source.DorisSource;
-import org.apache.seatunnel.connectors.doris.source.DorisSourceTable;
-import org.apache.seatunnel.connectors.doris.source.reader.DorisSourceReader;
-import org.apache.seatunnel.connectors.doris.source.split.DorisSourceSplit;
-import org.apache.seatunnel.connectors.seatunnel.file.source.BaseFileSource;
-import
org.apache.seatunnel.connectors.seatunnel.file.source.split.FileSourceSplit;
-import
org.apache.seatunnel.connectors.seatunnel.file.source.split.FileSourceSplitEnumerator;
+import org.apache.seatunnel.api.source.Boundedness;
+import org.apache.seatunnel.api.source.Collector;
+import org.apache.seatunnel.api.source.SeaTunnelSource;
+import org.apache.seatunnel.api.source.SourceReader;
+import org.apache.seatunnel.api.source.SourceSplit;
+import org.apache.seatunnel.api.source.SourceSplitEnumerator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.MockedStatic;
-import org.mockito.Mockito;
-
-import lombok.extern.slf4j.Slf4j;
+import java.io.IOException;
+import java.io.Serializable;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import static org.mockito.ArgumentMatchers.any;
-
-@Slf4j
-public class ParallelSourceTest {
+class ParallelSourceTest {
@Test
- void fileParallelSourceSplitEnumeratorTest() throws Exception {
- int fileSize = 15;
+ void shouldDistributeSplitsAcrossParallelReaders() throws Exception {
+ int splitCount = 15;
int parallelism = 4;
+ List<MockSplit> splits =
+ IntStream.range(0, splitCount)
+ .mapToObj(i -> new MockSplit("split-" + i))
+ .collect(Collectors.toList());
+
+ MockSource source = new MockSource(splits);
+ Map<Integer, List<MockSplit>> assignedSplits = new LinkedHashMap<>();
+
+ for (int subtaskId = 0; subtaskId < parallelism; subtaskId++) {
+ ParallelSource<String, MockSplit, Integer> parallelSource =
+ new ParallelSource<>(
+ source, null, parallelism, "parallel-source-test",
subtaskId);
+ parallelSource.open();
+ parallelSource.splitEnumerator.run();
- List<String> filePaths = new ArrayList<>();
- for (int i = 0; i < fileSize; i++) {
- filePaths.add("file" + i + ".txt");
+ assignedSplits.put(subtaskId,
parallelSource.reader.snapshotState(0L));
+ parallelSource.close();
}
- BaseFileSource baseFileSource = Mockito.spy(BaseFileSource.class);
- Set<FileSourceSplit> splitSet = new HashSet<>();
- for (int i = 0; i < parallelism; i++) {
+ int totalAssigned =
assignedSplits.values().stream().mapToInt(List::size).sum();
+ Assertions.assertEquals(splitCount, totalAssigned);
- ParallelEnumeratorContext<FileSourceSplit> context =
- Mockito.mock(ParallelEnumeratorContext.class);
+ List<String> splitIds =
+ assignedSplits.values().stream()
+ .flatMap(List::stream)
+ .map(MockSplit::splitId)
+ .sorted()
+ .collect(Collectors.toList());
+ Assertions.assertEquals(
+
splits.stream().map(MockSplit::splitId).sorted().collect(Collectors.toList()),
+ splitIds);
- Mockito.when(context.currentParallelism()).thenReturn(parallelism);
+ for (int subtaskId = 0; subtaskId < parallelism; subtaskId++) {
+ Assertions.assertEquals(
+ expectedSplitCount(subtaskId, parallelism, splitCount),
+ assignedSplits.get(subtaskId).size());
+ }
+ }
- FileSourceSplitEnumerator fileSourceSplitEnumerator =
- new FileSourceSplitEnumerator(context, filePaths);
+ @Test
+ void shouldRestoreReaderSplitsFromCheckpointState() throws Exception {
+ MockSource source = new MockSource(Collections.emptyList());
+ Map<Integer, List<byte[]>> restoredState = new HashMap<>();
+ restoredState.put(-1,
Collections.singletonList(toBytes(Integer.valueOf(7))));
+ restoredState.put(0, Collections.singletonList(toBytes(new
MockSplit("restored-split-0"))));
+
+ ParallelSource<String, MockSplit, Integer> parallelSource =
+ new ParallelSource<>(source, restoredState, 4,
"parallel-source-restore", 0);
+ parallelSource.open();
+ parallelSource.splitEnumerator.run();
+
+ Assertions.assertEquals(
+ Collections.singletonList("restored-split-0"),
+ parallelSource.reader.snapshotState(0L).stream()
+ .map(MockSplit::splitId)
+ .sorted()
+ .collect(Collectors.toList()));
+
+ MockSplitEnumerator restoredEnumerator = source.lastRestoredEnumerator;
+ Assertions.assertNotNull(restoredEnumerator);
+ Assertions.assertEquals(Integer.valueOf(7),
restoredEnumerator.restoredState);
+
+ parallelSource.close();
+ }
- Mockito.when(baseFileSource.createEnumerator(any()))
- .thenReturn(fileSourceSplitEnumerator);
+ private static int expectedSplitCount(int subtaskId, int parallelism, int
splitCount) {
+ int splitsPerReader = splitCount / parallelism;
+ int remainder = splitCount % parallelism;
+ return subtaskId < remainder ? splitsPerReader + 1 : splitsPerReader;
+ }
- ParallelSource parallelSource =
- new ParallelSource(
- baseFileSource, null, parallelism,
"parallel-source-test" + i, i);
+ private static byte[] toBytes(Serializable value) throws IOException {
+ return new
org.apache.seatunnel.api.serialization.DefaultSerializer<Serializable>()
+ .serialize(value);
+ }
- parallelSource.open();
- parallelSource.splitEnumerator.run();
+ private static final class MockSource implements SeaTunnelSource<String,
MockSplit, Integer> {
- ArgumentCaptor<Integer> subtaskId =
ArgumentCaptor.forClass(Integer.class);
- ArgumentCaptor<List> split = ArgumentCaptor.forClass(List.class);
+ private final List<MockSplit> splits;
+ private MockSplitEnumerator lastRestoredEnumerator;
- Mockito.verify(context, Mockito.times(parallelism))
- .assignSplit(subtaskId.capture(), split.capture());
+ private MockSource(List<MockSplit> splits) {
+ this.splits = splits;
+ }
- List<Integer> subTaskAllValues = subtaskId.getAllValues();
- List<List> splitAllValues = split.getAllValues();
+ @Override
+ public Boundedness getBoundedness() {
+ return Boundedness.BOUNDED;
+ }
- Assertions.assertEquals(i, subTaskAllValues.get(i));
- Assertions.assertEquals(
- allocateFiles(i, parallelism, fileSize),
splitAllValues.get(i).size());
+ @Override
+ public SourceReader<String, MockSplit>
createReader(SourceReader.Context readerContext) {
+ return new MockReader();
+ }
- splitSet.addAll(splitAllValues.get(i));
+ @Override
+ public SourceSplitEnumerator<MockSplit, Integer> createEnumerator(
+ SourceSplitEnumerator.Context<MockSplit> enumeratorContext) {
+ return new MockSplitEnumerator(enumeratorContext, splits, null);
}
- // Check that there are no duplicate file assign
- Assertions.assertEquals(splitSet.size(), fileSize);
+ @Override
+ public SourceSplitEnumerator<MockSplit, Integer> restoreEnumerator(
+ SourceSplitEnumerator.Context<MockSplit> enumeratorContext,
+ Integer checkpointState) {
+ lastRestoredEnumerator =
+ new MockSplitEnumerator(
+ enumeratorContext, Collections.emptyList(),
checkpointState);
+ return lastRestoredEnumerator;
+ }
+
+ @Override
+ public String getPluginName() {
+ return "mock-source";
+ }
}
- @Test
- public void dorisParallelSourceSplitEnumeratorTest() throws Exception {
- int parallelism = 4;
- int partitionNums = 30;
+ private static final class MockSplitEnumerator
+ implements SourceSplitEnumerator<MockSplit, Integer> {
+
+ private final SourceSplitEnumerator.Context<MockSplit> context;
+ private final Map<Integer, List<MockSplit>> pendingSplits = new
HashMap<>();
+ private final Integer restoredState;
+ private final AtomicInteger assignCount = new AtomicInteger(0);
+
+ private MockSplitEnumerator(
+ SourceSplitEnumerator.Context<MockSplit> context,
+ List<MockSplit> splits,
+ Integer restoredState) {
+ this.context = context;
+ this.restoredState = restoredState;
+ addPendingSplits(splits);
+ }
- DorisSourceConfig dorisSourceConfig =
Mockito.mock(DorisSourceConfig.class);
- DorisSourceTable dorisSourceTable =
Mockito.mock(DorisSourceTable.class);
+ @Override
+ public void open() {}
- Map<TablePath, DorisSourceTable> dorisSourceTableMap =
Maps.newHashMap();
- dorisSourceTableMap.put(new TablePath("default", null,
"default_table"), dorisSourceTable);
+ @Override
+ public void run() {
+ assignSplits(context.registeredReaders());
+ context.registeredReaders().forEach(context::signalNoMoreSplits);
+ }
- DorisSource dorisSource = new DorisSource(dorisSourceConfig,
dorisSourceTableMap);
+ private static int getSplitOwner(int assignCount, int parallelism) {
+ return assignCount % parallelism;
+ }
- MockedStatic<RestService> restServiceMockedStatic =
Mockito.mockStatic(RestService.class);
- restServiceMockedStatic
- .when(() -> RestService.findPartitions(any(), any(), any()))
- .thenReturn(buildPartitionDefinitions(partitionNums));
+ private void assignSplits(Iterable<Integer> readers) {
+ for (Integer reader : readers) {
+ List<MockSplit> assigned = pendingSplits.remove(reader);
+ if (assigned != null && !assigned.isEmpty()) {
+ context.assignSplit(reader, assigned);
+ }
+ }
+ }
- Set<DorisSourceSplit> splitSet = new HashSet<>();
- for (int i = 0; i < parallelism; i++) {
- ParallelSource parallelSource =
- new ParallelSource(
- dorisSource, null, parallelism,
"parallel-doris-source" + i, i);
- parallelSource.open();
+ private void addPendingSplits(List<MockSplit> splits) {
+ int parallelism = context.currentParallelism();
+ List<MockSplit> orderedSplits =
+ splits.stream()
+ .sorted((left, right) ->
left.splitId().compareTo(right.splitId()))
+ .collect(Collectors.toList());
+ for (MockSplit split : orderedSplits) {
+ int ownerReader = getSplitOwner(assignCount.getAndIncrement(),
parallelism);
+ pendingSplits.computeIfAbsent(ownerReader, key -> new
ArrayList<>()).add(split);
+ }
+ }
- // execute file allocation process
- parallelSource.splitEnumerator.run();
- List<DorisSourceSplit> sourceSplits =
- ((DorisSourceReader)
parallelSource.reader).snapshotState(0);
- log.info(
- "parallel source{} splits => {}",
- i + 1,
- sourceSplits.stream()
- .map(DorisSourceSplit::splitId)
- .collect(Collectors.toList()));
+ @Override
+ public void close() {}
- Assertions.assertEquals(
- allocateFiles(i, parallelism, partitionNums),
sourceSplits.size());
+ @Override
+ public void addSplitsBack(List<MockSplit> splits, int subtaskId) {
+ pendingSplits.computeIfAbsent(subtaskId, key -> new
ArrayList<>()).addAll(splits);
+ }
+
+ @Override
+ public int currentUnassignedSplitSize() {
+ return pendingSplits.values().stream().mapToInt(List::size).sum();
+ }
+
+ @Override
+ public void handleSplitRequest(int subtaskId) {}
+
+ @Override
+ public void registerReader(int subtaskId) {}
- // collect all splits
- splitSet.addAll(sourceSplits);
+ @Override
+ public Integer snapshotState(long checkpointId) {
+ return restoredState;
}
- Assertions.assertEquals(splitSet.size(), partitionNums);
+ @Override
+ public void notifyCheckpointComplete(long checkpointId) {}
+
+ @Override
+ public void notifyCheckpointAborted(long checkpointId) {}
}
- private List<PartitionDefinition> buildPartitionDefinitions(int
partitionNUms) {
+ private static final class MockReader implements SourceReader<String,
MockSplit> {
+
+ private final List<MockSplit> splits = new ArrayList<>();
+
+ @Override
+ public void open() {}
- List<PartitionDefinition> partitions = new ArrayList<>();
+ @Override
+ public void close() {}
- String beAddressPrefix = "doris-be-";
+ @Override
+ public void pollNext(Collector<String> output) {}
- IntStream.range(0, partitionNUms)
- .forEach(
- i -> {
- PartitionDefinition partitionDefinition =
- new PartitionDefinition(
- "default",
- "default_table",
- beAddressPrefix + i,
- new HashSet<>(i),
- "QUERY_PLAN");
+ @Override
+ public List<MockSplit> snapshotState(long checkpointId) {
+ return new ArrayList<>(splits);
+ }
+
+ @Override
+ public void addSplits(List<MockSplit> splits) {
+ this.splits.addAll(splits);
+ }
- partitions.add(partitionDefinition);
- });
+ @Override
+ public void handleNoMoreSplits() {}
- return partitions;
+ @Override
+ public void notifyCheckpointComplete(long checkpointId) {}
+
+ @Override
+ public void notifyCheckpointAborted(long checkpointId) {}
}
- /**
- * calculate the number of files assigned each time
- *
- * @param id id
- * @param parallelism parallelism
- * @param fileSize file size
- * @return
- */
- public int allocateFiles(int id, int parallelism, int fileSize) {
- int filesPerIteration = fileSize / parallelism;
- int remainder = fileSize % parallelism;
-
- if (id < remainder) {
- return filesPerIteration + 1;
- } else {
- return filesPerIteration;
+ private static final class MockSplit implements SourceSplit {
+
+ private final String splitId;
+
+ private MockSplit(String splitId) {
+ this.splitId = splitId;
+ }
+
+ @Override
+ public String splitId() {
+ return splitId;
}
}
}