From 7318097d4b4addb0da8514c4294824a13cadfa50 Mon Sep 17 00:00:00 2001 From: Maksim Davydov Date: Fri, 22 May 2026 14:50:37 +0300 Subject: [PATCH 1/4] WIP --- .github/workflows/check-protected-classes.yml | 37 ++------ .github/workflows/test-workflow-scripts.yml | 39 ++++++++ scripts/check-protected-classes.sh | 49 +++++++++++ tests/DEVNOTES.md | 32 +++++++ tests/check-protected-classes.bats | 88 +++++++++++++++++++ 5 files changed, 216 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/test-workflow-scripts.yml create mode 100644 scripts/check-protected-classes.sh create mode 100644 tests/DEVNOTES.md create mode 100644 tests/check-protected-classes.bats diff --git a/.github/workflows/check-protected-classes.yml b/.github/workflows/check-protected-classes.yml index 4abab25b790b8..132861d7dd94f 100644 --- a/.github/workflows/check-protected-classes.yml +++ b/.github/workflows/check-protected-classes.yml @@ -32,34 +32,18 @@ jobs: - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 + fetch-depth: 1 - name: Rolling Upgrade check id: check + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_OUTPUT: ${{ github.output }} run: | - BASE_SHA=${{ github.event.pull_request.base.sha }} - HEAD_SHA=${{ github.event.pull_request.head.sha }} - - HITS="" - - # New and deleted files: check diff content for @Order annotation. - for file in $(git diff --name-only --no-renames --diff-filter=AD "$BASE_SHA"..."$HEAD_SHA" -- '*.java'); do - if git diff "$BASE_SHA"..."$HEAD_SHA" -- "$file" | grep -q 'org.apache.ignite.internal.Order'; then - HITS="${HITS}${file}\n" - fi - done - - # Modified files: check base version content for @Order annotation. - for file in $(git diff --name-only --no-renames --diff-filter=M "$BASE_SHA"..."$HEAD_SHA" -- '*.java'); do - if git show "${BASE_SHA}:${file}" 2>/dev/null | grep -q 'org.apache.ignite.internal.Order'; then - HITS="${HITS}${file}\n" - fi - done - - if [ -n "$HITS" ]; then - echo "affected=true" >> "$GITHUB_OUTPUT" - printf '%b' "$HITS" > /tmp/protected-hits.txt - fi + git fetch --depth=1 origin "$BASE_SHA" + chmod +x ./scripts/check-protected-classes.sh + ./scripts/check-protected-classes.sh - name: Comment on PR if: steps.check.outputs.affected == 'true' @@ -102,11 +86,6 @@ jobs: body, }); - - name: Add label - if: steps.check.outputs.affected == 'true' - uses: actions/github-script@v8 - with: - script: | await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/test-workflow-scripts.yml b/.github/workflows/test-workflow-scripts.yml new file mode 100644 index 0000000000000..50df45c2a7961 --- /dev/null +++ b/.github/workflows/test-workflow-scripts.yml @@ -0,0 +1,39 @@ +# 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: Test Workflow Scripts + +on: + push: + branches: [ main, master ] + paths: + - 'scripts/**' + - 'tests/**' + pull_request: + paths: + - 'scripts/**' + - 'tests/**' + +jobs: + validate-bash-logic: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup BATS Framework + uses: bats-core/bats-action@v3 + + - name: Run Script Test Suite + run: bats tests/check-protected-classes.bats diff --git a/scripts/check-protected-classes.sh b/scripts/check-protected-classes.sh new file mode 100644 index 0000000000000..1b07e934927e8 --- /dev/null +++ b/scripts/check-protected-classes.sh @@ -0,0 +1,49 @@ +#!/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 + +# Inputs provided via environment variables or defaults +BASE_SHA="${BASE_SHA}" +HEAD_SHA="${HEAD_SHA}" +ANNOTATION="${ANNOTATION:-org.apache.ignite.internal.Order}" +HITS_FILE="${HITS_FILE:-/tmp/protected-hits.txt}" +GITHUB_OUTPUT="${GITHUB_OUTPUT:-/dev/null}" + +touch "$HITS_FILE" + +# 1. Check added/deleted files for the annotation in the diff context +git diff --name-only --no-renames --diff-filter=AD "$BASE_SHA"..."$HEAD_SHA" -- '*.java' | while read -r file; do + [ -z "$file" ] && continue + if git diff "$BASE_SHA"..."$HEAD_SHA" -- "$file" | grep -q "$ANNOTATION"; then + echo "$file" >> "$HITS_FILE" + fi +done + +# 2. Check modified files instantly by searching the file contents at BASE_SHA +MODIFIED_FILES=$(git diff --name-only --no-renames --diff-filter=M "$BASE_SHA"..."$HEAD_SHA" -- '*.java') + +if [ -n "$MODIFIED_FILES" ]; then + git grep -l "$ANNOTATION" "$BASE_SHA" -- $MODIFIED_FILES >> "$HITS_FILE" 2>/dev/null || true +fi + +# Clean up and produce Outputs +if [ -s "$HITS_FILE" ]; then + sed -i "s/^${BASE_SHA}://g" "$HITS_FILE" + sort -u -o "$HITS_FILE" "$HITS_FILE" + echo "affected=true" >> "$GITHUB_OUTPUT" +fi diff --git a/tests/DEVNOTES.md b/tests/DEVNOTES.md new file mode 100644 index 0000000000000..55bd3b07b998b --- /dev/null +++ b/tests/DEVNOTES.md @@ -0,0 +1,32 @@ +# Development Notes: Running BATS Tests + +This project uses the Bash Automated Testing System (BATS) for testing shell scripts. + +## 1. Prerequisites Installation + +You must install the BATS core framework on your local machine. + +### macOS (via Homebrew) +```bash +brew install bats-core +``` + +### Linux (Ubuntu/Debian) +```bash +sudo apt-get update +sudo apt-get install bats +``` + +## 2. Running Tests in IntelliJ IDEA + +Since we are using the free built-in **Shell Script** plugin, tests are executed via the integrated terminal. + +1. Open the built-in terminal in IntelliJ IDEA (`Alt + F12` or `Option + F12`). +2. Run all tests in the directory: + ```bash + bats . + ``` +3. Run a specific test file: + ```bash + bats test_script.bats + ``` diff --git a/tests/check-protected-classes.bats b/tests/check-protected-classes.bats new file mode 100644 index 0000000000000..2f5b9b01af212 --- /dev/null +++ b/tests/check-protected-classes.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +# 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. + +setup() { + # Dynamically calculate the repository root folder path + DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" + export SCRIPT_PATH="$DIR/../scripts/check-protected-classes.sh" + + export BASE_SHA="baseline123" + export HEAD_SHA="feature456" + export HITS_FILE="${BATS_TMPDIR}/hits.txt" + export GITHUB_OUTPUT="${BATS_TMPDIR}/output.env" + + rm -f "$HITS_FILE" "$GITHUB_OUTPUT" + touch "$GITHUB_OUTPUT" +} + +# Mock Git command execution paths +git() { + if [[ "$*" == *"diff --name-only"* && *"diff-filter=AD"* ]]; then + echo "${MOCK_AD_FILES:-}" + elif [[ "$*" == *"diff --name-only"* && *"diff-filter=M"* ]]; then + echo "${MOCK_M_FILES:-}" + elif [[ "$*" == *"diff"* && *"${MOCK_MATCHING_FILE:-}"* ]]; then + echo "+ @org.apache.ignite.internal.Order" + elif [[ "$*" == *"grep"* ]]; then + if [ -n "${MOCK_GREP_MATCHES:-}" ]; then + echo "${BASE_SHA}:${MOCK_GREP_MATCHES}" + return 0 + fi + return 1 + else + command git "$@" + fi +} + +# Helper function to execute our script within the mocked environment context +run_script() { + # Sourcing it instead of calling 'bash script.sh' keeps our mocked git() function active + run source "$SCRIPT_PATH" +} + +@test "Scenario 1: No changes should yield zero alerts" { + export MOCK_AD_FILES="" + export MOCK_M_FILES="" + + run_script + + [ "$status" -eq 0 ] + [ ! -s "$HITS_FILE" ] + ! grep -q "affected=true" "$GITHUB_OUTPUT" +} + +@test "Scenario 2: Newly added file with target annotation triggers hit" { + export MOCK_AD_FILES="src/main/java/NewProtectedClass.java" + export MOCK_MATCHING_FILE="NewProtectedClass.java" + + run_script + + [ "$status" -eq 0 ] + grep -q "src/main/java/NewProtectedClass.java" "$HITS_FILE" + grep -q "affected=true" "$GITHUB_OUTPUT" +} + +@test "Scenario 3: Modified file containing annotation triggers hit" { + export MOCK_M_FILES="src/main/java/ModifiedClass.java" + export MOCK_GREP_MATCHES="src/main/java/ModifiedClass.java" + + run_script + + [ "$status" -eq 0 ] + grep -q "src/main/java/ModifiedClass.java" "$HITS_FILE" + grep -q "affected=true" "$GITHUB_OUTPUT" +} From 933dc1243dbe64d98c0d1c6721238b86451fd3e4 Mon Sep 17 00:00:00 2001 From: Maksim Davydov Date: Fri, 22 May 2026 15:22:38 +0300 Subject: [PATCH 2/4] IGNITE-28716 Optimize Github action hook to control changes inside the messages --- scripts/check-protected-classes.sh | 0 scripts/local-check-protected-classes.sh | 45 ++++++++++++++++++++++++ tests/check-protected-classes.bats | 6 ++-- 3 files changed, 48 insertions(+), 3 deletions(-) mode change 100644 => 100755 scripts/check-protected-classes.sh create mode 100755 scripts/local-check-protected-classes.sh diff --git a/scripts/check-protected-classes.sh b/scripts/check-protected-classes.sh old mode 100644 new mode 100755 diff --git a/scripts/local-check-protected-classes.sh b/scripts/local-check-protected-classes.sh new file mode 100755 index 0000000000000..4f307e01b5f3f --- /dev/null +++ b/scripts/local-check-protected-classes.sh @@ -0,0 +1,45 @@ +#!/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 + +# Default to master if no base branch is specified as an argument +TARGET_BASE="${1:-master}" + +echo "Calculating diff baseline against branch: $TARGET_BASE..." + +# Automatically resolve SHA values simulating GitHub's environment +export BASE_SHA=$(git merge-base "$TARGET_BASE" HEAD 2>/dev/null || { echo "❌ Error: Branch '$TARGET_BASE' not found."; exit 1; }) +export HEAD_SHA=$(git rev-parse HEAD) +export GITHUB_OUTPUT=/dev/null +export HITS_FILE=/tmp/protected-hits.txt + +# Run the core validation engine +chmod +x ./check-protected-classes.sh +./check-protected-classes.sh + +# Evaluate the outputs +if [ -s "$HITS_FILE" ]; then + echo -e "\nFLAG TRIPPED: The following protected files were altered:" + echo "--------------------------------------------------------" + cat "$HITS_FILE" + echo "--------------------------------------------------------" + exit 1 +else + echo -e "\nSUCCESS: No protected class modifications detected." + exit 0 +fi diff --git a/tests/check-protected-classes.bats b/tests/check-protected-classes.bats index 2f5b9b01af212..2a4eaf1fc0a44 100644 --- a/tests/check-protected-classes.bats +++ b/tests/check-protected-classes.bats @@ -29,11 +29,11 @@ setup() { touch "$GITHUB_OUTPUT" } -# Mock Git command execution paths +# Mock Git command execution paths with explicit multi-condition evaluations git() { - if [[ "$*" == *"diff --name-only"* && *"diff-filter=AD"* ]]; then + if [[ "$*" == *"diff --name-only"* && "$*" == *"diff-filter=AD"* ]]; then echo "${MOCK_AD_FILES:-}" - elif [[ "$*" == *"diff --name-only"* && *"diff-filter=M"* ]]; then + elif [[ "$*" == *"diff --name-only"* && "$*" == *"diff-filter=M"* ]]; then echo "${MOCK_M_FILES:-}" elif [[ "$*" == *"diff"* && *"${MOCK_MATCHING_FILE:-}"* ]]; then echo "+ @org.apache.ignite.internal.Order" From cfab4be76b9cad4a8ccba404113d368ef5bfbae0 Mon Sep 17 00:00:00 2001 From: Maksim Davydov Date: Fri, 22 May 2026 15:44:10 +0300 Subject: [PATCH 3/4] IGNITE-28716 Optimize Github action hook to control changes inside the messages --- scripts/check-protected-classes.sh | 4 ++++ scripts/local-check-protected-classes.sh | 21 +++++++++++++++------ tests/check-protected-classes.bats | 4 +++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/scripts/check-protected-classes.sh b/scripts/check-protected-classes.sh index 1b07e934927e8..59837c86d08f4 100755 --- a/scripts/check-protected-classes.sh +++ b/scripts/check-protected-classes.sh @@ -24,6 +24,10 @@ ANNOTATION="${ANNOTATION:-org.apache.ignite.internal.Order}" HITS_FILE="${HITS_FILE:-/tmp/protected-hits.txt}" GITHUB_OUTPUT="${GITHUB_OUTPUT:-/dev/null}" +# Automatically locate and navigate to the repository root directory +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + touch "$HITS_FILE" # 1. Check added/deleted files for the annotation in the diff context diff --git a/scripts/local-check-protected-classes.sh b/scripts/local-check-protected-classes.sh index 4f307e01b5f3f..b5f97899a289b 100755 --- a/scripts/local-check-protected-classes.sh +++ b/scripts/local-check-protected-classes.sh @@ -20,17 +20,26 @@ set -euo pipefail # Default to master if no base branch is specified as an argument TARGET_BASE="${1:-master}" -echo "Calculating diff baseline against branch: $TARGET_BASE..." +echo "Calculating diff baseline against branch: $TARGET_BASE" + +# Dynamically locate the repository root directory +REPO_ROOT="$(git rev-parse --show-toplevel)" # Automatically resolve SHA values simulating GitHub's environment -export BASE_SHA=$(git merge-base "$TARGET_BASE" HEAD 2>/dev/null || { echo "❌ Error: Branch '$TARGET_BASE' not found."; exit 1; }) -export HEAD_SHA=$(git rev-parse HEAD) +export BASE_SHA=$(git -C "$REPO_ROOT" merge-base "$TARGET_BASE" HEAD 2>/dev/null || { echo "❌ Error: Branch '$TARGET_BASE' not found."; exit 1; }) +export HEAD_SHA=$(git -C "$REPO_ROOT" rev-parse HEAD) export GITHUB_OUTPUT=/dev/null export HITS_FILE=/tmp/protected-hits.txt -# Run the core validation engine -chmod +x ./check-protected-classes.sh -./check-protected-classes.sh +# Clear any residue results from a previous local run +rm -f "$HITS_FILE" + +# Absolute reference to the core validation script path +CORE_SCRIPT="$REPO_ROOT/scripts/check-protected-classes.sh" + +# Run the core validation engine safely using the absolute path reference +chmod +x "$CORE_SCRIPT" +"$CORE_SCRIPT" # Evaluate the outputs if [ -s "$HITS_FILE" ]; then diff --git a/tests/check-protected-classes.bats b/tests/check-protected-classes.bats index 2a4eaf1fc0a44..480b89695b6d2 100644 --- a/tests/check-protected-classes.bats +++ b/tests/check-protected-classes.bats @@ -31,7 +31,9 @@ setup() { # Mock Git command execution paths with explicit multi-condition evaluations git() { - if [[ "$*" == *"diff --name-only"* && "$*" == *"diff-filter=AD"* ]]; then + if [[ "$*" == *"rev-parse --show-toplevel"* ]]; then + echo "${BATS_TMPDIR}" + elif [[ "$*" == *"diff --name-only"* && "$*" == *"diff-filter=AD"* ]]; then echo "${MOCK_AD_FILES:-}" elif [[ "$*" == *"diff --name-only"* && "$*" == *"diff-filter=M"* ]]; then echo "${MOCK_M_FILES:-}" From 6c64817ed7bd9997c96127c74dea59022642233d Mon Sep 17 00:00:00 2001 From: Maksim Davydov Date: Fri, 22 May 2026 16:48:55 +0300 Subject: [PATCH 4/4] IGNITE-28716 Visa --- .github/workflows/check-protected-classes.yml | 4 +++- .github/workflows/test-workflow-scripts.yml | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/check-protected-classes.yml b/.github/workflows/check-protected-classes.yml index 132861d7dd94f..e06265c9ab2e6 100644 --- a/.github/workflows/check-protected-classes.yml +++ b/.github/workflows/check-protected-classes.yml @@ -15,7 +15,9 @@ name: Protected Classes -on: pull_request_target +on: + pull_request: + branches: [ main, master ] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} diff --git a/.github/workflows/test-workflow-scripts.yml b/.github/workflows/test-workflow-scripts.yml index 50df45c2a7961..d033584fc46f5 100644 --- a/.github/workflows/test-workflow-scripts.yml +++ b/.github/workflows/test-workflow-scripts.yml @@ -16,12 +16,8 @@ name: Test Workflow Scripts on: - push: - branches: [ main, master ] - paths: - - 'scripts/**' - - 'tests/**' pull_request: + branches: [ main, master ] paths: - 'scripts/**' - 'tests/**'