This is an automated email from the ASF dual-hosted git repository.

gkoszyk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 5a2939a17 feat(ci): dependency-DAG-based test scoping for Rust CI 
(#3095)
5a2939a17 is described below

commit 5a2939a17c465612808a174f8ac4cc689dbcc5eb
Author: Krishna Vishal <[email protected]>
AuthorDate: Mon Apr 13 20:08:24 2026 +0530

    feat(ci): dependency-DAG-based test scoping for Rust CI (#3095)
    
    ## Which issue does this PR close?
    
    Closes #3094
    
    ## Summary
    
    - Use `cargo-rail` to compute affected crates via the workspace
    dependency DAG, then scope cargo build and `cargo nextest` run to only
    those crates
    - Fix two detection blind spots: `core/iobuf and `core/server-ng` were
    not covered by any component in `components.yml`
    - Split `rust-simulator` out of `rust-cluster` so simulator-only changes
    don't trigger Python/Node/Go/Java/C# SDK tests
---
 .config/rail.toml                         | 28 +++++++++
 .github/actions/rust/pre-merge/action.yml | 95 +++++++++++++++++++++++++++++--
 .github/config/components.yml             | 21 +++++++
 3 files changed, 140 insertions(+), 4 deletions(-)

diff --git a/.config/rail.toml b/.config/rail.toml
new file mode 100644
index 000000000..14114221d
--- /dev/null
+++ b/.config/rail.toml
@@ -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.
+
+# cargo-rail change detection configuration
+# See: https://github.com/loadingalias/cargo-rail
+
+[change-detection]
+# Changes to these paths trigger a full workspace rebuild/retest
+infrastructure = [
+    "Cargo.lock",
+    "rust-toolchain.toml",
+    ".cargo/**",
+    ".github/**",
+]
diff --git a/.github/actions/rust/pre-merge/action.yml 
b/.github/actions/rust/pre-merge/action.yml
index 0daa91b5d..742d489f9 100644
--- a/.github/actions/rust/pre-merge/action.yml
+++ b/.github/actions/rust/pre-merge/action.yml
@@ -59,6 +59,50 @@ runs:
         esac
       shell: bash
 
+    # DAG-based test scoping: use cargo-rail to compute affected crates from 
the
+    # workspace dependency graph + git diff, avoiding the full test suite when 
only
+    # a subset of crates changed.
+    # Safety: cargo check/clippy run on the full workspace separately, 
catching all
+    # compilation errors. This only scopes test BUILD and EXECUTION.
+    - name: Fetch base branch for DAG analysis
+      if: startsWith(inputs.task, 'test-')
+      run: git fetch origin master --depth=1 2>/dev/null || true
+      shell: bash
+
+    - name: Install cargo-rail
+      if: startsWith(inputs.task, 'test-')
+      uses: taiki-e/install-action@v2
+      with:
+        tool: cargo-rail
+
+    - name: Compute affected crates (cargo-rail)
+      if: startsWith(inputs.task, 'test-')
+      run: |
+        TOTAL_CRATES=$(cargo metadata --format-version 1 --no-deps 2>/dev/null 
| jq '.workspace_members | length' 2>/dev/null || echo "?")
+        echo "$TOTAL_CRATES" > /tmp/total-crates.txt
+
+        PLAN_JSON=$(cargo rail plan --since origin/master -f json 
2>/tmp/affected-stderr.txt || echo "")
+
+        if [[ -n "$PLAN_JSON" ]]; then
+          MODE=$(echo "$PLAN_JSON" | jq -r '.scope.mode')
+          if [[ "$MODE" == "crates" ]]; then
+            CRATES=$(echo "$PLAN_JSON" | jq -r '.scope.crates[]')
+            CRATE_COUNT=$(echo "$CRATES" | wc -l)
+            # Build -p flags for cargo build/test
+            echo "$CRATES" | sed 's/^/-p /' | tr '\n' ' ' > /tmp/packages.txt
+            # Build nextest filter expression for cargo nextest run
+            echo "$CRATES" | sed 's/^/package(/; s/$/)/' | paste -sd ' | ' > 
/tmp/nextest-filter.txt
+            echo "::notice::DAG analysis: ${CRATE_COUNT} affected crates (of 
${TOTAL_CRATES} total)"
+          else
+            echo "::notice::Full workspace affected (${TOTAL_CRATES} crates)"
+          fi
+        else
+          STDERR=$(cat /tmp/affected-stderr.txt 2>/dev/null || echo "")
+          echo "::warning::Could not compute affected crates, running full 
test suite. ${STDERR}"
+          rm -f /tmp/nextest-filter.txt /tmp/packages.txt
+        fi
+      shell: bash
+
     # Individual lint tasks for parallel execution
     - name: Cargo check
       if: inputs.task == 'check'
@@ -117,16 +161,45 @@ runs:
           echo "::notice::Running test partition ${PARTITION_INDEX}/2"
         fi
 
+        # Read DAG-based affected crate filter (computed in earlier step)
+        NEXTEST_FILTER=""
+        PACKAGE_FLAGS=""
+        TOTAL_CRATES="?"
+        if [[ -f /tmp/nextest-filter.txt ]]; then
+          NEXTEST_FILTER=$(cat /tmp/nextest-filter.txt)
+        fi
+        if [[ -f /tmp/packages.txt ]]; then
+          PACKAGE_FLAGS=$(cat /tmp/packages.txt)
+        fi
+        if [[ -f /tmp/total-crates.txt ]]; then
+          TOTAL_CRATES=$(cat /tmp/total-crates.txt)
+        fi
+
+        if [[ -n "$PACKAGE_FLAGS" ]]; then
+          CRATE_COUNT=$(echo "$NEXTEST_FILTER" | grep -o 'package(' | wc -l)
+          echo "::notice::DAG-scoped build: ${CRATE_COUNT} crates (cargo 
check/clippy cover full workspace separately)"
+        else
+          echo "::notice::Full workspace build (no DAG filter available)"
+        fi
+
         source <(cargo llvm-cov show-env --export-prefix)
 
         bins_start=$(date +%s)
-        cargo build --locked
+        if [[ -n "$PACKAGE_FLAGS" ]]; then
+          cargo build --locked $PACKAGE_FLAGS
+        else
+          cargo build --locked
+        fi
         bins_end=$(date +%s)
         bins_duration=$((bins_end - bins_start))
         echo "::notice::Binaries and libraries built in ${bins_duration}s 
($(date -ud @${bins_duration} +'%M:%S'))"
 
         compile_start=$(date +%s)
-        cargo test --locked --no-run
+        if [[ -n "$PACKAGE_FLAGS" ]]; then
+          cargo test --locked --no-run $PACKAGE_FLAGS
+        else
+          cargo test --locked --no-run
+        fi
         compile_end=$(date +%s)
         compile_duration=$((compile_end - compile_start))
         echo "::notice::Tests compiled in ${compile_duration}s ($(date -ud 
@${compile_duration} +'%M:%S'))"
@@ -144,12 +217,20 @@ runs:
 
         test_start=$(date +%s)
         if command -v cargo-nextest &> /dev/null; then
-          cargo nextest run --locked --no-fail-fast --profile ci 
$PARTITION_FLAG
+          if [[ -n "$NEXTEST_FILTER" ]]; then
+            cargo nextest run --locked --no-fail-fast --profile ci 
$PARTITION_FLAG -E "$NEXTEST_FILTER"
+          else
+            cargo nextest run --locked --no-fail-fast --profile ci 
$PARTITION_FLAG
+          fi
         else
           if [[ -n "$PARTITION_FLAG" ]]; then
             echo "::error::cargo-nextest not found, falling back to cargo test 
without partitioning (all tests will run on every partition)"
           fi
-          cargo test --locked --no-fail-fast
+          if [[ -n "$PACKAGE_FLAGS" ]]; then
+            cargo test --locked --no-fail-fast $PACKAGE_FLAGS
+          else
+            cargo test --locked --no-fail-fast
+          fi
         fi
         test_end=$(date +%s)
         test_duration=$((test_end - test_start))
@@ -159,6 +240,12 @@ runs:
         total_duration=$((build_duration + test_duration))
         echo ""
         echo "========================================="
+        if [[ -n "$PACKAGE_FLAGS" ]]; then
+          CRATE_COUNT=$(echo "$NEXTEST_FILTER" | grep -o 'package(' | wc -l)
+          echo "DAG scope:                     ${CRATE_COUNT}/${TOTAL_CRATES} 
crates"
+        else
+          echo "DAG scope:                     full workspace (${TOTAL_CRATES} 
crates)"
+        fi
         echo "All targets build:             ${bins_duration}s ($(date -ud 
@${bins_duration} +'%M:%S'))"
         echo "Tests compile:                 ${compile_duration}s ($(date -ud 
@${compile_duration} +'%M:%S'))"
         echo "Tests execute:                 ${test_duration}s ($(date -ud 
@${test_duration} +'%M:%S'))"
diff --git a/.github/config/components.yml b/.github/config/components.yml
index 7585a91b9..a898f30db 100644
--- a/.github/config/components.yml
+++ b/.github/config/components.yml
@@ -51,9 +51,17 @@ components:
     paths:
       - "core/common/**"
 
+  # Leaf crate: zero-copy I/O buffer, depended on by binary_protocol and 
cluster
+  rust-iobuf:
+    depends_on:
+      - "rust-workspace"
+    paths:
+      - "core/iobuf/**"
+
   rust-binary-protocol:
     depends_on:
       - "rust-workspace" # Protocol is affected by workspace changes
+      - "rust-iobuf" # binary_protocol depends on iobuf
       - "ci-infrastructure" # CI changes trigger full regression
     paths:
       - "core/binary_protocol/**"
@@ -67,10 +75,13 @@ components:
       - "rust-cluster"
     paths:
       - "core/server/**"
+      - "core/server-ng/**"
 
   rust-cluster:
     depends_on:
       - "rust-workspace"
+      - "rust-iobuf" # cluster crates depend on iobuf
+      - "rust-binary-protocol" # cluster crates depend on binary_protocol
     paths:
       - "core/clock/**"
       - "core/consensus/**"
@@ -79,18 +90,28 @@ components:
       - "core/metadata/**"
       - "core/message_bus/**"
       - "core/partitions/**"
+
+  # Standalone simulation tool, does NOT affect server binary or foreign SDKs.
+  # Split from rust-cluster to avoid triggering SDK tests on simulator-only 
changes.
+  rust-simulator:
+    depends_on:
+      - "rust-workspace"
+      - "rust-cluster"
+    paths:
       - "core/simulator/**"
 
   # Main Rust workspace testing
   rust:
     depends_on:
       - "rust-workspace"
+      - "rust-iobuf"
       - "rust-configs"
       - "rust-sdk"
       - "rust-common"
       - "rust-binary-protocol"
       - "rust-server"
       - "rust-cluster"
+      - "rust-simulator"
       - "rust-tools"
       - "rust-cli"
       - "rust-bench"

Reply via email to