This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 301d7b307f [ci] Run affected test suites through reusable workflows
(#9787)
301d7b307f is described below
commit 301d7b307f39cb40d6a2e4e64c26289f2ba0e570
Author: Jingsong Lee <[email protected]>
AuthorDate: Sun Sep 13 17:55:19 2026 +0800
[ci] Run affected test suites through reusable workflows (#9787)
---
.github/CI.md | 83 +++++++
.github/workflows/{docs-tests.yml => ci-docs.yml} | 15 +-
.github/workflows/ci-java.yml | 73 +++++++
.../{check-licensing.yml => ci-licensing.yml} | 31 ++-
.../{paimon-python-checks.yml => ci-python.yml} | 43 ++--
.github/workflows/ci.yml | 112 ++++++++++
.github/workflows/e2e-tests-flink-1.x.yml | 80 -------
.github/workflows/e2e-tests-flink-2.x-jdk11.yml | 81 -------
.github/workflows/file-size-check.yml | 48 -----
.../{stale-pr.yml => maintenance-stale-pr.yml} | 2 +-
...pshot-jdk17.yml => publish-snapshot-spark4.yml} | 14 +-
.../{publish_snapshot.yml => publish-snapshot.yml} | 14 +-
.github/workflows/release-java.yml | 2 +-
.github/workflows/release-python-publish.yml | 2 +-
.github/workflows/release-python.yml | 2 +-
.github/workflows/utcase-eslib.yml | 73 -------
.github/workflows/utcase-full-text.yml | 61 ------
.github/workflows/utitcase-flink-1.x-common.yml | 75 -------
.github/workflows/utitcase-flink-1.x-others.yml | 75 -------
.github/workflows/utitcase-flink-2.x-jdk11.yml | 76 -------
.github/workflows/utitcase-iceberg-ga.yml | 66 ------
.github/workflows/utitcase-jdk11.yml | 72 -------
.github/workflows/utitcase-spark-3.x.yml | 79 -------
.github/workflows/utitcase-spark-4.x.yml | 76 -------
.github/workflows/utitcase.yml | 78 -------
docs/docs/project/verifying-a-release-candidate.md | 2 +-
tools/ci/plan.py | 180 ++++++++++++++++
tools/ci/run-java-tests.sh | 129 +++++++++++
tools/ci/test_java_commands.py | 112 ++++++++++
tools/ci/test_plan.py | 238 +++++++++++++++++++++
tools/ci/test_result.py | 56 +++++
{.github/workflows => tools/ci}/utils.sh | 0
32 files changed, 1051 insertions(+), 999 deletions(-)
diff --git a/.github/CI.md b/.github/CI.md
new file mode 100644
index 0000000000..d0383aa5ff
--- /dev/null
+++ b/.github/CI.md
@@ -0,0 +1,83 @@
+# Continuous integration
+
+`workflows/ci.yml` is the entry point for pull requests, pushes to any branch
or
+tag, and manual full runs. Contributors can validate changes in their own forks
+before opening an upstream PR, once GitHub Actions is enabled in the fork.
+Both push and PR runs select tests by changed paths. Updating a PR cancels its
+previous CI run; push and publishing runs are not cancelled in progress.
+
+The entry point calls `ci-java.yml`, `ci-python.yml`, `ci-docs.yml` and
+`ci-licensing.yml`. `publish-snapshot*.yml`, `release*.yml` and
+`maintenance-stale-pr.yml` have separate publishing or maintenance triggers.
+
+## Selecting tests
+
+`tools/ci/plan.py` is the single source of path routing and the Java matrix.
+Its selection is shown in the workflow run summary. It uses the PR merge-base
+diff, or the entire before/after push diff. Both sides of a rename are
included.
+Diffs are read from Git without a changed-file API limit. If a complete diff is
+unavailable (including a new branch), CI runs all suites. Manual dispatch also
+runs everything.
+
+PRs check changed files against a 1 MiB size limit. When the PR diff is
unavailable,
+this check is skipped with a warning in the log and run summary so that
existing
+oversized files do not block the full CI run.
+
+| Changed paths | Java tests |
+| --- | --- |
+| `paimon-spark/**` | Spark 3/4, core and integrations, Flink 1/Spark E2E |
+| `paimon-flink/**` | Flink 1/2, core and integrations, both E2E groups,
Iceberg GA |
+| `paimon-e2e-tests/**` | Both E2E groups |
+| `paimon-eslib/**` | ESLib |
+| `paimon-iceberg/**` | Core and integrations, Iceberg GA |
+| `paimon-full-text/**` | Core and integrations, full text |
+| Shared modules, root/build configuration, unclassified source paths | All
Java groups |
+
+Java changes retain the artifact licensing check. Changes to `docs/**` build
the
+website; other Markdown-only changes skip expensive jobs. Changes to
+`paimon-python/**` and Java/Python `JavaPy*` test fixtures run Python checks.
+Root POM changes also run Python to validate its Java build. Mixed changes take
+the union of all required groups. Changes to CI routing or execution scripts
+validate every consumer, including Python and documentation.
+
+The core and integrations jobs deliberately still run for Spark/Flink changes:
+`paimon-spark-common` owns tests outside the Spark connector test jobs,
+`paimon-docs` tests engine configuration documentation, and Hive/Iceberg tests
+depend on Flink. Do not remove these jobs by looking only at the workflow name.
+`SparkE2eTest` runs in the JDK 8 E2E job and is disabled on JDK 11.
+
+## Builds and caches
+
+`tools/ci/run-java-tests.sh` owns Java build/test commands. Each group installs
+its selected modules and their dependencies with `-pl ... -am -DskipTests`,
then
+tests only its modules. The test invocation does not clean away the first build
+or run both `test` and `verify`. Normal Maven checks remain enabled.
+
+Java dependency caches are isolated by suite, JDK and Scala. This prevents a
+small group's cache from permanently starving other groups of their
dependencies.
+Failed Java jobs upload Surefire reports with a seven-day retention period.
+Python builds only the Java modules used by the mixed-language tests and their
+dependencies; its Python version and compatibility coverage is preserved.
+
+## Required checks and maintenance
+
+Configure branch protection to require **CI result** when adopting this layout.
+Remove any requirements referring to the retired individual workflow/job names.
+The result job always runs and requires the planner and every selected group to
+succeed. Unselected groups must be skipped. A planner failure, failed test,
+unexpected skip or cancellation cannot produce a green result.
+
+Every run validates the workflow syntax with pinned actionlint 1.7.11 and runs
+the routing/command tests before starting expensive jobs. Locally:
+
+```sh
+python3 -m unittest discover -s tools/ci -p 'test_*.py'
+actionlint -shellcheck= -pyflakes=
+CI_DRY_RUN=true bash tools/ci/run-java-tests.sh spark3 2.12 8
+```
+
+When adding an engine version, update both the matrix in `plan.py` and the
+module lists in `run-java-tests.sh`, including the core exclusions where
needed.
+Add behavioral tests for any new routing boundary and retain shared-dependency
+fallbacks. Use a manual CI run to validate the complete matrix before adopting
+changes to build profiles or test ownership.
diff --git a/.github/workflows/docs-tests.yml b/.github/workflows/ci-docs.yml
similarity index 84%
rename from .github/workflows/docs-tests.yml
rename to .github/workflows/ci-docs.yml
index d6d1df1987..e9d77e644b 100644
--- a/.github/workflows/docs-tests.yml
+++ b/.github/workflows/ci-docs.yml
@@ -13,16 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-name: docs-tests
+name: CI / Documentation
+
on:
- push:
- pull_request:
- paths:
- - 'docs/**'
+ workflow_call:
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
+permissions:
+ contents: read
jobs:
build:
@@ -31,7 +28,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml
new file mode 100644
index 0000000000..1283bbe19f
--- /dev/null
+++ b/.github/workflows/ci-java.yml
@@ -0,0 +1,73 @@
+################################################################################
+# 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.
+################################################################################
+
+name: CI / Java
+
+on:
+ workflow_call:
+ inputs:
+ matrix:
+ description: Java suites selected by tools/ci/plan.py
+ required: true
+ type: string
+
+permissions:
+ contents: read
+
+env:
+ MAVEN_OPTS: -Xmx4096m -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
+
+jobs:
+ test:
+ name: ${{ matrix.name }}
+ runs-on: ubuntu-latest
+ timeout-minutes: ${{ matrix.timeout }}
+ strategy:
+ # Keep independent results when one engine fails.
+ fail-fast: false
+ matrix: ${{ fromJSON(inputs.matrix) }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v5
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: temurin
+ - name: Cache Maven dependencies
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2/repository
+ key: ci-maven-${{ runner.os }}-${{ runner.arch }}-${{ matrix.suite
}}-jdk${{ matrix.java }}-scala${{ matrix.scala }}-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ci-maven-${{ runner.os }}-${{ runner.arch }}-${{ matrix.suite
}}-jdk${{ matrix.java }}-scala${{ matrix.scala }}-
+ - name: Build dependencies and run tests
+ env:
+ CI_SUITE: ${{ matrix.suite }}
+ CI_SCALA: ${{ matrix.scala }}
+ CI_JAVA: ${{ matrix.java }}
+ run: bash tools/ci/run-java-tests.sh "$CI_SUITE" "$CI_SCALA" "$CI_JAVA"
+ - name: Upload failed test reports
+ if: failure()
+ uses: actions/upload-artifact@v6
+ with:
+ name: test-reports-${{ matrix.suite }}-jdk${{ matrix.java
}}-scala${{ matrix.scala }}
+ path: '**/target/surefire-reports/**'
+ if-no-files-found: ignore
+ retention-days: 7
diff --git a/.github/workflows/check-licensing.yml
b/.github/workflows/ci-licensing.yml
similarity index 82%
rename from .github/workflows/check-licensing.yml
rename to .github/workflows/ci-licensing.yml
index 207cdd13fa..3857c2022e 100644
--- a/.github/workflows/check-licensing.yml
+++ b/.github/workflows/ci-licensing.yml
@@ -13,29 +13,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-name: Check licensing
+name: CI / Licensing
on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
+ workflow_call:
+
+permissions:
+ contents: read
env:
MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
jobs:
- build:
+ check:
+ name: Artifact licensing
+ timeout-minutes: 60
runs-on: ubuntu-latest
env:
MVN_COMMON_OPTIONS: -U -B --no-transfer-progress
@@ -50,6 +42,13 @@ jobs:
with:
java-version: 8
distribution: 'temurin'
+ - name: Cache Maven dependencies
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2/repository
+ key: ci-maven-licensing-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ci-maven-licensing-${{ runner.os }}-${{ runner.arch }}-
- name: Build
run: |
set -o pipefail
diff --git a/.github/workflows/paimon-python-checks.yml
b/.github/workflows/ci-python.yml
old mode 100755
new mode 100644
similarity index 95%
rename from .github/workflows/paimon-python-checks.yml
rename to .github/workflows/ci-python.yml
index ac48f730fc..daffd050ad
--- a/.github/workflows/paimon-python-checks.yml
+++ b/.github/workflows/ci-python.yml
@@ -16,19 +16,13 @@
# limitations under the License.
################################################################################
-name: Python Check Code Style and Test
+name: CI / Python
on:
- push:
- paths:
- - 'paimon-python/**'
- - '!**/*.md'
- - '.github/workflows/paimon-python-checks.yml'
- pull_request:
- paths:
- - 'paimon-python/**'
- - '!**/*.md'
- - '.github/workflows/paimon-python-checks.yml'
+ workflow_call:
+
+permissions:
+ contents: read
env:
JDK_VERSION: 8
@@ -37,13 +31,11 @@ env:
PYPAIMON_RUST_REV: be5456e96b326939570b6ef5d63acddda3311c4f
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
jobs:
# Lint + test on selected versions only (3.6 / 3.7 low end, 3.10 / 3.11 /
3.12 / 3.13 current), not every Python version.
- lint-python:
+ test:
+ name: Tests / Python ${{ matrix.python-version }}
+ timeout-minutes: 90
runs-on: ubuntu-latest
container: "python:${{ matrix.python-version }}-slim"
strategy:
@@ -61,6 +53,14 @@ jobs:
java-version: ${{ env.JDK_VERSION }}
distribution: 'temurin'
+ - name: Cache Maven dependencies
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2/repository
+ key: ci-maven-python-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ci-maven-python-${{ runner.os }}-${{ runner.arch }}-
+
- name: Set up Maven
uses: stCarolas/setup-maven@v5
with:
@@ -116,7 +116,8 @@ jobs:
- name: Build Java
run: |
echo "Start compiling modules"
- mvn -T 2C -B -ntp clean install -DskipTests
+ mvn -T 2C -B -ntp install -DskipTests \
+ -pl
paimon-core,paimon-lance,paimon-vortex/paimon-vortex-format,paimon-full-text,paimon-lumina,paimon-vector
-am
- name: Install Python dependencies
shell: bash
@@ -184,7 +185,9 @@ jobs:
fi
./paimon-python/dev/lint-python.sh -e pytest_torch
- torch_test:
+ torch:
+ name: PyTorch / Python 3.10
+ timeout-minutes: 60
runs-on: ubuntu-latest
container: "python:3.10-slim"
@@ -219,7 +222,9 @@ jobs:
./paimon-python/dev/lint-python.sh -i pytest_torch
# One job: check dev/requirements.txt on each Python version in sequence,
then Ray version test on 3.10.
- requirement_version_compatible_test:
+ compatibility:
+ name: Requirements and Ray compatibility
+ timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- name: Checkout code
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000000..f250062d4b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,112 @@
+################################################################################
+# 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.
+################################################################################
+
+name: CI
+
+on:
+ pull_request:
+ push:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.event_name }}-${{ github.event.pull_request.number ||
github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+jobs:
+ plan:
+ name: Select tests and validate CI
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ outputs:
+ java: ${{ steps.plan.outputs.java }}
+ matrix: ${{ steps.plan.outputs.matrix }}
+ python: ${{ steps.plan.outputs.python }}
+ docs: ${{ steps.plan.outputs.docs }}
+ licensing: ${{ steps.plan.outputs.licensing }}
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Test CI selection and Maven commands
+ run: python3 -m unittest discover -s tools/ci -p 'test_*.py'
+ - name: Validate workflow syntax
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ gh release download v1.7.11 --repo rhysd/actionlint \
+ --pattern 'actionlint_1.7.11_linux_amd64.tar.gz' --dir
"$RUNNER_TEMP"
+ tar -xzf "$RUNNER_TEMP/actionlint_1.7.11_linux_amd64.tar.gz" -C
"$RUNNER_TEMP" actionlint
+ "$RUNNER_TEMP/actionlint" -shellcheck= -pyflakes=
+ - name: Select affected tests and check changed file sizes
+ id: plan
+ run: python3 tools/ci/plan.py
+
+ java:
+ name: Java
+ needs: plan
+ if: needs.plan.outputs.java == 'true'
+ uses: ./.github/workflows/ci-java.yml
+ with:
+ matrix: ${{ needs.plan.outputs.matrix }}
+
+ python:
+ name: Python
+ needs: plan
+ if: needs.plan.outputs.python == 'true'
+ uses: ./.github/workflows/ci-python.yml
+
+ docs:
+ name: Documentation
+ needs: plan
+ if: needs.plan.outputs.docs == 'true'
+ uses: ./.github/workflows/ci-docs.yml
+
+ licensing:
+ name: Licensing
+ needs: plan
+ if: needs.plan.outputs.licensing == 'true'
+ uses: ./.github/workflows/ci-licensing.yml
+
+ result:
+ # Use this stable check for branch protection, not individual matrix jobs.
+ name: CI result
+ if: always()
+ needs: [plan, java, python, docs, licensing]
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Require all selected jobs to pass
+ env:
+ RESULTS: ${{ toJSON(needs) }}
+ run: |
+ python3 - <<'PYTHON'
+ import json
+ import os
+ results = json.loads(os.environ['RESULTS'])
+ if results['plan']['result'] != 'success':
+ raise SystemExit('CI selection or validation failed')
+ selected = results['plan']['outputs']
+ for name in ('java', 'python', 'docs', 'licensing'):
+ expected = 'success' if selected[name] == 'true' else 'skipped'
+ if results[name]['result'] != expected:
+ raise SystemExit(name + ': ' + results[name]['result'] + ',
expected ' + expected)
+ PYTHON
diff --git a/.github/workflows/e2e-tests-flink-1.x.yml
b/.github/workflows/e2e-tests-flink-1.x.yml
deleted file mode 100644
index 3d7c25b600..0000000000
--- a/.github/workflows/e2e-tests-flink-1.x.yml
+++ /dev/null
@@ -1,80 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: End to End Tests Flink 1.x
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
- timeout-minutes: 60
- strategy:
- fail-fast: true
- matrix:
- # Only Test Latest Version
- flink_version: [ '1.20' ]
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Flink
- run: mvn -T 2C -B -ntp clean install -DskipTests -Pflink1,spark3 -pl
paimon-e2e-tests -am -Pflink-${{ matrix.flink_version }}
-
- - name: Test Flink
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- profile="flink-${{ matrix.flink_version }}"
- if [ "${{ matrix.flink_version }}" = "${{ matrix.flink_version[-1]
}}" ]; then
- mvn -T 1C -B -ntp test -Pflink1,spark3 -pl paimon-e2e-tests
-Duser.timezone=$jvm_timezone
- else
- mvn -T 1C -B -ntp test -Pflink1,spark3 -pl paimon-e2e-tests
-Duser.timezone=$jvm_timezone -P${profile}
- fi
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/e2e-tests-flink-2.x-jdk11.yml
b/.github/workflows/e2e-tests-flink-2.x-jdk11.yml
deleted file mode 100644
index 548916d106..0000000000
--- a/.github/workflows/e2e-tests-flink-2.x-jdk11.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: End to End Tests Flink 2.x on JDK 11
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 11
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
- timeout-minutes: 60
- strategy:
- fail-fast: true
- matrix:
- # Only Test Latest Version
- flink_version: [ '2.2' ]
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Flink
- run: |
- mvn -T 2C -B -ntp clean install -DskipTests -Pflink2,spark3 -pl
paimon-e2e-tests -am -Pflink-${{ matrix.flink_version }},java11
-
- - name: Test Flink
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- profile="flink-${{ matrix.flink_version }}"
- if [ "${{ matrix.flink_version }}" = "${{ matrix.flink_version[-1]
}}" ]; then
- mvn -T 1C -B -ntp test -Pflink2,spark3 -pl paimon-e2e-tests
-Duser.timezone=$jvm_timezone -Pjava11
- else
- mvn -T 1C -B -ntp test -Pflink2,spark3 -pl paimon-e2e-tests
-Duser.timezone=$jvm_timezone -P${profile},java11
- fi
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/file-size-check.yml
b/.github/workflows/file-size-check.yml
deleted file mode 100644
index 9b4f073b0a..0000000000
--- a/.github/workflows/file-size-check.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: Check File Size
-
-on:
- pull_request:
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-jobs:
- check-file-size:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
- with:
- fetch-depth: 0
-
- - name: Check file size
- run: |
- files=$(git diff --name-only ${{ github.event.pull_request.base.sha
}}...${{ github.event.pull_request.head.sha }})
- for file in $files; do
- if [ -f "$file" ]; then
- size=$(ls -l "$file" | awk '{print $5}')
- if [ "$size" -gt 1048576 ]; then
- echo "Error: File $file is larger than 1MB ($size bytes)."
- exit 1
- fi
- fi
- done
diff --git a/.github/workflows/stale-pr.yml
b/.github/workflows/maintenance-stale-pr.yml
similarity index 98%
rename from .github/workflows/stale-pr.yml
rename to .github/workflows/maintenance-stale-pr.yml
index ca5c3dae46..252f8c7237 100644
--- a/.github/workflows/stale-pr.yml
+++ b/.github/workflows/maintenance-stale-pr.yml
@@ -19,7 +19,7 @@
#
# See [email protected] "Stale PR cleanup for Paimon" thread.
-name: Stale PR reminder
+name: Maintenance / Stale PR reminder
on:
schedule:
diff --git a/.github/workflows/publish_snapshot-jdk17.yml
b/.github/workflows/publish-snapshot-spark4.yml
similarity index 89%
rename from .github/workflows/publish_snapshot-jdk17.yml
rename to .github/workflows/publish-snapshot-spark4.yml
index 5835a4d14b..c1b8eabeb0 100644
--- a/.github/workflows/publish_snapshot-jdk17.yml
+++ b/.github/workflows/publish-snapshot-spark4.yml
@@ -16,7 +16,7 @@
# limitations under the License.
################################################################################
-name: Publish Snapshot on JDK 17
+name: Publish / Snapshot / Spark 4
on:
schedule:
@@ -29,12 +29,16 @@ env:
MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
+ group: publish-snapshot-spark4-${{ github.ref }}
+ cancel-in-progress: false
+
+permissions:
+ contents: read
jobs:
publish-snapshot:
if: github.repository == 'apache/paimon'
+ timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -48,9 +52,9 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.m2/repository
- key: snapshot-maven-${{ hashFiles('**/pom.xml') }}
+ key: snapshot-maven-spark4-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('**/pom.xml') }}
restore-keys: |
- snapshot-maven-
+ snapshot-maven-spark4-${{ runner.os }}-${{ runner.arch }}-
- name: Publish snapshot
env:
ASF_USERNAME: ${{ secrets.NEXUS_USER }}
diff --git a/.github/workflows/publish_snapshot.yml
b/.github/workflows/publish-snapshot.yml
similarity index 89%
rename from .github/workflows/publish_snapshot.yml
rename to .github/workflows/publish-snapshot.yml
index b1a9335dc4..b1f201593c 100644
--- a/.github/workflows/publish_snapshot.yml
+++ b/.github/workflows/publish-snapshot.yml
@@ -16,7 +16,7 @@
# limitations under the License.
################################################################################
-name: Publish Snapshot
+name: Publish / Snapshot / JDK 8
on:
schedule:
@@ -29,12 +29,16 @@ env:
MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
+ group: publish-snapshot-${{ github.ref }}
+ cancel-in-progress: false
+
+permissions:
+ contents: read
jobs:
publish-snapshot:
if: github.repository == 'apache/paimon'
+ timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -50,9 +54,9 @@ jobs:
uses: actions/cache@v5
with:
path: ~/.m2/repository
- key: snapshot-maven-${{ hashFiles('**/pom.xml') }}
+ key: snapshot-maven-jdk8-${{ runner.os }}-${{ runner.arch }}-${{
hashFiles('**/pom.xml') }}
restore-keys: |
- snapshot-maven-
+ snapshot-maven-jdk8-${{ runner.os }}-${{ runner.arch }}-
- name: Publish snapshot
env:
ASF_USERNAME: ${{ secrets.NEXUS_USER }}
diff --git a/.github/workflows/release-java.yml
b/.github/workflows/release-java.yml
index 12160e9808..fe6ed56e0f 100644
--- a/.github/workflows/release-java.yml
+++ b/.github/workflows/release-java.yml
@@ -16,7 +16,7 @@
# limitations under the License.
################################################################################
-name: Release Java
+name: Release / Java packages
on:
workflow_call:
diff --git a/.github/workflows/release-python-publish.yml
b/.github/workflows/release-python-publish.yml
index 9ae869a590..a153ff2d4c 100644
--- a/.github/workflows/release-python-publish.yml
+++ b/.github/workflows/release-python-publish.yml
@@ -16,7 +16,7 @@
# limitations under the License.
################################################################################
-name: Release PyPaimon Publish
+name: Release / Python publish
on:
workflow_call:
diff --git a/.github/workflows/release-python.yml
b/.github/workflows/release-python.yml
index 0dc5112821..c8d3ef32e2 100644
--- a/.github/workflows/release-python.yml
+++ b/.github/workflows/release-python.yml
@@ -19,7 +19,7 @@
# Build the PyPaimon source distribution and universal wheel. Publishing is
# handled by release-python-publish.yml after this package job passes.
-name: Release PyPaimon Package
+name: Release / Python packages
on:
workflow_call:
diff --git a/.github/workflows/utcase-eslib.yml
b/.github/workflows/utcase-eslib.yml
deleted file mode 100644
index a5dc7d9d20..0000000000
--- a/.github/workflows/utcase-eslib.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase ESLib on JDK 11
-
-on:
- push:
- paths:
- - 'paimon-api/**'
- - 'paimon-common/**'
- - 'paimon-eslib/**'
- - 'paimon-test-utils/**'
- - 'pom.xml'
- - '.github/workflows/utcase-eslib.yml'
- pull_request:
- paths:
- - 'paimon-api/**'
- - 'paimon-common/**'
- - 'paimon-eslib/**'
- - 'paimon-test-utils/**'
- - 'pom.xml'
- - '.github/workflows/utcase-eslib.yml'
-
-permissions:
- contents: read
-
-env:
- JDK_VERSION: 11
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- eslib_test:
- runs-on: ubuntu-latest
- timeout-minutes: 30
-
- steps:
- - name: Checkout code
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- with:
- persist-credentials: false
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build ESLib dependencies
- run: mvn -T 2C -B -ntp clean install -DskipTests -Ppaimon-eslib -pl
paimon-eslib -am
-
- - name: Test ESLib
- run: mvn -B -ntp verify -Ppaimon-eslib -pl paimon-eslib
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utcase-full-text.yml
b/.github/workflows/utcase-full-text.yml
deleted file mode 100644
index c9904529cf..0000000000
--- a/.github/workflows/utcase-full-text.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase Full Text
-
-on:
- push:
- paths:
- - 'paimon-full-text/**'
- - 'pom.xml'
- - '.github/workflows/utcase-full-text.yml'
- pull_request:
- paths:
- - 'paimon-full-text/**'
- - 'pom.xml'
- - '.github/workflows/utcase-full-text.yml'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- full_text_test:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build and test full-text module
- timeout-minutes: 30
- run: |
- mvn -T 2C -B -ntp clean install -DskipTests
- mvn -B -ntp verify -pl paimon-full-text -Dcheckstyle.skip=true
-Dspotless.check.skip=true
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase-flink-1.x-common.yml
b/.github/workflows/utitcase-flink-1.x-common.yml
deleted file mode 100644
index 59d124804d..0000000000
--- a/.github/workflows/utitcase-flink-1.x-common.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-name: UTCase and ITCase Flink 1.x Common
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
- timeout-minutes: 100
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Flink
- run: |
- COMPILE_MODULE="org.apache.paimon:paimon-flink-common"
- echo "Start compiling modules: $COMPILE_MODULE"
- mvn -T 2C -B -ntp clean install -DskipTests -Pflink1,spark3 -pl
"${COMPILE_MODULE}" -am
-
- - name: Test Flink
- run: |
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- TEST_MODULE="org.apache.paimon:paimon-flink-common"
- echo "Start testing module: $TEST_MODULE"
- mvn -T 2C -B -ntp test verify -Pflink1,spark3 -pl "${TEST_MODULE}"
-Duser.timezone=$jvm_timezone
- echo "All modules tested"
- env:
- MAVEN_OPTS: -Xmx4096m -XX:+UseG1GC -XX:CICompilerCount=2
diff --git a/.github/workflows/utitcase-flink-1.x-others.yml
b/.github/workflows/utitcase-flink-1.x-others.yml
deleted file mode 100644
index d042f1a8ed..0000000000
--- a/.github/workflows/utitcase-flink-1.x-others.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-name: UTCase and ITCase Flink 1.x Others
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
- timeout-minutes: 60
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Flink
- run: |
- mvn -T 2C -B -ntp clean install -DskipTests -Pflink1,spark3
-
- - name: Test Flink
- run: |
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- test_modules=""
- for suffix in cdc 1.16 1.17 1.18 1.19 1.20; do
- test_modules+="org.apache.paimon:paimon-flink-${suffix},"
- done
- test_modules="${test_modules%,}"
- mvn -T 2C -B -ntp test verify -Pflink1,spark3 -pl "${test_modules}"
-Duser.timezone=$jvm_timezone
- env:
- MAVEN_OPTS: -Xmx4096m -XX:+UseG1GC -XX:CICompilerCount=2
diff --git a/.github/workflows/utitcase-flink-2.x-jdk11.yml
b/.github/workflows/utitcase-flink-2.x-jdk11.yml
deleted file mode 100644
index b1b6005867..0000000000
--- a/.github/workflows/utitcase-flink-2.x-jdk11.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase and ITCase Flink 2.x on JDK 11
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 11
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.ref }}
- cancel-in-progress: ${{ github.event_name == 'pull_request' }}
-
-jobs:
- build:
- runs-on: ubuntu-latest
- timeout-minutes: 120
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Flink
- run: |
- mvn -T 2C -B -ntp clean install -DskipTests -Pflink2,spark3
-
- - name: Test Flink
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- test_modules=""
- for suffix in 2.0 2.1 2.2 common; do
- test_modules+="org.apache.paimon:paimon-flink-${suffix},"
- done
- test_modules="${test_modules%,}"
- mvn -T 2C -B -ntp test verify -Pflink2,spark3 -pl "${test_modules}"
-Duser.timezone=$jvm_timezone
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase-iceberg-ga.yml
b/.github/workflows/utitcase-iceberg-ga.yml
deleted file mode 100644
index deca2434fc..0000000000
--- a/.github/workflows/utitcase-iceberg-ga.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase Iceberg GA row lineage on JDK 17
-
-on:
- push:
- paths:
- - 'paimon-iceberg/**'
- - 'paimon-core/**'
- - 'paimon-common/**'
- - 'paimon-api/**'
- - 'paimon-format/**'
- - 'pom.xml'
- - '.github/workflows/utitcase-iceberg-ga.yml'
- pull_request:
- paths:
- - 'paimon-iceberg/**'
- - 'paimon-core/**'
- - 'paimon-common/**'
- - 'paimon-api/**'
- - 'paimon-format/**'
- - 'pom.xml'
- - '.github/workflows/utitcase-iceberg-ga.yml'
-
-env:
- JDK_VERSION: 17
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
- - name: Build
- run: mvn -T 1C -B -ntp clean install -DskipTests -pl paimon-iceberg
-am -Ppaimon-iceberg,iceberg-ga
- - name: Test against GA Iceberg
- run: mvn -B -ntp test -pl paimon-iceberg -Ppaimon-iceberg,iceberg-ga
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase-jdk11.yml
b/.github/workflows/utitcase-jdk11.yml
deleted file mode 100644
index a6ae09a52b..0000000000
--- a/.github/workflows/utitcase-jdk11.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase and ITCase Others on JDK 11
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 11
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
- - name: Build
- run: mvn -T 1C -B -ntp clean install -DskipTests -Pflink1,spark3
- - name: Test
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
-
test_modules="!paimon-e2e-tests,!org.apache.paimon:paimon-hive-connector-3.1,"
- for suffix in ut 3.5 3.4 3.3 3.2; do
- test_modules+="!org.apache.paimon:paimon-spark-${suffix}_2.12,"
- done
- test_modules="${test_modules%,}"
- mvn -T 1C -B -ntp clean install -pl "${test_modules}"
-Pflink1,spark3 -Pskip-paimon-flink-tests -Duser.timezone=$jvm_timezone
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase-spark-3.x.yml
b/.github/workflows/utitcase-spark-3.x.yml
deleted file mode 100644
index 0fe7923060..0000000000
--- a/.github/workflows/utitcase-spark-3.x.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase and ITCase Spark 3.x
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
- strategy:
- fail-fast: true
- matrix:
- scala_version: [ '2.12', '2.13' ]
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Spark 3 with ${{ matrix.scala_version }}
- run: mvn -T 2C -B -ntp clean install -DskipTests
-Pspark3,flink1,scala-${{ matrix.scala_version }}
-
- - name: Test Spark 3 with ${{ matrix.scala_version }}
- timeout-minutes: 60
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- test_modules=""
- for suffix in ut 3.5 3.4 3.3 3.2; do
- test_modules+="org.apache.paimon:paimon-spark-${suffix}_${{
matrix.scala_version }},"
- done
- test_modules="${test_modules%,}"
- mvn -T 2C -B -ntp verify -pl "${test_modules}"
-Duser.timezone=$jvm_timezone -Pspark3,flink1,scala-${{ matrix.scala_version }}
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase-spark-4.x.yml
b/.github/workflows/utitcase-spark-4.x.yml
deleted file mode 100644
index 72c43c018e..0000000000
--- a/.github/workflows/utitcase-spark-4.x.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase and ITCase Spark 4.x
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 17
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Spark
- run: mvn -T 2C -B -ntp clean install -DskipTests -Pspark4,flink1
-
- - name: Test Spark
- timeout-minutes: 60
- run: |
- # run tests with random timezone to find out timezone related bugs
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
- test_modules=""
- for suffix in ut 4.0 4.1; do
- test_modules+="org.apache.paimon:paimon-spark-${suffix}_2.13,"
- done
- test_modules="${test_modules%,}"
- mvn -T 2C -B -ntp verify -pl "${test_modules}"
-Duser.timezone=$jvm_timezone -Pspark4,flink1
- env:
- MAVEN_OPTS: -Xmx4096m
diff --git a/.github/workflows/utitcase.yml b/.github/workflows/utitcase.yml
deleted file mode 100644
index e83be074f4..0000000000
--- a/.github/workflows/utitcase.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-################################################################################
-# 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.
-################################################################################
-
-name: UTCase and ITCase Others
-
-on:
- push:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
- pull_request:
- paths-ignore:
- - 'docs/**'
- - '**/*.md'
- - 'paimon-python/**'
- - '.github/workflows/paimon-python-checks.yml'
- - 'paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java'
-
-env:
- JDK_VERSION: 8
- MAVEN_OPTS: -Dmaven.wagon.httpconnectionManager.ttlSeconds=30
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{
github.event.number || github.run_id }}
- cancel-in-progress: true
-
-jobs:
- build_test:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v6
-
- - name: Set up JDK ${{ env.JDK_VERSION }}
- uses: actions/setup-java@v5
- with:
- java-version: ${{ env.JDK_VERSION }}
- distribution: 'temurin'
-
- - name: Build Others
- run: |
- echo "Start compiling modules"
- mvn -T 2C -B -ntp clean install -DskipTests -Pflink1,spark3
-
- - name: Test Others
- timeout-minutes: 60
- run: |
- . .github/workflows/utils.sh
- jvm_timezone=$(random_timezone)
- echo "JVM timezone is set to $jvm_timezone"
-
- TEST_MODULES="!paimon-e2e-tests,"
- for suffix in ut 3.5 3.4 3.3 3.2; do
- TEST_MODULES+="!org.apache.paimon:paimon-spark-${suffix}_2.12,"
- done
- TEST_MODULES="${TEST_MODULES%,}"
- mvn -T 2C -B -ntp clean install -pl "${TEST_MODULES}"
-Pskip-paimon-flink-tests -Duser.timezone=$jvm_timezone
- env:
- MAVEN_OPTS: -Xmx4096m -XX:+UseG1GC -XX:CICompilerCount=2
diff --git a/docs/docs/project/verifying-a-release-candidate.md
b/docs/docs/project/verifying-a-release-candidate.md
index c0a5b55933..f3cd609281 100644
--- a/docs/docs/project/verifying-a-release-candidate.md
+++ b/docs/docs/project/verifying-a-release-candidate.md
@@ -343,7 +343,7 @@ archive; the PyPaimon source distribution must not be used
by itself for an
independent release. Run the tests from
`paimon-PAIMON_VERSION/paimon-python` on as many supported Python versions as
your environment allows. Use the Python matrix in the candidate's
-`.github/workflows/paimon-python-checks.yml` to select relevant versions rather
+`.github/workflows/ci-python.yml` to select relevant versions rather
than assuming that a matrix from another release applies:
```shell
diff --git a/tools/ci/plan.py b/tools/ci/plan.py
new file mode 100644
index 0000000000..b539f45a60
--- /dev/null
+++ b/tools/ci/plan.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+# 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.
+
+"""Select CI suites from the complete git diff, without API pagination
limits."""
+
+import json
+import os
+from pathlib import Path
+import re
+import subprocess
+
+
+# Keep the existing JDK, engine and Scala coverage. A suite is a test boundary,
+# not just a source directory: core also owns Hive and generated-doc tests.
+SUITES = [
+ ("core", "Core and integrations / JDK 8", 8, "2.12", 90),
+ ("core", "Core and integrations / JDK 11", 11, "2.12", 90),
+ ("flink1-common", "Flink 1 / Common", 8, "2.12", 100),
+ ("flink1-connectors", "Flink 1 / Connectors and CDC", 8, "2.12", 60),
+ ("flink2", "Flink 2 / Common and connectors", 11, "2.12", 120),
+ ("spark3", "Spark 3 / Scala 2.12", 8, "2.12", 90),
+ ("spark3", "Spark 3 / Scala 2.13", 8, "2.13", 90),
+ ("spark4", "Spark 4 / Scala 2.13", 17, "2.13", 90),
+ ("e2e-flink1", "E2E / Flink 1 and Spark interoperability", 8, "2.12", 60),
+ ("e2e-flink2", "E2E / Flink 2", 11, "2.12", 60),
+ ("iceberg", "Iceberg GA / JDK 17", 17, "2.12", 60),
+ ("eslib", "ESLib / JDK 11", 11, "2.12", 30),
+ ("full-text", "Full text / JDK 8", 8, "2.12", 30),
+]
+ALL_JAVA = {suite[0] for suite in SUITES}
+FLINK = {"flink1-common", "flink1-connectors", "flink2"}
+SPARK = {"spark3", "spark4"}
+E2E = {"e2e-flink1", "e2e-flink2"}
+
+
+def select(paths, full=False):
+ selected = set()
+ docs = python = licensing = False
+ if full:
+ selected.update(ALL_JAVA)
+ docs = python = licensing = True
+
+ for path in paths:
+ if path.startswith("docs/"):
+ docs = True
+ continue
+ if path.endswith(".md"):
+ continue
+ if path.startswith("paimon-python/") or path ==
".github/workflows/ci-python.yml":
+ python = True
+ continue
+ if re.search(r"/src/test/.*?/JavaPy[^/]*\.java$", path):
+ python = True
+ continue
+ if path == ".github/workflows/ci-docs.yml":
+ docs = True
+ continue
+ if path == ".github/workflows/ci-licensing.yml":
+ licensing = True
+ continue
+ # Changes to CI machinery must validate every consumer of that
machinery.
+ if path.startswith((".github/workflows/ci", "tools/ci/")):
+ selected.update(ALL_JAVA)
+ docs = python = licensing = True
+ continue
+ if path.startswith((".github/", ".asf.yaml")):
+ continue
+
+ licensing = True
+ if path.startswith("paimon-spark/"):
+ # Core includes spark-common tests and paimon-docs completeness
tests.
+ # SparkE2eTest is disabled on JDK 11, so only the Flink 1 E2E lane
applies.
+ selected.update(SPARK | {"core", "e2e-flink1"})
+ elif path.startswith("paimon-flink/"):
+ # Hive and Iceberg integration tests depend on flink-common.
+ selected.update(FLINK | E2E | {"core", "iceberg"})
+ elif path.startswith("paimon-e2e-tests/"):
+ selected.update(E2E)
+ elif path.startswith("paimon-eslib/"):
+ selected.add("eslib")
+ elif path.startswith("paimon-iceberg/"):
+ selected.update({"core", "iceberg"})
+ elif path.startswith("paimon-full-text/"):
+ selected.update({"core", "full-text"})
+ else:
+ # Shared modules, POMs outside a known leaf, build configuration
and
+ # new/unknown directories fall back to all Java suites.
+ selected.update(ALL_JAVA)
+ if path == "pom.xml":
+ python = True
+
+ matrix = [
+ dict(suite=suite, name=name, java=java, scala=scala, timeout=timeout)
+ for suite, name, java, scala, timeout in SUITES
+ if suite in selected
+ ]
+ return dict(java=bool(matrix), matrix={"include": matrix}, docs=docs,
+ python=python, licensing=licensing)
+
+
+def changed_paths(event_name, event):
+ if event_name == "pull_request":
+ base = event["pull_request"]["base"]["sha"]
+ head = event["pull_request"]["head"]["sha"]
+ separator = "..."
+ elif event_name == "push":
+ base, head = event["before"], event["after"]
+ separator = ".."
+ else:
+ return None
+ for sha in (base, head):
+ if not re.fullmatch(r"[0-9a-f]{40}", sha) or sha == "0" * 40:
+ return None
+ try:
+ # Disabling rename detection includes both the old and new path. -z
also
+ # handles whitespace, newlines and non-ASCII names without shell
parsing.
+ output = subprocess.check_output([
+ "git", "diff", "--no-renames", "--name-only", "-z",
+ base + separator + head, "--",
+ ])
+ except subprocess.CalledProcessError:
+ print("Cannot resolve the complete diff; selecting full CI.")
+ return None
+ return [os.fsdecode(path) for path in output.split(b"\0") if path]
+
+
+def check_file_sizes(paths, root=Path(".")):
+ for path in paths:
+ candidate = root / path
+ # Git symlinks contain a link target; don't follow them outside
checkout.
+ if candidate.is_symlink() or not candidate.is_file():
+ continue
+ if candidate.stat().st_size > 1048576:
+ raise ValueError("Changed file exceeds 1 MiB: " + repr(path))
+
+
+def main():
+ event_name = os.environ["GITHUB_EVENT_NAME"]
+ event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
+ paths = changed_paths(event_name, event)
+ plan = select(paths or [], full=paths is None)
+ size_warning = None
+ if event_name == "pull_request":
+ if paths is None:
+ # Existing oversized files must not block the full-CI fallback.
+ size_warning = "Skipping changed-file size check because the PR
diff is unavailable."
+ print("::warning::" + size_warning)
+ else:
+ check_file_sizes(paths)
+ with open(os.environ["GITHUB_OUTPUT"], "a") as output:
+ for key, value in plan.items():
+ output.write(key + "=" + json.dumps(value, separators=(",", ":"))
+ "\n")
+ with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as summary:
+ summary.write("## CI selection\n\n")
+ summary.write("Full run (manual or unavailable diff).\n\n" if paths is
None
+ else "Compared {} changed paths.\n\n".format(len(paths)))
+ if size_warning:
+ summary.write("**Warning:** " + size_warning + "\n\n")
+ for lane in plan["matrix"]["include"]:
+ summary.write("- " + lane["name"] + "\n")
+ for key in ("python", "docs", "licensing"):
+ summary.write("- {}: {}\n".format(key, "run" if plan[key] else
"skip"))
+ print(json.dumps(plan, indent=2))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/ci/run-java-tests.sh b/tools/ci/run-java-tests.sh
new file mode 100644
index 0000000000..b5a7de266c
--- /dev/null
+++ b/tools/ci/run-java-tests.sh
@@ -0,0 +1,129 @@
+#!/usr/bin/env bash
+# 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.
+
+set -euo pipefail
+
+suite=${1:?Specify a CI suite}
+scala=${2:-2.12}
+java=${3:-8}
+profiles=flink1,spark3
+threads=2C
+goal=verify
+test_options=(-Dstyle.color=never)
+modules=()
+
+case "$suite" in
+ core)
+ # Keep spark-common and paimon-docs here: they have their own tests,
+ # including JDK 11 coverage, outside the Spark connector test modules.
+ modules=('!paimon-e2e-tests')
+ for version in ut 3.5 3.4 3.3 3.2; do
+ modules+=("!org.apache.paimon:paimon-spark-${version}_2.12")
+ done
+ if [[ "$java" == 11 ]]; then
+ modules+=('!org.apache.paimon:paimon-hive-connector-3.1')
+ threads=1C
+ fi
+ test_options+=(-Pskip-paimon-flink-tests)
+ ;;
+ flink1-common)
+ modules=(paimon-flink/paimon-flink-common)
+ ;;
+ flink1-connectors)
+ for version in cdc 1.16 1.17 1.18 1.19 1.20; do
+ modules+=("org.apache.paimon:paimon-flink-${version}")
+ done
+ ;;
+ flink2)
+ profiles=flink2,spark3
+ for version in 2.0 2.1 2.2 common; do
+ modules+=("org.apache.paimon:paimon-flink-${version}")
+ done
+ ;;
+ spark3)
+ profiles="flink1,spark3,scala-${scala}"
+ for version in ut 3.5 3.4 3.3 3.2; do
+ modules+=("org.apache.paimon:paimon-spark-${version}_${scala}")
+ done
+ ;;
+ spark4)
+ profiles=flink1,spark4
+ for version in ut 4.0 4.1; do
+ modules+=("org.apache.paimon:paimon-spark-${version}_2.13")
+ done
+ ;;
+ e2e-flink1)
+ profiles=flink1,spark3,flink-1.20
+ modules=(paimon-e2e-tests)
+ threads=1C
+ goal=test
+ ;;
+ e2e-flink2)
+ profiles=flink2,spark3,flink-2.2,java11
+ modules=(paimon-e2e-tests)
+ threads=1C
+ goal=test
+ ;;
+ iceberg)
+ profiles=flink1,spark3,paimon-iceberg,iceberg-ga
+ modules=(paimon-iceberg)
+ threads=1C
+ goal=test
+ ;;
+ eslib)
+ profiles=paimon-eslib
+ modules=(paimon-eslib)
+ threads=1
+ ;;
+ full-text)
+ modules=(paimon-full-text)
+ threads=1
+ ;;
+ *)
+ echo "Unknown CI suite: $suite" >&2
+ exit 1
+ ;;
+esac
+
+module_list=$(IFS=,; echo "${modules[*]}")
+
+run_maven() {
+ if [[ "${CI_DRY_RUN:-false}" == true ]]; then
+ printf '%q ' mvn "$@"
+ printf '\n'
+ else
+ mvn "$@"
+ fi
+}
+
+# -am compiles and installs the selected tests' dependency closure, including
+# test jars. Only the selected modules run tests in the second invocation.
+run_maven -T 2C -B -ntp install -DskipTests -P"$profiles" -pl "$module_list"
-am
+
+jvm_timezone=GMT+00:00
+if [[ "${CI_DRY_RUN:-false}" != true ]]; then
+ # Preserve the native-library setup used by the original engine/core lanes.
+ if [[ "$suite" != eslib && "$suite" != full-text ]]; then
+ . tools/ci/utils.sh
+ jvm_timezone=$(random_timezone)
+ fi
+ echo "JVM timezone is set to $jvm_timezone"
+fi
+
+# verify already includes the test phase; don't execute `test verify` or clean
+# away the dependencies and test classes just built in this fresh checkout.
+run_maven -T "$threads" -B -ntp "$goal" -P"$profiles" -pl "$module_list" \
+ "${test_options[@]}" -Duser.timezone="$jvm_timezone"
diff --git a/tools/ci/test_java_commands.py b/tools/ci/test_java_commands.py
new file mode 100644
index 0000000000..5cfc7aa487
--- /dev/null
+++ b/tools/ci/test_java_commands.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# 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.
+
+"""Validate suite coverage and Maven command boundaries without running
Maven."""
+
+import os
+from pathlib import Path
+import re
+import shlex
+import subprocess
+import unittest
+import xml.etree.ElementTree as ET
+
+from plan import SUITES
+
+
+SCRIPT = Path(__file__).with_name('run-java-tests.sh')
+
+
+class MavenCommandsTest(unittest.TestCase):
+ def commands(self, suite, scala='2.12', java=8):
+ output = subprocess.check_output(
+ ['bash', str(SCRIPT), suite, scala, str(java)],
+ env=dict(os.environ, CI_DRY_RUN='true'), text=True)
+ return [shlex.split(line) for line in output.splitlines()]
+
+ def modules(self, command):
+ return set(command[command.index('-pl') + 1].split(','))
+
+ def
test_every_lane_builds_dependencies_but_only_tests_selected_modules(self):
+ for suite, name, java, scala, timeout in SUITES:
+ with self.subTest(name=name):
+ build, test = self.commands(suite, scala, java)
+ self.assertIn('-am', build)
+ self.assertIn('-DskipTests', build)
+ self.assertNotIn('-am', test)
+ self.assertNotIn('-DskipTests', test)
+ self.assertEqual(self.modules(build), self.modules(test))
+ self.assertNotIn('clean', test)
+ self.assertFalse('test' in test and 'verify' in test)
+ self.assertFalse(any('fast-build' in arg for arg in build +
test))
+
+ def test_spark_retains_all_connector_versions_and_scala_variants(self):
+ for scala in ('2.12', '2.13'):
+ test = self.commands('spark3', scala)[1]
+ for version in ('ut', '3.2', '3.3', '3.4', '3.5'):
+ self.assertIn('org.apache.paimon:paimon-spark-' + version +
'_' + scala,
+ self.modules(test))
+ self.assertIn('-Pflink1,spark3,scala-' + scala, test)
+ self.assertEqual(self.modules(self.commands('spark4', '2.13', 17)[1]),
+ {'org.apache.paimon:paimon-spark-' + version + '_2.13'
+ for version in ('ut', '4.0', '4.1')})
+
+ def
test_core_keeps_common_and_docs_tests_and_existing_jdk11_exclusion(self):
+ for java in (8, 11):
+ test = self.commands('core', java=java)[1]
+ self.assertIn('-Pskip-paimon-flink-tests', test)
+ self.assertNotIn('!org.apache.paimon:paimon-spark-common_2.12',
self.modules(test))
+ self.assertNotIn('!paimon-docs', self.modules(test))
+ self.assertEqual('!org.apache.paimon:paimon-hive-connector-3.1' in
self.modules(test),
+ java == 11)
+
+ def test_flink_coverage_includes_cdc_and_both_major_versions(self):
+ self.assertEqual(self.modules(self.commands('flink1-connectors')[1]),
+ {'org.apache.paimon:paimon-flink-' + version
+ for version in ('cdc', '1.16', '1.17', '1.18',
'1.19', '1.20')})
+ self.assertEqual(self.modules(self.commands('flink2', java=11)[1]),
+ {'org.apache.paimon:paimon-flink-' + version
+ for version in ('common', '2.0', '2.1', '2.2')})
+
+ def test_e2e_uses_the_same_version_when_building_and_testing(self):
+ for suite, java, version in [('e2e-flink1', 8, '1.20'), ('e2e-flink2',
11, '2.2')]:
+ build, test = self.commands(suite, java=java)
+ self.assertTrue(any('flink-' + version in arg for arg in build))
+ self.assertEqual([arg for arg in build if arg.startswith('-P')],
+ [arg for arg in test if arg.startswith('-P')])
+ self.assertIn('test', test)
+
+ def test_unknown_suite_fails_instead_of_silently_running_no_tests(self):
+ result = subprocess.run(['bash', str(SCRIPT), 'typo'],
+ env=dict(os.environ, CI_DRY_RUN='true'),
capture_output=True)
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn(b'Unknown CI suite', result.stderr)
+
+ def
test_python_build_selects_runnable_modules_not_just_aggregator_poms(self):
+ root = SCRIPT.resolve().parents[2]
+ workflow = (root / '.github/workflows/ci-python.yml').read_text()
+ modules = set(re.search(r'-pl ([^\s]+) -am',
workflow).group(1).split(','))
+ self.assertTrue({'paimon-core', 'paimon-lance', 'paimon-lumina',
+ 'paimon-vortex/paimon-vortex-format',
'paimon-full-text',
+ 'paimon-vector'}.issubset(modules))
+ for module in modules:
+ pom = ET.parse(root / module / 'pom.xml').getroot()
+ packaging =
pom.findtext('{http://maven.apache.org/POM/4.0.0}packaging', 'jar')
+ self.assertNotEqual(packaging, 'pom', module)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/ci/test_plan.py b/tools/ci/test_plan.py
new file mode 100644
index 0000000000..4d0c26a174
--- /dev/null
+++ b/tools/ci/test_plan.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+# 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.
+
+"""Behavioral checks for CI routing and the complete PR/push diff."""
+
+import contextlib
+import io
+import json
+import os
+from pathlib import Path
+import subprocess
+import tempfile
+import unittest
+from unittest.mock import patch
+
+import plan
+
+
+class SelectionTest(unittest.TestCase):
+ def suites(self, paths):
+ return {lane['suite'] for lane in
plan.select(paths)['matrix']['include']}
+
+ def test_spark_keeps_common_docs_and_interoperability_tests(self):
+ suites =
self.suites(['paimon-spark/paimon-spark-common/src/main/scala/Change.scala'])
+ self.assertEqual(suites, {'core', 'spark3', 'spark4', 'e2e-flink1'})
+
+ def test_flink_keeps_hive_iceberg_and_both_e2e_versions(self):
+ suites =
self.suites(['paimon-flink/paimon-flink-common/src/main/java/Change.java'])
+ self.assertEqual(suites, {'core', 'flink1-common', 'flink1-connectors',
+ 'flink2', 'e2e-flink1', 'e2e-flink2',
'iceberg'})
+
+ def test_shared_and_unknown_changes_run_all_java(self):
+ for path in ['paimon-core/Change.java', 'paimon-api/Change.java',
+ 'paimon-format/pom.xml', 'paimon-new-module/pom.xml',
+ '.mvn/jvm.config', '.scalafmt.conf', 'copyright.txt']:
+ with self.subTest(path=path):
+ self.assertEqual(self.suites([path]), plan.ALL_JAVA)
+
+ def test_parent_pom_also_validates_python_build(self):
+ self.assertTrue(plan.select(['pom.xml'])['python'])
+ self.assertEqual(self.suites(['pom.xml']), plan.ALL_JAVA)
+
+ def test_docs_and_markdown_do_not_start_maven(self):
+ result = plan.select(['docs/start.md', 'README.md'])
+ self.assertTrue(result['docs'])
+ self.assertFalse(result['java'])
+ self.assertFalse(result['python'])
+ self.assertFalse(result['licensing'])
+ self.assertFalse(plan.select(['paimon-python/README.md'])['python'])
+
+ def test_mixed_changes_take_union(self):
+ self.assertEqual(self.suites(['docs/intro.md', 'paimon-spark/pom.xml',
+ 'paimon-flink/pom.xml']),
+ {'core', 'spark3', 'spark4', 'flink1-common',
+ 'flink1-connectors', 'flink2', 'e2e-flink1',
+ 'e2e-flink2', 'iceberg'})
+ result = plan.select(['paimon-python/setup.py',
'paimon-core/Change.java'])
+ self.assertTrue(result['python'])
+ self.assertTrue(result['java'])
+
+ def test_java_python_fixtures_run_python(self):
+ for module, name in [('paimon-core', 'JavaPyE2ETest'),
+ ('paimon-lance', 'JavaPyLanceE2ETest'),
+ ('paimon-lumina', 'JavaPyLuminaE2ETest')]:
+ result = plan.select([module + '/src/test/java/org/apache/paimon/'
+ name + '.java'])
+ self.assertTrue(result['python'])
+ self.assertFalse(result['java'])
+
+ def test_leaf_modules_and_workflows(self):
+ self.assertEqual(self.suites(['paimon-eslib/pom.xml']), {'eslib'})
+ self.assertEqual(self.suites(['paimon-e2e-tests/pom.xml']),
+ {'e2e-flink1', 'e2e-flink2'})
+
self.assertTrue(plan.select(['.github/workflows/ci-python.yml'])['python'])
+
self.assertFalse(plan.select(['.github/workflows/ci-python.yml'])['java'])
+ self.assertTrue(plan.select(['.github/workflows/ci-docs.yml'])['docs'])
+
self.assertFalse(plan.select(['.github/workflows/release-java.yml'])['java'])
+
+ def test_ci_logic_and_manual_run_validate_every_consumer(self):
+ for result in [plan.select(['tools/ci/plan.py']),
+ plan.select(['.github/workflows/ci-java.yml']),
+ plan.select([], full=True)]:
+ self.assertTrue(all(result[key] for key in ('java', 'python',
'docs', 'licensing')))
+ self.assertEqual(len(result['matrix']['include']), 13)
+
+ def test_empty_diff_does_not_run_expensive_jobs(self):
+ result = plan.select([])
+ self.assertFalse(any(result[key] for key in ('java', 'python', 'docs',
'licensing')))
+ self.assertEqual(result['matrix'], {'include': []})
+
+ def test_size_guard_handles_spaces_deletions_and_symlinks(self):
+ with tempfile.TemporaryDirectory() as temp:
+ root = Path(temp)
+ large = root / 'large file.txt'
+ large.write_bytes(b'x' * (1048576 + 1))
+ (root / 'link').symlink_to(large)
+ plan.check_file_sizes(['deleted.txt', 'link'], root)
+ with self.assertRaisesRegex(ValueError, 'large file'):
+ plan.check_file_sizes(['large file.txt'], root)
+
+
+class GitDiffTest(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.previous = Path.cwd()
+ os.chdir(self.temp.name)
+ self.addCleanup(os.chdir, self.previous)
+ self.git('init', '-q')
+ self.git('config', 'user.email', '[email protected]')
+ self.git('config', 'user.name', 'CI test')
+ self.commit('base.txt', 'base')
+ self.base = self.git('rev-parse', 'HEAD')
+
+ def git(self, *args):
+ return subprocess.check_output(['git', *args],
stderr=subprocess.DEVNULL).decode().strip()
+
+ def commit(self, name, content):
+ path = Path(name)
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(content)
+ self.git('add', '--', name)
+ self.git('commit', '-qm', 'Test commit')
+
+ def test_pr_uses_merge_base_instead_of_including_base_branch_changes(self):
+ self.commit('base-only.txt', 'base change')
+ base_tip = self.git('rev-parse', 'HEAD')
+ self.git('checkout', '-q', '--detach', self.base)
+ self.commit('paimon-spark/name with spaces\nand newline.scala',
'spark')
+ head = self.git('rev-parse', 'HEAD')
+ paths = plan.changed_paths('pull_request', {
+ 'pull_request': {'base': {'sha': base_tip}, 'head': {'sha':
head}}})
+ self.assertEqual(paths, ['paimon-spark/name with spaces\nand
newline.scala'])
+
+ def test_push_includes_all_commits_and_both_sides_of_renames(self):
+ self.commit('paimon-spark/A.scala', 'spark')
+ before = self.git('rev-parse', 'HEAD')
+ Path('paimon-flink').mkdir()
+ self.git('mv', 'paimon-spark/A.scala', 'paimon-flink/A.java')
+ self.git('commit', '-qm', 'Move across modules')
+ self.commit('docs/intro.md', 'docs')
+ paths = plan.changed_paths('push', {'before': before,
+ 'after': self.git('rev-parse',
'HEAD')})
+ self.assertEqual(set(paths), {'paimon-spark/A.scala',
'paimon-flink/A.java',
+ 'docs/intro.md'})
+
+ def test_large_diffs_do_not_truncate_late_core_changes(self):
+ for index in range(350):
+ Path('doc{:03d}.md'.format(index)).write_text('docs')
+ self.commit('z-new-core/Change.java', 'core')
+ self.git('add', '.')
+ self.git('commit', '-qm', 'Many docs')
+ paths = plan.changed_paths('push', {'before': self.base,
+ 'after': self.git('rev-parse',
'HEAD')})
+ self.assertEqual(len(paths), 351)
+ self.assertTrue(plan.select(paths)['java'])
+
+ def test_new_branch_missing_commits_and_manual_run_fall_back(self):
+ self.assertIsNone(plan.changed_paths('push', {'before': '0' * 40,
'after': self.base}))
+ self.assertIsNone(plan.changed_paths('workflow_dispatch', {}))
+ with patch('subprocess.check_output',
side_effect=subprocess.CalledProcessError(128, 'git')):
+ self.assertIsNone(plan.changed_paths('push', {'before': 'a' * 40,
'after': self.base}))
+
+ def run_pr_plan(self, base, head):
+ Path('event.json').write_text(json.dumps({
+ 'pull_request': {'base': {'sha': base}, 'head': {'sha': head}}}))
+ env = {'GITHUB_EVENT_NAME': 'pull_request', 'GITHUB_EVENT_PATH':
'event.json',
+ 'GITHUB_OUTPUT': 'output.txt', 'GITHUB_STEP_SUMMARY':
'summary.md'}
+ with patch.dict(os.environ, env),
contextlib.redirect_stdout(io.StringIO()) as log:
+ plan.main()
+ outputs = dict(line.split('=', 1) for line in
Path('output.txt').read_text().splitlines())
+ return {key: json.loads(value) for key, value in outputs.items()},
log.getvalue()
+
+ def
test_pr_unavailable_diff_runs_full_ci_with_unchanged_oversized_file(self):
+ self.commit('docs/static/old image.png', 'x' * (1048576 + 1))
+ base = self.git('rev-parse', 'HEAD')
+ self.commit('paimon-spark/Change.scala', 'spark')
+ head = self.git('rev-parse', 'HEAD')
+ git_output = subprocess.check_output
+
+ def unavailable_diff(command, *args, **kwargs):
+ if command[:2] == ['git', 'diff']:
+ raise subprocess.CalledProcessError(128, command)
+ return git_output(command, *args, **kwargs)
+
+ with patch('subprocess.check_output', side_effect=unavailable_diff):
+ outputs, log = self.run_pr_plan(base, head)
+ self.assertTrue(all(outputs[key] for key in ('java', 'python', 'docs',
'licensing')))
+ self.assertEqual(len(outputs['matrix']['include']), 13)
+ summary = Path('summary.md').read_text()
+ self.assertIn('Full run (manual or unavailable diff).', summary)
+ self.assertIn('Skipping changed-file size check', log)
+ self.assertIn('Skipping changed-file size check', summary)
+
+ def
test_pr_size_guard_ignores_unchanged_but_rejects_changed_oversized_files(self):
+ self.commit('docs/static/old image.png', 'x' * (1048576 + 1))
+ base = self.git('rev-parse', 'HEAD')
+ self.commit('paimon-spark/Change.scala', 'spark')
+ outputs, _ = self.run_pr_plan(base, self.git('rev-parse', 'HEAD'))
+ self.assertEqual(len(outputs['matrix']['include']), 6)
+
+ for path in ('docs/static/old image.png', 'docs/static/new image.png'):
+ with self.subTest(path=path):
+ base = self.git('rev-parse', 'HEAD')
+ self.commit(path, 'y' * (1048576 + 1))
+ with self.assertRaisesRegex(ValueError, 'Changed file exceeds
1 MiB') as error:
+ self.run_pr_plan(base, self.git('rev-parse', 'HEAD'))
+ self.assertIn(path, str(error.exception))
+
+ def
test_github_outputs_are_valid_json_and_summary_explains_selection(self):
+ self.commit('paimon-spark/Change.scala', 'spark')
+ Path('event.json').write_text(json.dumps({'before': self.base,
+ 'after': self.git('rev-parse',
'HEAD')}))
+ env = {'GITHUB_EVENT_NAME': 'push', 'GITHUB_EVENT_PATH': 'event.json',
+ 'GITHUB_OUTPUT': 'output.txt', 'GITHUB_STEP_SUMMARY':
'summary.md'}
+ with patch.dict(os.environ, env), contextlib.redirect_stdout(None):
+ plan.main()
+ outputs = dict(line.split('=', 1) for line in
Path('output.txt').read_text().splitlines())
+ self.assertTrue(json.loads(outputs['java']))
+ self.assertFalse(json.loads(outputs['python']))
+ self.assertEqual(len(json.loads(outputs['matrix'])['include']), 6)
+ self.assertIn('Spark interoperability', Path('summary.md').read_text())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/ci/test_result.py b/tools/ci/test_result.py
new file mode 100644
index 0000000000..ff373e3216
--- /dev/null
+++ b/tools/ci/test_result.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# 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.
+
+"""The required CI check must not pass when a selected group did not pass."""
+
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+import textwrap
+import unittest
+
+
+class ResultTest(unittest.TestCase):
+ def run_gate(self, plan_result='success', **outcomes):
+ workflow = Path(__file__).resolve().parents[2] /
'.github/workflows/ci.yml'
+ source = workflow.read_text().split("python3 - <<'PYTHON'\n", 1)[1]
+ source = textwrap.dedent(source.split(' PYTHON', 1)[0])
+ flags = dict(java='true', python='false', docs='false',
licensing='true')
+ results = dict(plan=dict(result=plan_result, outputs=flags),
+ java=dict(result='success'),
python=dict(result='skipped'),
+ docs=dict(result='skipped'),
licensing=dict(result='success'))
+ for name, result in outcomes.items():
+ results[name]['result'] = result
+ return subprocess.run([sys.executable, '-c', source],
capture_output=True,
+ env=dict(os.environ,
RESULTS=json.dumps(results)))
+
+ def test_selected_groups_pass_and_unselected_groups_skip(self):
+ self.assertEqual(self.run_gate().returncode, 0)
+
+ def test_selected_failure_cancellation_and_unexpected_skip_fail(self):
+ for outcome in ('failure', 'cancelled', 'skipped'):
+ with self.subTest(outcome=outcome):
+ self.assertNotEqual(self.run_gate(java=outcome).returncode, 0)
+
+ def test_planner_failure_fails_even_if_other_groups_report_success(self):
+ for outcome in ('failure', 'cancelled', 'skipped'):
+ self.assertNotEqual(self.run_gate(plan_result=outcome).returncode,
0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/.github/workflows/utils.sh b/tools/ci/utils.sh
similarity index 100%
rename from .github/workflows/utils.sh
rename to tools/ci/utils.sh