This is an automated email from the ASF dual-hosted git repository.
zhangshenghang 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 ab25ea0ead [Improve][CI] Harden dedicated all-connectors shard guards
(#11828)
ab25ea0ead is described below
commit ab25ea0eadeb22f23d5a7388a98ad52b25ae7345
Author: David Zollo <[email protected]>
AuthorDate: Sun Aug 23 21:12:53 2026 +0800
[Improve][CI] Harden dedicated all-connectors shard guards (#11828)
---
.../test_update_modules_check.py | 123 +++++++++++++++++----
tools/update_modules_check/update_modules_check.py | 92 ++++++++++-----
2 files changed, 162 insertions(+), 53 deletions(-)
diff --git a/tools/update_modules_check/test_update_modules_check.py
b/tools/update_modules_check/test_update_modules_check.py
index 843795784a..59c4f2e13f 100644
--- a/tools/update_modules_check/test_update_modules_check.py
+++ b/tools/update_modules_check/test_update_modules_check.py
@@ -15,8 +15,15 @@
# limitations under the License.
import unittest
+from collections import Counter
+from pathlib import Path
+import re
-from update_modules_check import build_sub_it_modules
+from update_modules_check import (
+ ALL_CONNECTORS_DEDICATED_SHARD_MODULES,
+ ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES,
+ build_sub_it_modules,
+)
class UpdateModulesCheckTest(unittest.TestCase):
@@ -24,40 +31,108 @@ class UpdateModulesCheckTest(unittest.TestCase):
Guard the all-connectors shard contract used by backend CI.
"""
- def test_dedicated_heavy_suites_do_not_stay_in_regular_shards(self):
+ @staticmethod
+ def parse_modules(modules):
+ return [module.lstrip(":") for module in modules.split(",") if module]
+
+ def test_regular_shards_keep_only_remaining_modules_once(self):
+ """
+ Regular all-connectors shards should keep each surviving module once.
+ """
+ expected_modules = {
+ "connector-assert-e2e",
+ "connector-cdc-sqlserver-e2e",
+ "connector-http-e2e",
+ }
+ modules = ",".join(
+ ["", *sorted(expected_modules),
*ALL_CONNECTORS_DEDICATED_SHARD_MODULES]
+ )
+
+ shard_outputs = [build_sub_it_modules(modules, 7, shard) for shard in
range(7)]
+ shard_modules = [self.parse_modules(output) for output in
shard_outputs]
+ combined_counter = Counter(
+ module for output_modules in shard_modules for module in
output_modules
+ )
+
+ self.assertEqual(expected_modules, set(combined_counter))
+ self.assertEqual(Counter(expected_modules), combined_counter)
+ self.assertTrue(
+
set(ALL_CONNECTORS_DEDICATED_SHARD_MODULES).isdisjoint(set(combined_counter))
+ )
+ for output_modules in shard_modules:
+ self.assertNotIn("connector-iceberg-e2e", output_modules)
+ self.assertNotIn("connector-hbase-e2e", output_modules)
+
+ def test_regular_shards_fail_fast_when_dedicated_modules_disappear(self):
"""
- Iceberg and HBase should run only in the dedicated shard.
+ The all-connectors source list should fail loudly if a dedicated
module drifts.
"""
modules = ",".join(
[
"",
"connector-assert-e2e",
- "connector-jdbc-e2e",
- "connector-redis-e2e",
- "connector-cdc-sqlserver-e2e",
- "connector-kafka-e2e",
- "connector-iceberg-e2e",
- "connector-hbase-e2e",
- "connector-http-e2e",
- "connector-rocketmq-e2e",
- "connector-kudu-e2e",
- "connector-amazonsqs-e2e",
- "connector-doris-e2e",
- "connector-paimon-e2e",
- "connector-cdc-oracle-e2e",
- "connector-file-local-e2e",
- "connector-file-sftp-e2e",
- "connector-sensorsdata-e2e",
+ *[
+ module
+ for module in
ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES
+ if module != "connector-elasticsearch-e2e"
+ ],
]
)
+ with self.assertRaisesRegex(ValueError, "connector-elasticsearch-e2e"):
+ build_sub_it_modules(modules, 7, 0)
+
+ def
test_regular_shards_allow_optional_dedicated_modules_to_be_absent(self):
+ """
+ Dedicated suites outside connector-v2 input should stay optional.
+ """
+ modules = ",".join(
+ ["", "connector-assert-e2e",
*ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES]
+ )
+
shard_outputs = [build_sub_it_modules(modules, 7, shard) for shard in
range(7)]
- combined_outputs = ",".join(shard_outputs)
+ combined_modules = {
+ module
+ for output in shard_outputs
+ for module in self.parse_modules(output)
+ }
+
+ self.assertEqual({"connector-assert-e2e"}, combined_modules)
- self.assertIn("connector-assert-e2e", combined_outputs)
- for shard_modules in shard_outputs:
- self.assertNotIn("connector-iceberg-e2e", shard_modules)
- self.assertNotIn("connector-hbase-e2e", shard_modules)
+ def test_workflow_keeps_dedicated_jobs_for_excluded_modules(self):
+ """
+ Workflow job lists should continue covering modules excluded from
regular shards.
+ """
+ workflow = (
+ Path(__file__).resolve().parents[2] / ".github" / "workflows" /
"backend.yml"
+ ).read_text(encoding="utf-8")
+ workflow_modules = set()
+ for modules in re.findall(
+ r"-pl\s+(:[A-Za-z0-9._-]+(?:,:[A-Za-z0-9._-]+)*)", workflow
+ ):
+ workflow_modules.update(
+ module.lstrip(":") for module in modules.split(",") if module
+ )
+
+ expected_workflow_modules = set(ALL_CONNECTORS_DEDICATED_SHARD_MODULES)
+ expected_workflow_modules.remove("connector-jdbc-e2e")
+ expected_workflow_modules.update(
+ {
+ "connector-jdbc-e2e-part-1",
+ "connector-jdbc-e2e-part-2",
+ "connector-jdbc-e2e-part-3",
+ "connector-jdbc-e2e-part-4",
+ "connector-jdbc-e2e-part-5",
+ "connector-jdbc-e2e-part-6",
+ "connector-jdbc-e2e-part-7",
+ "connector-jdbc-e2e-ddl",
+ }
+ )
+
+ self.assertFalse(
+ expected_workflow_modules - workflow_modules,
+ f"Missing dedicated workflow modules:
{sorted(expected_workflow_modules - workflow_modules)}",
+ )
if __name__ == "__main__":
diff --git a/tools/update_modules_check/update_modules_check.py
b/tools/update_modules_check/update_modules_check.py
index 0238bb1f5c..844bd6c48a 100644
--- a/tools/update_modules_check/update_modules_check.py
+++ b/tools/update_modules_check/update_modules_check.py
@@ -17,6 +17,38 @@
import json
import sys
+ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES = (
+ "connector-jdbc-e2e",
+ "connector-kafka-e2e",
+ "connector-rocketmq-e2e",
+ "connector-kudu-e2e",
+ "connector-amazonsqs-e2e",
+ "connector-doris-e2e",
+ "connector-paimon-e2e",
+ "connector-cdc-oracle-e2e",
+ "connector-file-local-e2e",
+ "connector-file-sftp-e2e",
+ "connector-redis-e2e",
+ "connector-sensorsdata-e2e",
+ "connector-elasticsearch-e2e",
+ "connector-cdc-mysql-e2e",
+ "connector-iceberg-e2e",
+ "connector-hbase-e2e",
+)
+
+# These suites have dedicated jobs in backend.yml, but they are not listed by
+# seatunnel-connector-v2-e2e's project.modules input.
+ALL_CONNECTORS_OPTIONAL_DEDICATED_SHARD_MODULES = (
+ "connector-seatunnel-e2e-base",
+ "connector-console-seatunnel-e2e",
+ "seatunnel-edge-agent-e2e",
+)
+
+ALL_CONNECTORS_DEDICATED_SHARD_MODULES = (
+ ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES
+ + ALL_CONNECTORS_OPTIONAL_DEDICATED_SHARD_MODULES
+)
+
def get_cv2_modules(files):
get_modules(files, 1, "connector-", "seatunnel-connectors-v2")
@@ -141,6 +173,28 @@ def get_deleted_modules(files):
print(output_module)
+def filter_dedicated_shard_modules(modules_arr, dedicated_modules,
fail_on_missing):
+ """
+ Remove modules that already run in dedicated workflow jobs.
+
+ The all-connectors path stays strict because a missing dedicated module
+ means the exclusion list drifted away from backend.yml.
+ """
+ module_set = set(modules_arr)
+ if fail_on_missing:
+ missing_modules = [
+ module for module in dedicated_modules if module not in module_set
+ ]
+ if missing_modules:
+ raise ValueError(
+ "Missing dedicated shard modules from all-connectors input: "
+ + ",".join(missing_modules)
+ )
+
+ dedicated_modules_set = set(dedicated_modules)
+ return [module for module in modules_arr if module not in
dedicated_modules_set]
+
+
def build_sub_it_modules(modules, total_num, current_num):
"""
Build one all-connectors shard while excluding suites with dedicated jobs.
@@ -149,38 +203,18 @@ def build_sub_it_modules(modules, total_num, current_num):
round-robin shards, otherwise CI runs them twice and wastes runner time.
"""
modules_arr = list(dict.fromkeys(modules.split(",")))
- modules_arr.remove("connector-jdbc-e2e")
- modules_arr.remove("connector-kafka-e2e")
- modules_arr.remove("connector-rocketmq-e2e")
- modules_arr.remove("connector-kudu-e2e")
- modules_arr.remove("connector-amazonsqs-e2e")
- modules_arr.remove("connector-doris-e2e")
- modules_arr.remove("connector-paimon-e2e")
- modules_arr.remove("connector-cdc-oracle-e2e")
- modules_arr.remove("connector-file-local-e2e")
- modules_arr.remove("connector-file-sftp-e2e")
- modules_arr.remove("connector-redis-e2e")
- modules_arr.remove("connector-sensorsdata-e2e")
- if "connector-elasticsearch-e2e" in modules_arr:
- modules_arr.remove("connector-elasticsearch-e2e")
- if "connector-cdc-mysql-e2e" in modules_arr:
- modules_arr.remove("connector-cdc-mysql-e2e")
- if "connector-iceberg-e2e" in modules_arr:
- modules_arr.remove("connector-iceberg-e2e")
- if "connector-hbase-e2e" in modules_arr:
- modules_arr.remove("connector-hbase-e2e")
- if "connector-seatunnel-e2e-base" in modules_arr:
- modules_arr.remove("connector-seatunnel-e2e-base")
- if "connector-console-seatunnel-e2e" in modules_arr:
- modules_arr.remove("connector-console-seatunnel-e2e")
- if "seatunnel-edge-agent-e2e" in modules_arr:
- modules_arr.remove("seatunnel-edge-agent-e2e")
- output = ""
+ modules_arr = filter_dedicated_shard_modules(
+ modules_arr, ALL_CONNECTORS_REQUIRED_DEDICATED_SHARD_MODULES, True
+ )
+ modules_arr = filter_dedicated_shard_modules(
+ modules_arr, ALL_CONNECTORS_OPTIONAL_DEDICATED_SHARD_MODULES, False
+ )
+ output = []
for i, module in enumerate(modules_arr):
if len(module) > 0 and i % int(total_num) == int(current_num):
- output = output + ",:" + module
+ output.append(":" + module)
- return output[1:len(output)]
+ return ",".join(output)
def get_sub_it_modules(modules, total_num, current_num):