Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 11 additions & 30 deletions .github/workflows/check-protected-classes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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'
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/test-workflow-scripts.yml
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions scripts/check-protected-classes.sh
Original file line number Diff line number Diff line change
@@ -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
54 changes: 54 additions & 0 deletions scripts/local-check-protected-classes.sh
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions tests/DEVNOTES.md
Original file line number Diff line number Diff line change
@@ -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
```
90 changes: 90 additions & 0 deletions tests/check-protected-classes.bats
Original file line number Diff line number Diff line change
@@ -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"
}