diff --git a/.github/workflows/check-protected-classes.yml b/.github/workflows/check-protected-classes.yml index 4abab25b790b8..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 }} @@ -32,34 +34,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 +88,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..d033584fc46f5 --- /dev/null +++ b/.github/workflows/test-workflow-scripts.yml @@ -0,0 +1,35 @@ +# 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: + pull_request: + branches: [ main, master ] + 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 100755 index 0000000000000..59837c86d08f4 --- /dev/null +++ b/scripts/check-protected-classes.sh @@ -0,0 +1,53 @@ +#!/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}" + +# 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 +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/scripts/local-check-protected-classes.sh b/scripts/local-check-protected-classes.sh new file mode 100755 index 0000000000000..b5f97899a289b --- /dev/null +++ b/scripts/local-check-protected-classes.sh @@ -0,0 +1,54 @@ +#!/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" + +# 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 -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 + +# 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 + 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/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..480b89695b6d2 --- /dev/null +++ b/tests/check-protected-classes.bats @@ -0,0 +1,90 @@ +#!/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 with explicit multi-condition evaluations +git() { + 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:-}" + 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" +}