Skip to content

Commit f237e41

Browse files
authored
ci: avoid running split ITs unexpectedly on unrelated changes (#12029) (#14217)
Fixes #12029 (b/487770623) This PR is part 1 of 2 in a stacked series: 1. This PR (#14217): `fix: avoid running split ITs unexpectedly on unrelated changes (#12029)` 2. #14218: `perf(ci): replace mvn help:evaluate with native bash and sed extraction` --- ## Problem In split integration and GraalVM tests (e.g. `integration-single` and `graalvm-single`), module modification detection previously checked if the module name was present as a substring in `modified_module_list`. For modules with shared prefixes (such as `java-bigquery` vs `java-bigquerystorage`, `java-bigquery-jdbc`, or `java-bigqueryconnection`), modifying one module could trigger tests for other prefix-sharing modules. ## Changes - **Module Boundary Anchoring (`is_module_modified`)**: Added `is_module_modified` in `.kokoro/common.sh` using exact directory prefix matching (`^${module}/`). - **Global Overrides**: `is_module_modified` respects parent POM modifications (`google-cloud-(pom|jar)-parent/pom.xml`), shared dependency modifications (`sdk-platform-java/java-shared-dependencies`), and `TEST_ALL_MODULES="true"` so that dependency and parent updates properly verify downstream integration suites. - **Short-Circuit in Split Jobs**: Updated `integration-single` and `graalvm-single` in `.kokoro/build.sh` to check `is_module_modified "${BUILD_SUBDIR}"` directly. - **Updated Shared Dependencies Path**: Updated `shared_dependencies_modified` in `.kokoro/common.sh` to match `sdk-platform-java/java-shared-dependencies`. - **Unit Tests**: Added unit test coverage in `.kokoro/common_test.sh` for `is_module_modified` covering empty inputs, prefix collision prevention, parent pom modification, shared dependencies modification, and `TEST_ALL_MODULES`.
1 parent 5b6a974 commit f237e41

3 files changed

Lines changed: 194 additions & 22 deletions

File tree

.kokoro/build.sh

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ case ${JOB_TYPE} in
119119
fi
120120
;;
121121
integration-single)
122-
generate_modified_modules_list false
123122
if [[ "$(release_please_snapshot_pull_request)" == "true" ]]; then
124123
echo "Not running integration checks -- this is Release Please SNAPSHOT pull request."
125-
elif [[ ! " ${modified_module_list[*]} " =~ " ${BUILD_SUBDIR} " ]]; then
124+
# Run tests if either global overrides require testing all modules (e.g. parent POM or
125+
# shared dependencies) OR if this specific module was modified. Otherwise skip.
126+
elif ! should_test_all_modules && ! is_module_modified "${BUILD_SUBDIR}"; then
126127
echo "${BUILD_SUBDIR} not modified, skipping split integration test"
127128
else
128129
echo "${BUILD_SUBDIR} modified, running split integration test"
@@ -188,10 +189,11 @@ case ${JOB_TYPE} in
188189
fi
189190
;;
190191
graalvm-single)
191-
generate_modified_modules_list false
192192
if [[ "$(release_please_snapshot_pull_request)" == "true" ]]; then
193193
echo "Not running GraalVM checks -- this is Release Please SNAPSHOT pull request."
194-
elif [[ ! " ${modified_module_list[*]} " =~ " ${BUILD_SUBDIR} " ]]; then
194+
# Run tests if either global overrides require testing all modules (e.g. parent POM or
195+
# shared dependencies) OR if this specific module was modified. Otherwise skip.
196+
elif ! should_test_all_modules && ! is_module_modified "${BUILD_SUBDIR}"; then
195197
echo "${BUILD_SUBDIR} not modified, skipping split GraalVM test"
196198
else
197199
echo "${BUILD_SUBDIR} modified, running split GraalVM test"

.kokoro/common.sh

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -231,21 +231,56 @@ function release_please_snapshot_pull_request() {
231231
fi
232232
}
233233

