This is an automated email from the ASF dual-hosted git repository.
wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new c2961aba [infra] Derive and validate the Flink versions tools/ut.sh
uses (#1007)
c2961aba is described below
commit c2961aba0ef63cc67adecfceb732599c900e955f
Author: Weiqing Yang <[email protected]>
AuthorDate: Mon Aug 17 02:40:55 2026 -0700
[infra] Derive and validate the Flink versions tools/ut.sh uses (#1007)
Generated-by: Claude Code 2.1.229 (Claude Opus 5)
---
.../pom.xml | 13 ++
tools/test/helpers/fake_root.bash | 33 ++++
tools/test/unit/ut_sh_flink_flag_scope.bats | 168 +++++++++++++++++++++
tools/test/unit/ut_sh_flink_version_supported.bats | 130 ++++++++++++++++
tools/test/unit/ut_sh_version_default.bats | 136 +++++++++++++++++
tools/ut.sh | 111 ++++++++++++--
6 files changed, 582 insertions(+), 9 deletions(-)
diff --git a/e2e-test/flink-agents-end-to-end-tests-integration/pom.xml
b/e2e-test/flink-agents-end-to-end-tests-integration/pom.xml
index 19aaf553..43d19328 100644
--- a/e2e-test/flink-agents-end-to-end-tests-integration/pom.xml
+++ b/e2e-test/flink-agents-end-to-end-tests-integration/pom.xml
@@ -186,6 +186,19 @@ under the License.
<flink.log4j2.version>2.24.3</flink.log4j2.version>
</properties>
</profile>
+
+ <!-- Flink 2.3 Profile -->
+ <!-- No flink.log4j2.version override here: the profile-less build of
+ this module is itself Flink 2.3, so the parent's value is the one
+ this line needs. The older profiles override it only because their
+ Flink lines require an older log4j2. -->
+ <profile>
+ <id>flink-2.3</id>
+ <properties>
+ <flink.version>${flink.2.3.version}</flink.version>
+
<flink.agents.dist.artifactId>flink-agents-dist-flink-2.3</flink.agents.dist.artifactId>
+ </properties>
+ </profile>
</profiles>
</project>
\ No newline at end of file
diff --git a/tools/test/helpers/fake_root.bash
b/tools/test/helpers/fake_root.bash
new file mode 100644
index 00000000..733ee16f
--- /dev/null
+++ b/tools/test/helpers/fake_root.bash
@@ -0,0 +1,33 @@
+# Fake-repo helper for the tools/ut.sh tests. Loaded via `load
'helpers/fake_root'`.
+#
+# ut.sh derives both its default Flink version and the set it accepts from
+# files in the tree above it, so proving either is derived rather than pinned
+# needs a tree that disagrees with the real repo. Requires $UT_SH to point at
+# the script under test.
+
+# Builds a throwaway tools/.. tree and echoes its root. $1 is the literal
+# <properties> body of the root pom, so a caller can pin a well-formed version,
+# a malformed one, or none at all. Every remaining argument becomes a
+# dist/flink-<version> directory, which is what ut.sh reads its supported set
+# from; passing none leaves a tree that carries no dist module at all.
+make_fake_root() {
+ local properties="$1"
+ shift
+ local fake="$BATS_TEST_TMPDIR/fake" version
+ mkdir -p "$fake/tools" "$fake/python"
+ cp "$UT_SH" "$fake/tools/ut.sh"
+ printf '<project>\n <properties>\n%s\n </properties>\n</project>\n' \
+ "$properties" >"$fake/pom.xml"
+ for version in "$@"; do
+ mkdir -p "$fake/dist/flink-${version}"
+ done
+ echo "$fake"
+}
+
+# Shorthand for the common case: a well-formed <flink.version> plus the dist
+# modules the tree should carry.
+make_fake_root_pinning() {
+ local pom_version="$1"
+ shift
+ make_fake_root " <flink.version>${pom_version}</flink.version>" "$@"
+}
diff --git a/tools/test/unit/ut_sh_flink_flag_scope.bats
b/tools/test/unit/ut_sh_flink_flag_scope.bats
new file mode 100644
index 00000000..fa437c2c
--- /dev/null
+++ b/tools/test/unit/ut_sh_flink_flag_scope.bats
@@ -0,0 +1,168 @@
+#!/usr/bin/env bats
+
+setup() {
+ load '../helpers/shim'
+ load '../helpers/fake_root'
+ shim_setup
+ UT_SH="${BATS_TEST_DIRNAME}/../../ut.sh"
+ REPO_POM="${BATS_TEST_DIRNAME}/../../../pom.xml"
+ # Every suite below is driven entirely through these two shims, so no
+ # Maven reactor or Python environment is touched.
+ shim_bin mvn
+ shim_bin uv
+}
+
+# Two-component Flink version the root pom pins, e.g. "2.3" for
<flink.version>2.3.0.
+repo_flink_minor() {
+ local v
+ v="$(sed -n 's/.*<flink.version>\(.*\)<\/flink.version>.*/\1/p'
"$REPO_POM" | head -1)"
+ echo "${v%.*}"
+}
+
+# The rejection's own text. Every substantive claim is pinned separately --
that
+# -e is what makes -f usable, which suite it selects for, that the unit tests
+# cannot be pointed elsewhere at all, and what each half of them builds against
+# instead -- so re-wording any one of them into something false fails here
+# rather than passing silently. Only those words are matched, on
+# whitespace-collapsed output, so re-flowing or re-punctuating the message is
+# not a failure.
+ERROR_REQUIRES_E2E="Error: -f requires -e"
+ERROR_E2E_SCOPE="it selects the Flink version the e2e tests run against"
+ERROR_NO_RETARGET="The unit tests cannot be retargeted"
+ERROR_UNIT_JAVA="each Java module builds against the version its own pom
resolves"
+
+flowed_output() {
+ printf '%s' "$output" | tr -s '[:space:]' ' '
+}
+
+assert_rejected() {
+ # The Python half names the requirement actually installed, so it is built
+ # from the pom here rather than pinned as a literal.
+ local python_clause="the Python unit tests install
apache-flink~=$(repo_flink_minor).0"
+ case "$(flowed_output)" in
+
*"$ERROR_REQUIRES_E2E"*"$ERROR_E2E_SCOPE"*"$ERROR_NO_RETARGET"*"$ERROR_UNIT_JAVA"*"$python_clause"*)
;;
+ *) false ;;
+ esac
+}
+
+assert_not_rejected() {
+ case "$(flowed_output)" in *"$ERROR_REQUIRES_E2E"*) false ;; *) ;; esac
+}
+
+# Fails if $output demonstrates an invocation the script would itself exit 1
on:
+# a -f or --flink form carrying no -e. Commas become newlines first, because
one
+# help line lists several forms and only one of them may be at fault; trailing
+# `#` comments are dropped, so prose that happens to mention -e cannot vouch
for
+# the invocation beside it; and each form is whitespace-collapsed so -e is
+# matched as its own argument rather than as a substring. The option's own
+# `-f, --flink` line does not match: the pattern needs a version token after
+# the flag.
+assert_no_rejectable_f_example() {
+ local form matched=0
+ while IFS= read -r form; do
+ [ -n "$form" ] || continue
+ matched=$((matched + 1))
+ form=" $(printf '%s' "$form" | tr -s '[:space:]' ' ') "
+ case "$form" in *" -e "*|*" --e2e "*) ;; *) false ;; esac
+ done <<EOF
+$(printf '%s\n' "$output" | tr ',' '\n' | sed 's/#.*//' \
+ | grep -E '(^|[[:space:]]|[(])(-f|--flink)[[:space:]]+[0-9]')
+EOF
+ # Without this the scan passes on output carrying no examples at all.
+ [ "$matched" -ge 1 ]
+}
+
+@test "-f with the Java unit tests is rejected, on stderr" {
+ run bash "$UT_SH" -j -f 1.20
+ [ "$status" -eq 1 ]
+ assert_rejected
+ # Dropping stderr must drop the message with it: an error on stdout would
+ # land in the middle of test output that gets parsed or piped.
+ run bash -c "bash '$UT_SH' -j -f 1.20 2>/dev/null"
+ [ "$status" -eq 1 ]
+ assert_not_rejected
+}
+
+@test "-f with only the Python tests is rejected too" {
+ # The Python tests do install the version they are given, so this is the
+ # combination a scope check written around the Java suite alone would let
+ # through -- and outside -e it is just as inapplicable.
+ run bash "$UT_SH" -p -f 1.20
+ [ "$status" -eq 1 ]
+ assert_rejected
+}
+
+@test "the message names the requirement actually installed, derived from the
pom" {
+ # A hardcoded version matches the real repo today and rots at the next
+ # bump, so drive the check against a tree pinning a different one. The
+ # x.y.0 form is the point: ~= constrains only the x.y line, so the pom's
+ # three-component value describes a precision the install does not have.
+ local fake
+ fake="$(make_fake_root_pinning 9.9.9 9.9)"
+ run bash "$fake/tools/ut.sh" -p -f 9.9
+ [ "$status" -eq 1 ]
+ case "$(flowed_output)" in *"apache-flink~=9.9.0"*) ;; *) false ;; esac
+ case "$(flowed_output)" in *"9.9.9"*) false ;; *) ;; esac
+}
+
+@test "-f is accepted with the e2e tests, and still selects the version they
use" {
+ run bash "$UT_SH" -j -e -f 1.20
+ [ "$status" -eq 0 ]
+ assert_not_rejected
+ # Exit 0 alone would still hold if -f had become a no-op, so pin the two
+ # places the version reaches Maven: the dist module installed, and the
+ # profile the e2e run activates.
+ case "$(shim_calls mvn)" in *"dist/flink-1.20"*) ;; *) false ;; esac
+ case "$(shim_calls mvn)" in *"-Pflink-1.20"*) ;; *) false ;; esac
+ run bash "$UT_SH" -p -e -f 1.20
+ [ "$status" -eq 0 ]
+ assert_not_rejected
+ case "$(shim_calls uv)" in *"apache-flink~=1.20.0"*) ;; *) false ;; esac
+}
+
+@test "-e is honored after -f, not only before it" {
+ # The guard reads the parse loop's final state rather than the order the
+ # flags arrive in; folding it into the -f case branch would break this.
+ run bash "$UT_SH" -p -f 1.20 -e
+ [ "$status" -eq 0 ]
+ assert_not_rejected
+ case "$(shim_calls uv)" in *"apache-flink~=1.20.0"*) ;; *) false ;; esac
+}
+
+@test "a run that passes no -f is not rejected over the defaulted version" {
+ run bash "$UT_SH" -j
+ [ "$status" -eq 0 ]
+ assert_not_rejected
+}
+
+@test "an unsupported version outside -e is reported as a scope error, not an
unsupported one" {
+ run bash "$UT_SH" -p -f 9.9
+ [ "$status" -eq 1 ]
+ assert_rejected
+ # No value of -f applies here, so naming 9.9 as the problem would send the
+ # caller hunting for a supported version instead of adding -e.
+ case "$output" in *"unsupported Flink version"*) false ;; *) ;; esac
+}
+
+@test "the rejection lands before any Maven or Python work starts" {
+ # The point of rejecting at all is that it costs the caller no build, so
+ # neither shim may have been reached by the time the script exits. Driven
+ # with no suite flag, which selects both, so a rejection reached from only
+ # one of the two suite paths cannot pass this.
+ run bash "$UT_SH" -f 1.20
+ [ "$status" -eq 1 ]
+ assert_rejected
+ [ "$(shim_call_count mvn)" -eq 0 ]
+ [ "$(shim_call_count uv)" -eq 0 ]
+}
+
+@test "the script demonstrates no -f form it would itself reject" {
+ # Both places -f usage is advertised: the help text, and the error shown
+ # when -f is given no version, which prints the help after it.
+ run bash "$UT_SH" --help
+ [ "$status" -eq 0 ]
+ assert_no_rejectable_f_example
+ run bash -c "bash '$UT_SH' -f 2>&1"
+ [ "$status" -eq 1 ]
+ assert_no_rejectable_f_example
+}
diff --git a/tools/test/unit/ut_sh_flink_version_supported.bats
b/tools/test/unit/ut_sh_flink_version_supported.bats
new file mode 100644
index 00000000..eba044f3
--- /dev/null
+++ b/tools/test/unit/ut_sh_flink_version_supported.bats
@@ -0,0 +1,130 @@
+#!/usr/bin/env bats
+
+setup() {
+ load '../helpers/shim'
+ load '../helpers/fake_root'
+ shim_setup
+ UT_SH="${BATS_TEST_DIRNAME}/../../ut.sh"
+ ROOT="${BATS_TEST_DIRNAME}/../../.."
+
E2E_POM="${ROOT}/e2e-test/flink-agents-end-to-end-tests-integration/pom.xml"
+ DIST_POM="${ROOT}/dist/pom.xml"
+}
+
+# The versions the repo actually carries a dist module for, sorted, one per
line.
+dist_versions() {
+ local d
+ for d in "${ROOT}"/dist/flink-*/; do
+ basename "$d" | sed 's/^flink-//'
+ done | sort
+}
+
+# The flink-* profile ids the e2e integration pom declares, sorted, one per
line.
+# The java-21 profile is excluded by the flink- prefix.
+e2e_profile_versions() {
+ sed -n 's/.*<id>flink-\(.*\)<\/id>.*/\1/p' "$E2E_POM" | sort
+}
+
+# The flink-* modules dist/pom.xml builds, sorted, one per line. The common
+# module is excluded by the flink- prefix.
+dist_module_versions() {
+ sed -n 's/.*<module>flink-\(.*\)<\/module>.*/\1/p' "$DIST_POM" | sort
+}
+
+@test "a Flink version with no dist module is rejected before any test runs,
on stderr" {
+ shim_bin mvn
+ shim_bin uv
+ run bash "$UT_SH" -e -f 9.9
+ [ "$status" -eq 1 ]
+ case "$output" in *"Error: unsupported Flink version '9.9'"*) ;; *) false
;; esac
+ # The message has to carry the way out of the mistake, not just report it.
+ case "$output" in *"$(dist_versions | tr '\n' ' ' | sed 's/ $//')"*) ;; *)
false ;; esac
+ # Nothing may run: the point of validating before the suites start is that
+ # a typo costs no build.
+ [ "$(shim_call_count mvn)" -eq 0 ]
+ [ "$(shim_call_count uv)" -eq 0 ]
+ run bash -c "bash '$UT_SH' -e -f 9.9 2>/dev/null"
+ case "$output" in *"unsupported Flink version"*) false ;; *) ;; esac
+}
+
+@test "every version the repo carries a dist module for is accepted" {
+ shim_bin uv
+ local version
+ while read -r version; do
+ run bash "$UT_SH" -p -e -f "$version"
+ [ "$status" -eq 0 ]
+ done < <(dist_versions)
+}
+
+@test "--help lists the dist modules that exist rather than a literal" {
+ local fake
+ fake="$(make_fake_root_pinning 3.0.0 3.0 4.1)"
+ run bash "$fake/tools/ut.sh" --help
+ [ "$status" -eq 0 ]
+ case "$output" in *"Supported versions: 3.0 4.1"*) ;; *) false ;; esac
+}
+
+@test "the versions -f accepts follow the dist modules that exist rather than
a fixed list" {
+ # --help only proves the text the user is shown follows dist/; a validation
+ # list pinned to today's repo would still pass that. Drive the check itself
+ # against a tree whose dist modules disagree with the real one: the version
+ # that exists only there has to be accepted, and the one that exists only
+ # in the real repo has to be rejected.
+ shim_bin uv
+ local fake
+ fake="$(make_fake_root_pinning 3.0.0 3.0 4.1)"
+ run bash "$fake/tools/ut.sh" -p -e -f 4.1
+ [ "$status" -eq 0 ]
+ run bash "$fake/tools/ut.sh" -p -e -f 1.20
+ [ "$status" -eq 1 ]
+ case "$output" in *"Error: unsupported Flink version '1.20'"*) ;; *) false
;; esac
+ case "$output" in *"supported versions: 3.0 4.1"*) ;; *) false ;; esac
+}
+
+@test "a defaulted version with no dist module is rejected, naming the pom
rather than the caller" {
+ # The default and the accepted set are read from different files, so a pom
+ # bumped ahead of dist/ makes a bare run name a Maven module that does not
+ # exist. Nothing the caller typed is wrong here, so the message must not
+ # report an unsupported choice or offer -f as the way out.
+ shim_bin mvn
+ shim_bin uv
+ local fake
+ fake="$(make_fake_root_pinning 9.9.9 3.0 4.1)"
+ run bash "$fake/tools/ut.sh"
+ [ "$status" -eq 1 ]
+ case "$output" in *"the root pom pins <flink.version> 9.9.9"*) ;; *) false
;; esac
+ case "$output" in *"carries no flink-9.9 module"*) ;; *) false ;; esac
+ case "$output" in *"unsupported Flink version"*) false ;; *) ;; esac
+ [ "$(shim_call_count mvn)" -eq 0 ]
+ [ "$(shim_call_count uv)" -eq 0 ]
+}
+
+@test "a tree carrying no dist module names that as the fault rather than the
version" {
+ # With no dist/flink-* at all the supported set is empty, so the
per-version
+ # messages would report the version under test as the problem and trail off
+ # into an empty list of alternatives. Neither describes what is wrong.
+ shim_bin mvn
+ shim_bin uv
+ local fake
+ fake="$(make_fake_root_pinning 2.3.0)"
+ run bash "$fake/tools/ut.sh"
+ [ "$status" -eq 1 ]
+ case "$output" in *"Error: found no dist/flink-* modules under"*) ;; *)
false ;; esac
+ case "$output" in *"carries no flink-2.3 module"*) false ;; *) ;; esac
+ case "$output" in *"unsupported Flink version"*) false ;; *) ;; esac
+ # The shape the two per-version messages degrade to when the set is empty:
+ # a colon introducing a list, closing the message with nothing after it.
+ case "$output" in *":" | *": ") false ;; *) ;; esac
+ [ "$(shim_call_count mvn)" -eq 0 ]
+ [ "$(shim_call_count uv)" -eq 0 ]
+}
+
+@test "every dist module directory is built and has a matching e2e Flink
profile" {
+ # -f <version> resolves to dist/flink-<version>, to that module's entry in
+ # dist/pom.xml, and to -Pflink-<version>. A version present on one side
+ # only is silently wrong: a directory dist/pom.xml does not list is never
+ # built, a missing dist module fails the reactor, and a missing profile
+ # leaves Maven to warn about an unactivatable profile and fall through to
+ # the pom's own default.
+ [ "$(dist_versions)" = "$(dist_module_versions)" ]
+ [ "$(dist_versions)" = "$(e2e_profile_versions)" ]
+}
diff --git a/tools/test/unit/ut_sh_version_default.bats
b/tools/test/unit/ut_sh_version_default.bats
new file mode 100644
index 00000000..508549d5
--- /dev/null
+++ b/tools/test/unit/ut_sh_version_default.bats
@@ -0,0 +1,136 @@
+#!/usr/bin/env bats
+
+setup() {
+ load '../helpers/shim'
+ load '../helpers/fake_root'
+ shim_setup
+ UT_SH="${BATS_TEST_DIRNAME}/../../ut.sh"
+ REPO_POM="${BATS_TEST_DIRNAME}/../../../pom.xml"
+}
+
+# Two-component Flink version the root pom pins, e.g. "2.3" for
<flink.version>2.3.0.
+repo_flink_minor() {
+ local v
+ v="$(sed -n 's/.*<flink.version>\(.*\)<\/flink.version>.*/\1/p'
"$REPO_POM" | head -1)"
+ echo "${v%.*}"
+}
+
+@test "--help does not claim every Flink version is tested by default" {
+ run bash "$UT_SH" --help
+ [ "$status" -eq 0 ]
+ case "$output" in *"all versions"*) false ;; *) ;; esac
+ case "$output" in *"all Flink versions"*) false ;; *) ;; esac
+}
+
+@test "--help states the default Flink version and which suites -f applies to"
{
+ run bash "$UT_SH" --help
+ [ "$status" -eq 0 ]
+ case "$output" in *"Default: $(repo_flink_minor)"*) ;; *) false ;; esac
+ # The scope sentence wraps across help lines; compare on collapsed
+ # whitespace so re-indenting or re-wrapping the block cannot break this.
+ local flowed
+ flowed="$(printf '%s' "$output" | tr -s '[:space:]' ' ')"
+ case "$flowed" in
+ *"Requires -e: it selects the Flink version the e2e tests run
against"*) ;;
+ *) false ;;
+ esac
+ case "$flowed" in *"The unit tests cannot be retargeted"*) ;; *) false ;;
esac
+ # Which version they use instead is the half most easily re-worded into
+ # something false, so both suites are pinned: each Java module resolves
+ # flink.version through its own pom, which most modules inherit from the
+ # root and most dist ones override, and the Python suite installs the
+ # requirement built from the default.
+ case "$flowed" in
+ *"each Java module builds against the version its own pom resolves"*)
;;
+ *) false ;;
+ esac
+ case "$flowed" in
+ *"Python unit tests install apache-flink~=$(repo_flink_minor).0"*) ;;
+ *) false ;;
+ esac
+}
+
+@test "--help builds the Python requirement from the pom rather than a
literal" {
+ # The assertion above reads the same pom the help renders from, so a
+ # hardcoded literal satisfies it for as long as the pom agrees. Only a tree
+ # pinning a different version separates interpolation from a literal.
+ local fake flowed
+ fake="$(make_fake_root_pinning 9.9.9 9.9)"
+ run bash "$fake/tools/ut.sh" --help
+ [ "$status" -eq 0 ]
+ flowed="$(printf '%s' "$output" | tr -s '[:space:]' ' ')"
+ case "$flowed" in
+ *"Python unit tests install apache-flink~=9.9.0"*) ;;
+ *) false ;;
+ esac
+}
+
+@test "a bare Python run installs the Flink version the root pom pins" {
+ shim_bin uv
+ run bash "$UT_SH" -p
+ [ "$status" -eq 0 ]
+ case "$(shim_calls uv)" in
+ *"apache-flink~=$(repo_flink_minor).0"*) ;;
+ *) false ;;
+ esac
+}
+
+@test "the default Flink version follows the pom rather than a literal" {
+ shim_bin uv
+ local fake
+ # The fake tree carries the dist module its pom pins, so the defaulted
+ # version is a supported one and only the value being read is under test.
+ fake="$(make_fake_root ' <flink.version>9.9.9</flink.version>' 9.9)"
+ run bash "$fake/tools/ut.sh" -p
+ [ "$status" -eq 0 ]
+ case "$(shim_calls uv)" in *"apache-flink~=9.9.0"*) ;; *) false ;; esac
+}
+
+@test "a whitespace-padded flink.version is read as the version it pads" {
+ shim_bin uv
+ local fake
+ fake="$(make_fake_root ' <flink.version> 9.9.9 </flink.version>' 9.9)"
+ run bash "$fake/tools/ut.sh" -p
+ [ "$status" -eq 0 ]
+ case "$(shim_calls uv)" in *"apache-flink~=9.9.0"*) ;; *) false ;; esac
+}
+
+@test "a pom carrying no flink.version at all is fatal" {
+ local fake
+ fake="$(make_fake_root ' <other.version>1.0.0</other.version>')"
+ run bash "$fake/tools/ut.sh"
+ [ "$status" -eq 1 ]
+ # Nothing was read, so the error must not quote a value as though one was.
+ case "$output" in
+ *"Error: found no usable <flink.version> value in"*) ;;
+ *) false ;;
+ esac
+ case "$output" in *"read '"*) false ;; *) ;; esac
+}
+
+@test "a two-component flink.version is fatal rather than a one-component
token" {
+ local fake
+ fake="$(make_fake_root ' <flink.version>2.3</flink.version>')"
+ run bash "$fake/tools/ut.sh"
+ [ "$status" -eq 1 ]
+ # The value was read; the error has to say so rather than claim it could
not be.
+ case "$output" in
+ *"Error: read '2.3' as <flink.version>"*) ;;
+ *) false ;;
+ esac
+ case "$output" in *"expected an x.y.z version"*) ;; *) false ;; esac
+}
+
+@test "a flink.version holding a property reference is fatal rather than a
broken token" {
+ local fake
+ fake="$(make_fake_root '
<flink.version>${flink.2.3.version}</flink.version>')"
+ run bash "$fake/tools/ut.sh"
+ [ "$status" -eq 1 ]
+ # A value is present, so the error has to quote it rather than report an
+ # absent element the way the missing-property case does.
+ case "$output" in
+ *"Error: read '\${flink.2.3.version}' as <flink.version>"*) ;;
+ *) false ;;
+ esac
+ case "$output" in *"expected an x.y.z version"*) ;; *) false ;; esac
+}
diff --git a/tools/ut.sh b/tools/ut.sh
index d413507b..8b27e0cf 100755
--- a/tools/ut.sh
+++ b/tools/ut.sh
@@ -20,8 +20,53 @@ set -e
ROOT="$(cd "$( dirname "$0" )" && pwd)/.."
-# Default Flink version
-DEFAULT_FLINK_VERSION="2.2"
+# Read the default Flink version from the root pom rather than pinning it here,
+# so bumping the project's Flink version cannot leave this script testing an
+# older line. The pom carries x.y.z; the version tokens used below are x.y.
+# Anything that does not end up shaped x.y is fatal, because the value flows
+# unvalidated into dist module paths, -P profile names and the pip requirement.
+# Whitespace is stripped because XML permits padding inside the element and a
+# version token carries none of its own.
+POM_FLINK_VERSION="$(sed -n
's/.*<flink\.version>\([^<]*\)<\/flink\.version>.*/\1/p' "${ROOT}/pom.xml"
2>/dev/null | head -1 | tr -d '[:space:]')"
+DEFAULT_FLINK_VERSION="${POM_FLINK_VERSION%.*}"
+if [[ -z "${POM_FLINK_VERSION}" ]]; then
+ echo "Error: found no usable <flink.version> value in ${ROOT}/pom.xml; the
file must exist and pin an x.y.z version" >&2
+ exit 1
+fi
+if [[ ! "${DEFAULT_FLINK_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
+ echo "Error: read '${POM_FLINK_VERSION}' as <flink.version> from
${ROOT}/pom.xml; expected an x.y.z version, not a property reference or a
shorter version" >&2
+ exit 1
+fi
+
+# The versions -f accepts are derived from the dist modules rather than listed
+# here, because -f resolves to dist/flink-<version>: a value with no directory
+# names a Maven module that does not exist. Glob expansion is already sorted,
+# so the list is deterministic; the ordering is lexicographic rather than by
+# version precedence, so a two-digit minor sorts ahead of a one-digit one.
+SUPPORTED_FLINK_VERSIONS=()
+for dist_dir in "${ROOT}"/dist/flink-*/; do
+ [[ -d "${dist_dir}" ]] || continue
+ dist_dir="${dist_dir%/}"
+ SUPPORTED_FLINK_VERSIONS+=("${dist_dir##*/flink-}")
+done
+# Checked separately from the per-version validation below: with nothing in the
+# set, every version fails that check, and its messages would blame the version
+# under test for what is really a tree carrying no dist modules at all.
+if [[ ${#SUPPORTED_FLINK_VERSIONS[@]} -eq 0 ]]; then
+ echo "Error: found no dist/flink-* modules under ${ROOT}; the set of
supported Flink versions is read from them and cannot be determined" >&2
+ exit 1
+fi
+
+# bash 3.2 has no associative arrays, so membership is a scan.
+is_supported_flink_version() {
+ local candidate="$1" supported
+ for supported in "${SUPPORTED_FLINK_VERSIONS[@]}"; do
+ if [[ "${candidate}" == "${supported}" ]]; then
+ return 0
+ fi
+ done
+ return 1
+}
# Default values
run_java=true
@@ -29,6 +74,10 @@ run_python=true
run_e2e=false
verbose=false
flink_versions=()
+# Whether -f was passed rather than defaulted. Not recoverable from
+# flink_versions afterwards: the default fill makes a defaulted array
+# indistinguishable from an explicit request for the same version.
+flink_explicit=false
# Help information
show_help() {
@@ -43,17 +92,21 @@ Options:
-e, --e2e Run e2e tests
-b, --both Run both Java and Python tests (default)
-f, --flink Specify Flink version to test (can be used multiple times)
- Supported versions: 2.3, 2.2, 2.1, 2.0, 1.20
- Examples: -f 2.3, -f 1.20, -f 2.3 -f 1.20
- Default: run all versions if not specified
+ Supported versions: ${SUPPORTED_FLINK_VERSIONS[*]}
+ Examples: -e -f 2.3, -e -f 1.20, -e -f 2.3 -f 1.20
+ Default: ${DEFAULT_FLINK_VERSION}, from flink.version in
the root pom.xml
+ Requires -e: it selects the Flink version the e2e tests run
+ against. The unit tests cannot be retargeted; each Java
+ module builds against the version its own pom resolves, and
+ the Python unit tests install
apache-flink~=${DEFAULT_FLINK_VERSION}.0.
-v, --verbose Show verbose output
-h, --help Display this help message
Examples:
- $0 --java # Run only Java tests (all Flink versions)
+ $0 --java # Run only Java tests
$0 -p # Run only Python tests
- $0 -f 2.2 # Run tests only for Flink 2.2
- $0 -f 1.20 # Run tests only for Flink 1.20
+ $0 -e -f 2.2 # Run the e2e tests against Flink 2.2
+ $0 -e -f 1.20 # Run the e2e tests against Flink 1.20
$0 -v # Run all tests with verbose output
Exit codes:
@@ -84,11 +137,12 @@ while [[ "$#" -gt 0 ]]; do
;;
-f|--flink)
if [[ -z "$2" || "$2" == -* ]]; then
- echo "Error: -f requires a version argument (e.g., -f 1.20)"
>&2
+ echo "Error: -f requires a version argument (e.g., -e -f
1.20)" >&2
show_help
exit 1
fi
flink_versions+=("$2")
+ flink_explicit=true
shift
;;
-v|--verbose)
@@ -107,11 +161,50 @@ while [[ "$#" -gt 0 ]]; do
shift
done
+# -f selects the Flink version the e2e tests run against, and nothing else
+# reads it. Each Java module compiles and tests against the <flink.version> its
+# own pom resolves: most inherit the root's, while most dist/flink-<v> modules
+# override it with the matching flink.<v>.version, and those modules stay in
+# the unit-test reactor because only the e2e modules are excluded from it. The
+# Python unit tests install the requirement built from the default version. So
+# outside -e the flag has nothing left to select.
+# Checked before the version is validated below: when the flag does not apply
+# at all, the value it carries is beside the point, and reporting that value as
+# unsupported would send the caller looking for a different one.
+if $flink_explicit && ! $run_e2e; then
+ cat >&2 <<EOF
+Error: -f requires -e; it selects the Flink version the e2e tests run against.
+ The unit tests cannot be retargeted: each Java module builds against the
+ version its own pom resolves, and the Python unit tests install
+ apache-flink~=${DEFAULT_FLINK_VERSION}.0.
+EOF
+ exit 1
+fi
+
# If no version is specified, the default version will be run by default.
if [ ${#flink_versions[@]} -eq 0 ]; then
flink_versions=("${DEFAULT_FLINK_VERSION}")
fi
+# Validated here rather than as each -f is parsed so that the defaulted value
+# is checked too. The default and the accepted set are derived from different
+# files -- the root pom's <flink.version> and the dist/ modules -- and nothing
+# else makes them agree, so a pom bumped ahead of its dist modules would
+# otherwise let a bare run reach a Maven module that does not exist.
+# Which of the two is at fault decides the message: a value the caller typed
+# is theirs to correct, while a defaulted one means the repo disagrees with
+# itself and no choice of -f is the fix.
+for version in "${flink_versions[@]}"; do
+ if ! is_supported_flink_version "${version}"; then
+ if $flink_explicit; then
+ echo "Error: unsupported Flink version '${version}'; supported
versions: ${SUPPORTED_FLINK_VERSIONS[*]}" >&2
+ else
+ echo "Error: the root pom pins <flink.version>
${POM_FLINK_VERSION}, but ${ROOT}/dist carries no flink-${version} module; dist
modules exist for: ${SUPPORTED_FLINK_VERSIONS[*]}" >&2
+ fi
+ exit 1
+ fi
+done
+
# Remove duplicates and sort version numbers
flink_versions=($(echo "${flink_versions[@]}" | tr ' ' '\n' | sort -u | tr
'\n' ' '))