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' ' '))
 

Reply via email to