234-
# Sets bash variables for maven_modules and modified_module_list
235-
# maven_modules is the list of all maven submodules of the root pom
236-
# modified_module_list is the subset of maven_modules that have been touched
237-
# in the current pull request
234+
# Returns the list of modified files in the current PR diff via git diff.
235+
function get_modified_files() {
236+
# In Kokoro Docker containers, the build runs as root (UID 0) while repository files
237+
# belong to the host user (UID 1000). Git 2.35.2+ flags this UID mismatch as 'dubious ownership'
238+
# and aborts git commands; safe.directory allows Git to operate in this directory.
239+
git config --global --add safe.directory "$(realpath .)" 2>/dev/null || true
240+
241+
# '${VAR:-DEFAULT}' uses $VAR if set and non-empty, otherwise falls back to DEFAULT.
242+
# This allows developers to run these scripts locally outside the Kokoro CI environment.
243+
local target_branch="${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH:-origin/main}"
244+
local target_commit="${KOKORO_GITHUB_PULL_REQUEST_COMMIT:-HEAD}"
245+
246+
# 'git diff A...B' (triple-dot) diffs between the merge-base (common ancestor) of
247+
# target_branch and target_commit, listing only files changed in this branch.
248+
git diff --name-only "${target_branch}...${target_commit}"
249+
}
250+
251+
# Determines if the entire monorepo must be tested.
252+
#
253+
# Monorepo-wide testing is triggered under three conditions:
254+
# 1. TEST_ALL_MODULES is set to "true" (used by nightly and scheduled CI builds).
255+
# 2. Root parent POMs (google-cloud-jar-parent or google-cloud-pom-parent) are modified,
256+
# as changes to parent POMs affect shared dependency versions and compiler/build plugins.
257+
# 3. Core shared dependencies (sdk-platform-java/java-shared-dependencies) are modified,
258+
# as gax, auth, and transport changes can break downstream client library integration tests.
259+
function should_test_all_modules() {
260+
local files
261+
files=$(get_modified_files)
262+
263+
# '<<< STRING' is a Bash "here-string" that feeds the string variable directly to
264+
# stdin of grep, avoiding an external subshell pipeline (like 'echo "$var" | grep').
265+
if [[ "${TEST_ALL_MODULES}" == "true" ]] || \
266+
grep -q -E '^google-cloud-(pom|jar)-parent/pom.xml$' <<< "${files}" || \
267+
grep -q -E '^sdk-platform-java/java-shared-dependencies/' <<< "${files}"; then
268+
return 0
269+
fi
270+
return 1
271+
}
272+
273+
# Generates the list of modified Maven modules for batch integration/GraalVM test jobs.
274+
# Sets global variables:
275+
# - maven_modules: list of all Maven modules defined in the root POM.
276+
# - modified_module_list: modules that need to be tested for the current PR.
238277
#
239-
# The first positional argument is a value true/false. If true (default), then
240-
# exclude modules from the global exclusion list.
278+
# Positional parameter $1 (default "true") specifies whether to filter out modules
279+
# defined in the 'excluded_modules' array.
241280
function generate_modified_modules_list() {
242-
# Find the files changed from when the PR branched to the last commit
243-
# Filter for java modules and get all the unique elements
244-
# grep returns 1 (error code) and exits the pipeline if there is no match
245-
# If there is no match, it will return true so the rest of the commands can run
246-
git config --global --add safe.directory $(realpath .)
247-
modified_files=$(git diff --name-only "${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH}...${KOKORO_GITHUB_PULL_REQUEST_COMMIT}")
248-
printf "Modified files:\n%s\n" "${modified_files}"
281+
local files
282+
files=$(get_modified_files)
283+
printf "Modified files:\n%s\n" "${files}"
249284

250285
# Generate the list of valid maven modules
251286
maven_modules_list=$(mvn help:evaluate -Dexpression=project.modules | grep '<.*>.*</.*>' | sed -e 's/<.*>\(.*\)<\/.*>/\1/g')
@@ -265,14 +300,12 @@ function generate_modified_modules_list() {
265300
fi
266301

267302
modified_module_list=()
268-
# If either parent pom.xml is touched, run ITs on all the modules
269-
parent_pom_modified=$(echo "${modified_files}" | grep -E '^google-cloud-(pom|jar)-parent/pom.xml$' || true)
270-
shared_dependencies_modified=$(echo "${modified_files}" | grep -E '^java-shared-dependencies' || true)
271-
if [[ (-n $parent_pom_modified) || (-n $shared_dependencies_modified) || ("${TEST_ALL_MODULES}" == "true") ]]; then
303+
# If either parent pom.xml or core shared dependency is touched, run ITs on all the modules
304+
if should_test_all_modules; then
272305
modified_module_list=(${maven_modules[*]})
273306
echo "Testing the entire monorepo"
274307
else
275-
modules=$(echo "${modified_files}" | grep -E '(google-auth|java)-.*' || true)
308+
modules=$(echo "${files}" | grep -E '(google-auth|java)-.*' || true)
276309
printf "Files in java modules:\n%s\n" "${modules}"
277310
if [[ -n $modules ]]; then
278311
modules=$(echo "${modules}" | cut -d '/' -f1 | sort -u)
@@ -287,6 +320,23 @@ function generate_modified_modules_list() {
287320
fi
288321
}
289322

323+
# Checks if files within a specific module directory were modified in the PR diff.
324+
#
325+
# Uses exact directory prefix matching ('^${module}/') to prevent substring collisions
326+
# where modifying one module triggers tests for another module that shares its prefix
327+
# (e.g. java-bigquery vs java-bigquerystorage).
328+
function is_module_modified() {
329+
local module="$1"
330+
if [[ -z "${module}" ]]; then
331+
return 1
332+
fi
333+
334+
local files
335+
files=$(get_modified_files)
336+
# '<<< "${files}"' feeds the diff string directly to grep via stdin.
337+
grep -q -E "^${module}/" <<< "${files}"
338+
}
339+
290340
# Filters the modified_module_list to only include modules that contain
291341
# integration test files (matching IT*.java or *IT.java in src/test/java).
292342
# Not all modules will have ITs written and there is not need to test

.kokoro/common_test.sh

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ source "$scriptDir/common.sh"
1919
mkdir -p target
2020
cd target
2121

22+
# Mock get_modified_files for unit testing to avoid running Git commands.
23+
# Individual tests set TEST_MODIFIED_FILES to simulate changed files.
24+
function get_modified_files() {
25+
printf '%s\n' "${TEST_MODIFIED_FILES:-}"
26+
}
27+
2228
function test_find_all_poms_with_versioned_dependency {
2329
mkdir -p test_find_all_poms_with_dependency
2430
pushd test_find_all_poms_with_dependency
@@ -75,6 +81,120 @@ function test_parse_pom_version {
7581
popd
7682
}
7783

84+
# Tests that is_module_modified strictly matches the module directory prefix,
85+
# preventing prefix collisions (e.g. java-bigquery vs java-bigquerystorage).
86+
function test_is_module_modified {
87+
if is_module_modified ""; then
88+
echo "is_module_modified should return non-zero for empty module name"
89+
exit 1
90+
fi
91+
92+
# Touching 'java-bigquery' should not match 'java-bigquerystorage', 'java-bigquery-jdbc', etc.
93+
TEST_MODIFIED_FILES="java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQuery.java
94+
java-bigquery/pom.xml"
95+
96+
if ! is_module_modified "java-bigquery"; then
97+
echo "is_module_modified failed to detect java-bigquery modification"
98+
exit 1
99+
fi
100+
if is_module_modified "java-bigquerystorage"; then
101+
echo "is_module_modified incorrectly matched java-bigquerystorage for java-bigquery"
102+
exit 1
103+
fi
104+
if is_module_modified "java-bigquery-jdbc"; then
105+
echo "is_module_modified incorrectly matched java-bigquery-jdbc for java-bigquery"
106+
exit 1
107+
fi
108+
if is_module_modified "java-bigqueryconnection"; then
109+
echo "is_module_modified incorrectly matched java-bigqueryconnection for java-bigquery"
110+
exit 1
111+
fi
112+
113+
# Touching 'java-bigquerystorage' should match only java-bigquerystorage
114+
TEST_MODIFIED_FILES="java-bigquerystorage/google-cloud-bigquerystorage/src/main/java/Foo.java"
115+
if is_module_modified "java-bigquery"; then
116+
echo "is_module_modified incorrectly matched java-bigquery for java-bigquerystorage"
117+
exit 1
118+
fi
119+
if ! is_module_modified "java-bigquerystorage"; then
120+
echo "is_module_modified failed to detect java-bigquerystorage modification"
121+
exit 1
122+
fi
123+
124+
# External / root files should not be considered module modifications
125+
TEST_MODIFIED_FILES="google-cloud-jar-parent/pom.xml
126+
sdk-platform-java/java-shared-dependencies/pom.xml"
127+
if is_module_modified "java-bigquery"; then
128+
echo "is_module_modified should return false for java-bigquery when only external files changed"
129+
exit 1
130+
fi
131+
132+
unset TEST_MODIFIED_FILES
133+
}
134+
135+
# Test should_test_all_modules triggers properly on global changes.
136+
function test_should_test_all_modules {
137+
# When only a normal library is modified, should return false (1)
138+
TEST_MODIFIED_FILES="java-bigquery/pom.xml"
139+
if should_test_all_modules; then
140+
echo "should_test_all_modules should return false for single module change"
141+
exit 1
142+
fi
143+
144+
# Root jar parent pom
145+
TEST_MODIFIED_FILES="google-cloud-jar-parent/pom.xml"
146+
if ! should_test_all_modules; then
147+
echo "should_test_all_modules should return true for google-cloud-jar-parent change"
148+
exit 1
149+
fi
150+
151+
# Root pom parent
152+
TEST_MODIFIED_FILES="google-cloud-pom-parent/pom.xml"
153+
if ! should_test_all_modules; then
154+
echo "should_test_all_modules should return true for google-cloud-pom-parent change"
155+
exit 1
156+
fi
157+
158+
# Core shared dependencies
159+
TEST_MODIFIED_FILES="sdk-platform-java/java-shared-dependencies/pom.xml"
160+
if ! should_test_all_modules; then
161+
echo "should_test_all_modules should return true for java-shared-dependencies change"
162+
exit 1
163+
fi
164+
165+
# Prefix collision check: sibling paths starting with java-shared-dependencies must not match
166+
TEST_MODIFIED_FILES="sdk-platform-java/java-shared-dependencies-bom/pom.xml"
167+
if should_test_all_modules; then
168+
echo "should_test_all_modules should return false for java-shared-dependencies prefix match"
169+
exit 1
170+
fi
171+
172+
# TEST_ALL_MODULES=true
173+
TEST_MODIFIED_FILES=""
174+
if ! TEST_ALL_MODULES="true" should_test_all_modules; then
175+
echo "should_test_all_modules should return true when TEST_ALL_MODULES is true"
176+
exit 1
177+
fi
178+
179+
unset TEST_MODIFIED_FILES
180+
}
181+
182+
# Test mock get_modified_files returns simulated files.
183+
function test_mock_get_modified_files {
184+
TEST_MODIFIED_FILES="dummy/file.txt"
185+
local files
186+
files=$(get_modified_files)
187+
if [[ "${files}" != "dummy/file.txt" ]]; then
188+
echo "mock get_modified_files failed to return expected files"
189+
exit 1
190+
fi
191+
unset TEST_MODIFIED_FILES
192+
}
193+
78194
test_find_all_poms_with_versioned_dependency
79195
test_update_pom_dependency
80196
test_parse_pom_version
197+
test_mock_get_modified_files
198+
test_should_test_all_modules
199+
test_is_module_modified
200+

0 commit comments

Comments
 (0)