From 0aed1c5852879aa66c37f8edc22486333adc66b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:55:57 +0300 Subject: [PATCH 01/15] fixed scripts for php --- php/check_phpstan.sh | 124 ++++++++++++++++++++++++++++++++++++++ php/laravel/check_pint.sh | 44 ++++++-------- 2 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 php/check_phpstan.sh diff --git a/php/check_phpstan.sh b/php/check_phpstan.sh new file mode 100644 index 0000000..f6371cb --- /dev/null +++ b/php/check_phpstan.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +STRICTNESS="$1" +shift 1 + +FILES=("$@") +BASELINE_FILE=".phpstan-error-count.json" +LOCK_FILE=".phpstan-error-count.lock" +BLOCK_COMMIT=0 +BASELINE_UPDATED=0 + +lock_baseline() { + local timeout=30 + local elapsed=0 + while [ -f "$LOCK_FILE" ] && [ $elapsed -lt $timeout ]; do + sleep 0.1 + elapsed=$((elapsed + 1)) + done + + if [ $elapsed -ge $timeout ]; then + echo "ERROR: Could not acquire lock after ${timeout}s" + exit 1 + fi + + touch "$LOCK_FILE" +} + +unlock_baseline() { + rm -f "$LOCK_FILE" +} + +trap unlock_baseline EXIT + +# Init baseline +if [ ! -f "$BASELINE_FILE" ] || [ ! -s "$BASELINE_FILE" ]; then + echo '{}' > "$BASELINE_FILE" +fi + +if ! jq empty "$BASELINE_FILE" 2>/dev/null; then + echo '{}' > "$BASELINE_FILE" +fi + +if [ ${#FILES[@]} -eq 0 ]; then + echo "[PHPStan] No PHP files to check." + exit 0 +fi + +for FILE in "${FILES[@]}"; do + if [ ! -f "$FILE" ]; then + continue + fi + + echo "Checking: $FILE" + + ERR_NEW=$(vendor/bin/phpstan analyse --error-format=raw --no-progress "$FILE" 2>/dev/null | grep -c '^') + + lock_baseline + ERR_OLD=$(jq -r --arg file "$FILE" '.[$file] // empty' "$BASELINE_FILE" 2>/dev/null) + unlock_baseline + + IS_NEW_FILE=0 + if [ -z "$ERR_OLD" ]; then + IS_NEW_FILE=1 + ERR_OLD=$ERR_NEW + fi + + if [ "$STRICTNESS" = "strict" ]; then + TARGET=0 + else + # Для следующего коммита требуем хотя бы на 1 ошибку меньше + TARGET=$((ERR_OLD - 1)) + [ "$TARGET" -lt 0 ] && TARGET=0 + fi + + SHOULD_UPDATE=0 + + if [ "$IS_NEW_FILE" -eq 1 ] && [ "$ERR_NEW" -gt 0 ]; then + echo " New file: $ERR_NEW errors found" + vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE" + echo " → Baseline set to $ERR_NEW errors. Fix at least 1 error before next commit." + SHOULD_UPDATE=1 + BLOCK_COMMIT=1 + elif [ "$ERR_NEW" -le "$TARGET" ]; then + echo " ✓ Progress: $ERR_OLD → $ERR_NEW errors" + SHOULD_UPDATE=1 + else + if [ "$ERR_NEW" -eq "$ERR_OLD" ]; then + echo " ✗ No progress: still $ERR_NEW errors (need ≤ $TARGET)" + else + echo " ✗ Errors increased: $ERR_OLD → $ERR_NEW (need ≤ $TARGET)" + fi + vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE" + BLOCK_COMMIT=1 + # НЕ обновляем baseline + fi + + if [ "$SHOULD_UPDATE" -eq 1 ]; then + lock_baseline + + if jq --arg file "$FILE" --argjson errors "$ERR_NEW" \ + '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" 2>/dev/null; then + mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" + BASELINE_UPDATED=1 + else + rm -f "$BASELINE_FILE.tmp" + fi + + unlock_baseline + fi + + echo "" +done + +if [ "$BASELINE_UPDATED" -eq 1 ]; then + git add "$BASELINE_FILE" 2>/dev/null +fi + +if [ "$BLOCK_COMMIT" -eq 1 ]; then + echo "Commit blocked. Reduce error count to proceed!" + exit 1 +fi + +echo "[PHPStan] All checks passed!" +exit 0 diff --git a/php/laravel/check_pint.sh b/php/laravel/check_pint.sh index 0f36da0..2460b87 100644 --- a/php/laravel/check_pint.sh +++ b/php/laravel/check_pint.sh @@ -1,41 +1,33 @@ #!/bin/bash -COMMAND="$1" - -# ----------------------------- -# Get list of PHP files to check -# ----------------------------- -if [ "$COMMAND" = "commit" ]; then - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) -elif [ "$COMMAND" = "push" ]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD) - ALL_FILES=$(git diff --name-only origin/$BRANCH --diff-filter=ACM | grep '\.php$' || true) -else - echo -e "⚠️ Unknown command: $COMMAND" - exit 1 +if [ $# -eq 0 ]; then + echo "[Pint] No PHP files to check." + exit 0 fi -# Exit if no PHP files found -if [ -z "$ALL_FILES" ]; then - echo -e "⚠️ [Pint] No PHP files to check." - exit 0 +FILES=() +for f in "$@"; do + [[ "$f" == *.php ]] && FILES+=("$f") +done + +if [ ${#FILES[@]} -eq 0 ]; then + echo "[Pint] No PHP files to check." + exit 0 fi # ----------------------------- # Run Pint in test mode # ----------------------------- -vendor/bin/pint --test $ALL_FILES +vendor/bin/pint --test "${FILES[@]}" RESULT=$? if [ $RESULT -ne 0 ]; then - echo -e "❌ Pint found code style issues. Auto-fixing..." - - vendor/bin/pint $ALL_FILES - echo "$ALL_FILES" | xargs git add - - echo -e "✅ [Pint] Code style fixed. Please re-run the commit." - exit 1 + echo "Pint found code style issues. Auto-fixing..." + vendor/bin/pint "${FILES[@]}" + git add "${FILES[@]}" + echo "[Pint] Code style fixed automatically." +else + echo "[Pint] All files pass code style." fi -echo -e "✅ [Pint] All files pass code style." exit 0 From 8e5cf19104c7e240588d096fab500c953c8ec691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:02:00 +0300 Subject: [PATCH 02/15] fixed add_task_id_in_commit.sh --- git/preparations/add_task_id_in_commit.sh | 25 +++++++++++++++++++++ shell/preparations/add_task_id_in_commit.sh | 13 ----------- 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 git/preparations/add_task_id_in_commit.sh delete mode 100644 shell/preparations/add_task_id_in_commit.sh diff --git a/git/preparations/add_task_id_in_commit.sh b/git/preparations/add_task_id_in_commit.sh new file mode 100644 index 0000000..1f81d3f --- /dev/null +++ b/git/preparations/add_task_id_in_commit.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# ------------------------------------------------------------------------------ +# Prepends task ID to commit message based on branch name. +# Extracts task ID from branch using configurable TASK_ID_PATTERN. +# Skips if task ID already present in commit message. +# Example: branch "feature/dev-2212_order" -> commit "dev-2212 | " +# ------------------------------------------------------------------------------ + +# ----------------------------------------- +# Configuration +# ----------------------------------------- +TASK_ID_PATTERN="dev-[0-9]+" +COMMIT_MSG_FILE=".git/COMMIT_EDITMSG" +# ----------------------------------------- + +BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) + +if echo "$BRANCH_NAME" | grep -qE "$TASK_ID_PATTERN"; then + TASK_ID=$(echo "$BRANCH_NAME" | grep -oE "$TASK_ID_PATTERN") + + if ! grep -q "$TASK_ID" "$COMMIT_MSG_FILE"; then + sed -i.bak "1s/^/$TASK_ID | /" "$COMMIT_MSG_FILE" + rm -f "$COMMIT_MSG_FILE.bak" + fi +fi diff --git a/shell/preparations/add_task_id_in_commit.sh b/shell/preparations/add_task_id_in_commit.sh deleted file mode 100644 index f5fed1b..0000000 --- a/shell/preparations/add_task_id_in_commit.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -COMMIT_MSG_FILE=".git/COMMIT_EDITMSG" -BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) - -if echo "$BRANCH_NAME" | grep -qE 'dev-[0-9]+'; then - TASK_ID=$(echo "$BRANCH_NAME" | grep -oE 'dev-[0-9]+') - - if ! grep -q "$TASK_ID" "$COMMIT_MSG_FILE"; then - sed -i.bak "1s/^/$TASK_ID | /" "$COMMIT_MSG_FILE" - rm -f "$COMMIT_MSG_FILE.bak" - fi -fi From f7cf514eec93f44c31b063f06f6404d126540a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:14:15 +0300 Subject: [PATCH 03/15] fixed check_hadolint.sh --- docker/check_hadolint.sh | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docker/check_hadolint.sh diff --git a/docker/check_hadolint.sh b/docker/check_hadolint.sh new file mode 100644 index 0000000..81ef3ee --- /dev/null +++ b/docker/check_hadolint.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# ------------------------------------------------------------------------------ +# Runs Hadolint on Dockerfiles passed as arguments. +# Only files named Dockerfile* are checked. +# Ignores specified Hadolint rules. +# Fails if any Dockerfile contains issues. +# ------------------------------------------------------------------------------ + +set -e + +# ----------------------------------------- +# Configuration +# ----------------------------------------- +PROJECT_DIR="." + +IGNORE_RULES="DL3008|DL3015" + +# ----------------------------------------- +# Get files from arguments +# ----------------------------------------- +ALL_FILES=("$@") +DOCKERFILES=() + +for FILE in "${ALL_FILES[@]}"; do + if [[ "$(basename "$FILE")" == Dockerfile* ]]; then + DOCKERFILES+=("$FILE") + fi +done + +if [[ ${#DOCKERFILES[@]} -eq 0 ]]; then + echo -e "No Dockerfiles in provided files. Skipping Hadolint." + exit 0 +fi + +# ----------------------------------------- +# Run Hadolint +# ----------------------------------------- +cd "$PROJECT_DIR" || { + echo -e "Cannot cd to $PROJECT_DIR" + exit 1 +} + +ERROR_FOUND=0 + +for FILE in "${DOCKERFILES[@]}"; do + FULL_PATH="$PROJECT_DIR/$FILE" + + if [[ ! -f "$FULL_PATH" ]]; then + echo -e "File $FULL_PATH not found. Skipping." + continue + fi + + if [[ -n "$IGNORE_RULES" ]]; then + output=$(hadolint "$FULL_PATH" 2>&1 | grep -vE "$IGNORE_RULES" || true) + else + output=$(hadolint "$FULL_PATH" 2>&1 || true) + fi + + if [[ -n "$output" ]]; then + echo -e "Issues found in $FULL_PATH:" + echo "$output" + ERROR_FOUND=1 + else + echo -e "$FULL_PATH passed Hadolint checks!" + fi +done + +if [[ $ERROR_FOUND -eq 0 ]]; then + echo -e "All Dockerfiles passed Hadolint checks!" +else + echo -e "Hadolint found issues in one or more Dockerfiles!" + exit 1 +fi From 37682c240f52963864f266781fc929836c84ccaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:15:03 +0300 Subject: [PATCH 04/15] delete old scripts --- docker/check_docker.sh | 34 --- php/laravel/check_phpstan.sh | 94 ------- php/laravel/find_tests.sh | 227 --------------- php/laravel/ssh_start_tests.sh | 166 ----------- php/laravel/start_tests.sh | 144 ---------- php/laravel/starting_test_from_docker.sh | 263 ------------------ .../prepare-commit-description.sh | 29 -- shell/ssh_start_shellcheck.sh | 22 -- 8 files changed, 979 deletions(-) delete mode 100644 docker/check_docker.sh delete mode 100644 php/laravel/check_phpstan.sh delete mode 100644 php/laravel/find_tests.sh delete mode 100644 php/laravel/ssh_start_tests.sh delete mode 100644 php/laravel/start_tests.sh delete mode 100644 php/laravel/starting_test_from_docker.sh delete mode 100644 shell/preparations/prepare-commit-description.sh delete mode 100644 shell/ssh_start_shellcheck.sh diff --git a/docker/check_docker.sh b/docker/check_docker.sh deleted file mode 100644 index 83a1cd5..0000000 --- a/docker/check_docker.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -echo "=== Остановка всех контейнеров ===" -docker-compose down - -echo "=== Сборка контейнеров ===" -docker-compose build - -echo "=== Запуск контейнеров в фоне ===" -docker-compose up -d - -# Пауза для запуска сервисов -echo "=== Ждем 5 секунд для старта сервисов ===" -sleep 5 - -echo "=== Проверка состояния контейнеров ===" -# Получаем статус всех контейнеров -STATUS=$(docker-compose ps --services --filter "status=running") - -if [ -z "$STATUS" ]; then - echo "Ошибка: ни один контейнер не запущен!" - exit 1 -else - echo "Запущенные контейнеры:" - docker-compose ps -fi - -# Дополнительно можно проверять HEALTHCHECK каждого контейнера -echo "=== Проверка состояния HEALTH ===" -docker ps --filter "health=unhealthy" --format "table {{.Names}}\t{{.Status}}" - -echo "=== Скрипт завершен ===" - -exit 0 diff --git a/php/laravel/check_phpstan.sh b/php/laravel/check_phpstan.sh deleted file mode 100644 index d1adb1f..0000000 --- a/php/laravel/check_phpstan.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash - -# ----------------------------- -# CHECK NEW FILES -# ----------------------------- -COMMAND="$1" - -if [ "$COMMAND" = "commit" ]; then - # Only new files (status A = Added) ending with .php - NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep '\.php$') - - # Filter out: - # - files in the tests/ folder - # - files ending with *Test.php - FILTERED_FILES=$(echo "$NEW_FILES" | grep -v '/tests/' | grep -v 'Test\.php$') - - if [ -z "$FILTERED_FILES" ]; then - echo -e "⚠️ [PHPStan] No new PHP files to check (or all are tests)" - else - echo "🔍 Found new PHP files. Running PHPStan only on new files (excluding tests)" - ./vendor/bin/phpstan analyse --no-progress --error-format=table $FILTERED_FILES - if [ $? -ne 0 ]; then - echo -e "❌ NEW FILES! PHPStan found type errors (MANDATORY)" - exit 1 - fi - fi -fi - -# ----------------------------- -# CHECK MODIFIED FILES -# ----------------------------- -BASELINE_FILE=".phpstan-error-count.json" -BLOCK_COMMIT=0 - -if [ "$COMMAND" = "commit" ]; then - # Added, Copied, or Modified files - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) -elif [ "$COMMAND" = "push" ]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD) - ALL_FILES=$(git diff --name-only origin/$BRANCH --diff-filter=ACM | grep '\.php$' || true) -else - echo -e "❌ Unknown command: $COMMAND" - exit 1 -fi - -# Initialize baseline file if missing -if [ ! -f "$BASELINE_FILE" ]; then - echo "{}" > "$BASELINE_FILE" -fi - -if [ -z "$ALL_FILES" ]; then - echo -e "⚠️ [PHPStan] No PHP files to check." - exit 0 -fi - -echo "🔍 [PHPStan] Checking files" - -for FILE in $ALL_FILES; do - echo -e "📄 Checking: $FILE" - - # Count new errors - ERR_NEW=$(vendor/bin/phpstan analyse --error-format=raw --no-progress "$FILE" 2>/dev/null | grep -c '^') - ERR_OLD=$(jq -r --arg file "$FILE" '.[$file] // empty' "$BASELINE_FILE") - - if [ -z "$ERR_OLD" ]; then - echo -e "🆕 File not checked before. It has $ERR_NEW errors." - ERR_OLD=$ERR_NEW - fi - - # Set target: allow at most one new error compared to baseline - TARGET=$((ERR_OLD - 1)) - [ "$TARGET" -lt 0 ] && TARGET=0 - - if [ "$ERR_NEW" -le "$TARGET" ]; then - echo -e "✅ Improved: was $ERR_OLD, now $ERR_NEW" - jq --arg file "$FILE" --argjson errors "$ERR_NEW" '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" - else - echo -e "❌ Errors: $ERR_NEW (must be ≤ $TARGET)" - vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE" - jq --arg file "$FILE" --argjson errors "$ERR_OLD" '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" - BLOCK_COMMIT=1 - fi - - echo "------------------" -done - -if [ "$BLOCK_COMMIT" -eq 1 ]; then - echo -e "⛔ Commit blocked. Reduce the number of errors compared to the previous version." - exit 1 -fi - -echo "✅ [PHPStan] Check completed successfully." - -exit 0 diff --git a/php/laravel/find_tests.sh b/php/laravel/find_tests.sh deleted file mode 100644 index 5c5da9b..0000000 --- a/php/laravel/find_tests.sh +++ /dev/null @@ -1,227 +0,0 @@ -#!/bin/bash - -# Script to check if classes have corresponding tests - -set -e - -# ----------------------------- -# CONFIG -# ----------------------------- -EXCLUDE_PATTERNS=( - "*Test" "*Controller*" "*Console*" - "*Models*" "*Resources*" "*DTO*" "*Dtos*" - "*Kernel*" "*config*" "*ValueObject*" - "*Enum*" "*Exception*" "*migrations*" - "*Mock*" "*resources*" "*Stubs*" "*TestCase*" -) - -# ----------------------------- -# Find project root -# ----------------------------- -find_project_root() { - local current_dir="$PWD" - - while [[ "$current_dir" != "/" ]]; do - if [[ -f "$current_dir/composer.json" ]]; then - echo "$current_dir" - return 0 - fi - current_dir=$(dirname "$current_dir") - done - - echo -e "❌ Laravel project root not found (composer.json missing)" - exit 1 -} - -# ----------------------------- -# Path → ClassName -# ----------------------------- -path_to_classname() { - local path="$1" - path="${path%.php}" - path="${path#app/}" - local classname="${path//\//\\}" - - echo "$classname" -} - -# ----------------------------- -# Determine if class should be tested -# ----------------------------- -should_be_tested() { - local classname="$1" - - for pattern in "${EXCLUDE_PATTERNS[@]}"; do - # shellcheck disable=SC2053 - if [[ "$classname" == $pattern ]]; then - return 1 - fi - done - - return 0 -} - -# ----------------------------- -# Get expected test class name -# ----------------------------- -get_expected_test_classname() { - local classname="$1" - echo "Tests\Unit\\${classname}Test" -} - -# ----------------------------- -# Find all test classes -# ----------------------------- -find_test_classes() { - local project_root="$1" - local test_classes=() - - # Include module tests - local test_paths=( - "$project_root/tests/Unit" - "$project_root/tests/Feature" - ) - - # Collect test classes - for path in "${test_paths[@]}"; do - if [[ -d "$path" ]]; then - while IFS= read -r -d '' file; do - if [[ "$file" == *"Test.php" ]]; then - local classname - classname=$(extract_classname_from_file "$file") - - if [[ -n "$classname" ]]; then - test_classes+=("$classname") - fi - fi - done < <(find "$path" -name "*.php" -type f -print0 2>/dev/null) - fi - done - - # Return unique class names - printf '%s\n' "${test_classes[@]}" | sort -u -} - -# ----------------------------- -# Extract class name from a PHP file -# ----------------------------- -extract_classname_from_file() { - local file="$1" - - if [[ ! -f "$file" ]]; then - return 1 - fi - - local namespace="" - namespace=$(grep -m1 "^namespace " "$file" | sed 's/namespace \(.*\);/\1/' | tr -d ' ') - - local classname="" - classname=$(grep -m1 "^class " "$file" | sed 's/class \([a-zA-Z0-9_]*\).*/\1/') - - if [[ -n "$namespace" && -n "$classname" ]]; then - echo "${namespace}\\${classname}" - fi -} - -# ----------------------------- -# Check if a test class exists -# ----------------------------- -has_test() { - local classname="$1" - local expected_test="$2" - shift 2 - local test_classes=("$@") - - for test_class in "${test_classes[@]}"; do - if [[ "$test_class" == "$expected_test" ]]; then - return 0 - fi - done - - return 1 -} - -# ----------------------------- -# Get file path of a test class -# ----------------------------- -get_test_file_path() { - local test_classname="$1" - local project_root="$2" - - local path="${test_classname//\\//}" - echo "${project_root}/${path}.php" -} - -# ----------------------------- -# Analyze if class has a test -# ----------------------------- -analyze_coverage() { - local app_class="$1" - local project_root="$2" - - local normalized_classname - normalized_classname=$(path_to_classname "$app_class") - - if ! should_be_tested "$normalized_classname"; then - echo -e "⚠️ Class does not require testing: $normalized_classname" - echo "---" - return 0 - fi - - local expected_test - expected_test=$(get_expected_test_classname "$normalized_classname") - - # Load all test classes - local test_classes_array=() - while IFS= read -r line; do - test_classes_array+=("$line") - done < <(find_test_classes "$project_root") - - if has_test "$normalized_classname" "$expected_test" "${test_classes_array[@]}"; then - echo -e "✅ Test found: $expected_test" - echo "---" - return 0 - else - echo -e "❌ Please create test file: $expected_test" - echo "---" - return 1 - fi -} - -# ----------------------------- -# Main function -# ----------------------------- -main() { - COMMAND="$1" # commit или push - - if [ "$COMMAND" = "commit" ]; then - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - elif [ "$COMMAND" = "push" ]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD) - ALL_FILES=$(git diff --name-only origin/$BRANCH --diff-filter=ACM | grep '\.php$' || true) - else - echo "Unknown command: $COMMAND" - exit 1 - fi - - if [ -z "$ALL_FILES" ]; then - echo -e "⚠️ [FindTest] No tests required!" - exit 0 - fi - - local status_analyze=1 - for app_class in $ALL_FILES; do - local project_root - project_root=$(find_project_root) - - if ! analyze_coverage "$app_class" "$project_root"; then - status_analyze=0 - fi - done - - if [ "$status_analyze" = 0 ]; then - exit 1 - fi -} - -main "$@" diff --git a/php/laravel/ssh_start_tests.sh b/php/laravel/ssh_start_tests.sh deleted file mode 100644 index 40d6a2f..0000000 --- a/php/laravel/ssh_start_tests.sh +++ /dev/null @@ -1,166 +0,0 @@ -#!/bin/bash - -set -e - -# ----------------------------------------- -# SSH + DOCKER SETTINGS -# ----------------------------------------- -SERVER_USER="root" -SERVER_HOST="12.34.56.789" -PROJECT_DIR="/home/project" -# ----------------------------------------- - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -info() { echo -e "${BLUE}ℹ️ $1${NC}"; } -success() { echo -e "${GREEN}✅ $1${NC}"; } -warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } -error() { echo -e "${RED}❌ $1${NC}"; } - -# ----------------------------------------- -# Find the project root -# ----------------------------------------- -find_project_root() { - local dir="$PWD" - while [[ "$dir" != "/" ]]; do - [[ -f "$dir/composer.json" ]] && echo "$dir" && return - dir=$(dirname "$dir") - done - error "Project root not found (composer.json)" - exit 1 -} - -# ----------------------------------------- -# Convert file path to namespace-classname -# ----------------------------------------- -path_to_classname() { - local path="$1" - path="${path%.php}" - path="${path#app/}" - echo "${path//\//\\}" -} - -# ----------------------------------------- -# Find the test file path for a given app class -# ----------------------------------------- -find_test_file_by_class() { - local classname="$1" - local project_root="$2" - - for dir in Unit Feature; do - local test_file="$project_root/tests/$dir/${classname//\\//}Test.php" - [[ -f "$test_file" ]] && echo "$test_file" && return 0 - done - - return 1 -} - -# ----------------------------------------- -# Run a test file on the server via SSH + Docker -# ----------------------------------------- -run_test_file() { - local test_file="$1" - # path relative to PROJECT_DIR on the server - local relative_path="${test_file#$PROJECT_ROOT/}" - - info "Running test on server: $relative_path" - - ssh ${SERVER_USER}@${SERVER_HOST} \ - "cd ${PROJECT_DIR} && docker compose exec -T app php artisan test $relative_path" - - local exit_code=$? - if [[ $exit_code -eq 0 ]]; then - success "Test passed: $relative_path" - return 0 - else - error "Test failed: $relative_path" - return 1 - fi -} - -# ----------------------------------------- -# Main function -# ----------------------------------------- -main() { - local COMMAND="$1" - - if [[ "$COMMAND" = "commit" ]]; then - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - elif [[ "$COMMAND" = "push" ]]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) - if [[ -z "$BRANCH" || "$BRANCH" = "HEAD" ]]; then - error "Failed to determine branch" - exit 1 - fi - - if ! git ls-remote --exit-code origin "$BRANCH" >/dev/null 2>&1; then - warning "origin/$BRANCH does not exist — testing staged files" - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - else - ALL_FILES=$(git diff --name-only origin/"$BRANCH" --diff-filter=ACM | grep '\.php$' || true) - fi - else - error "Unknown command: $COMMAND (commit|push)" - exit 1 - fi - - if [[ -z "$ALL_FILES" ]]; then - warning "[RunTests] No PHP files to test!" - exit 0 - fi - - PROJECT_ROOT=$(find_project_root) - has_failures=0 - declare -a tests_to_run=() - - # Add test to array if not already added - add_unique_test() { - local file="$1" - for f in "${tests_to_run[@]}"; do - [[ "$f" == "$file" ]] && return 0 - done - tests_to_run+=("$file") - } - - # Build a unique list of tests - while IFS= read -r file; do - [[ -z "$file" ]] && continue - - # If this is a test file - if [[ "$file" == tests/Unit/* || "$file" == tests/Feature/* ]]; then - local abs_path="$PROJECT_ROOT/$file" - [[ -f "$abs_path" ]] && add_unique_test "$abs_path" - fi - - # If this is an app class - if [[ "$file" == app/* ]]; then - local classname - classname=$(path_to_classname "$file") - - local test_file - test_file=$(find_test_file_by_class "$classname" "$PROJECT_ROOT") - - [[ -n "$test_file" ]] && add_unique_test "$test_file" - fi - done <<< "$ALL_FILES" - - # Run the tests - for test_file in "${tests_to_run[@]}"; do - run_test_file "$test_file" || has_failures=1 - done - - if [[ $has_failures -eq 1 ]]; then - error "One or more tests failed" - exit 1 - else - success "All tests passed successfully" - exit 0 - fi -} - -main "$@" diff --git a/php/laravel/start_tests.sh b/php/laravel/start_tests.sh deleted file mode 100644 index 614289c..0000000 --- a/php/laravel/start_tests.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/bin/bash - -set -e - -# ----------------------------------------- -# Find the project root -# ----------------------------------------- -find_project_root() { - local dir="$PWD" - while [[ "$dir" != "/" ]]; do - [[ -f "$dir/composer.json" ]] && echo "$dir" && return - dir=$(dirname "$dir") - done - echo -e "❌ Project root not found (composer.json)" - exit 1 -} - -# ----------------------------------------- -# Convert file path to namespace-classname -# ----------------------------------------- -path_to_classname() { - local path="$1" - path="${path%.php}" - path="${path#app/}" - echo "${path//\//\\}" -} - -# ----------------------------------------- -# Find the test file path for a given app class -# ----------------------------------------- -find_test_file_by_class() { - local classname="$1" - local project_root="$2" - - for dir in Unit Feature; do - local test_file="$project_root/tests/$dir/${classname//\\//}Test.php" - [[ -f "$test_file" ]] && echo "$test_file" && return 0 - done - - return 1 -} - -# ----------------------------------------- -# Run a test file locally -# ----------------------------------------- -run_test_file() { - local test_file="$1" - local relative_path="${test_file#$PROJECT_ROOT/}" - - echo -e "ℹ️ Running test: $relative_path" - - # Используем Artisan локально - php artisan test "$relative_path" - local exit_code=$? - - if [[ $exit_code -eq 0 ]]; then - echo -e "✅ Test passed: $relative_path" - return 0 - else - echo -e "❌ Test failed: $relative_path" - return 1 - fi -} - -# ----------------------------------------- -# Main function -# ----------------------------------------- -main() { - local COMMAND="$1" - - if [[ "$COMMAND" = "commit" ]]; then - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - elif [[ "$COMMAND" = "push" ]]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) - if [[ -z "$BRANCH" || "$BRANCH" = "HEAD" ]]; then - echo -e "❌ Failed to determine branch" - exit 1 - fi - - if ! git ls-remote --exit-code origin "$BRANCH" >/dev/null 2>&1; then - echo -e "⚠️ origin/$BRANCH does not exist — testing staged files" - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - else - ALL_FILES=$(git diff --name-only origin/"$BRANCH" --diff-filter=ACM | grep '\.php$' || true) - fi - else - echo -e "❌ Unknown command: $COMMAND (commit|push)" - exit 1 - fi - - if [[ -z "$ALL_FILES" ]]; then - echo -e "⚠️ [RunTests] No PHP files to test!" - exit 0 - fi - - PROJECT_ROOT=$(find_project_root) - has_failures=0 - declare -a tests_to_run=() - - add_unique_test() { - local file="$1" - for f in "${tests_to_run[@]}"; do - [[ "$f" == "$file" ]] && return 0 - done - tests_to_run+=("$file") - } - - while IFS= read -r file; do - [[ -z "$file" ]] && continue - - if [[ "$file" == tests/Unit/* || "$file" == tests/Feature/* ]]; then - local abs_path="$PROJECT_ROOT/$file" - [[ -f "$abs_path" ]] && add_unique_test "$abs_path" - fi - - if [[ "$file" == app/* ]]; then - local classname - classname=$(path_to_classname "$file") - local test_file - if test_file=$(find_test_file_by_class "$classname" "$PROJECT_ROOT"); then - add_unique_test "$test_file" - fi - fi - done <<< "$ALL_FILES" - - if [[ ${#tests_to_run[@]} -eq 0 ]]; then - echo -e "⚠️ [RunTests] No tests found to run — skipping" - exit 0 - fi - - for test_file in "${tests_to_run[@]}"; do - run_test_file "$test_file" || has_failures=1 - done - - if [[ $has_failures -eq 1 ]]; then - echo -e "❌ One or more tests failed" - exit 1 - else - echo -e "✅ All tests passed successfully" - exit 0 - fi -} - -main "$@" diff --git a/php/laravel/starting_test_from_docker.sh b/php/laravel/starting_test_from_docker.sh deleted file mode 100644 index 4cc53ce..0000000 --- a/php/laravel/starting_test_from_docker.sh +++ /dev/null @@ -1,263 +0,0 @@ -#!/bin/bash - -set -e - -COMPOSE_FILE="../docker-compose.yml" -SERVICE_NAME="app" -PROJECT_PATH="/var/www" - -# ----------------------------- -# CONFIG — list of exclusion patterns -# ----------------------------- -EXCLUDE_PATTERNS=( - "*Test" "*Search" "*Controller*" "*Console*" "*Jobs*" - "*Models*" "*Resources*" "*Requests*" "*DTO*" "*Dtos*" - "*Kernel*" "*Middleware*" "*config*" "*ValueObject*" - "*Enum*" "*Exception*" "*Migration*" "*Seeder*" - "*MockDto*" "*api*" "*Providers*" "*Abstract*" -) - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# ----------------------------- -# Output helper functions -# ----------------------------- -info() { - echo -e "${BLUE}ℹ️ $1${NC}" -} - -success() { - echo -e "${GREEN}✅ $1${NC}" -} - -warning() { - echo -e "${YELLOW}⚠️ $1${NC}" -} - -error() { - echo -e "${RED}❌ $1${NC}" -} - -# ----------------------------- -# Find the project root (where composer.json is located) -# ----------------------------- -find_project_root() { - local current_dir="$PWD" - - while [[ "$current_dir" != "/" ]]; do - if [[ -f "$current_dir/composer.json" ]]; then - echo "$current_dir" - return 0 - fi - current_dir=$(dirname "$current_dir") - done - - error "Laravel project root not found (composer.json missing)" - exit 1 -} - -# ----------------------------- -# Convert file path to PHP class name -# ----------------------------- -path_to_classname() { - local path="$1" - - # Remove .php extension - path="${path%.php}" - - # Remove 'app/' prefix if exists - path="${path#app/}" - - # Replace / with \ - local classname="${path//\//\\}" - echo "$classname" -} - -# ----------------------------- -# Check if a class should be tested -# ----------------------------- -should_be_tested() { - local classname="$1" - - # Skip classes matching exclusion patterns - for pattern in "${EXCLUDE_PATTERNS[@]}"; do - if [[ "$classname" == $pattern ]]; then - return 1 - fi - done - - return 0 -} - -# ----------------------------- -# Get expected test class name -# ----------------------------- -get_expected_test_classname() { - local classname="$1" - echo "Tests\\Unit\\${classname}Test" -} - -# ----------------------------- -# Extract class name from a PHP file -# ----------------------------- -extract_classname_from_file() { - local file="$1" - - if [[ ! -f "$file" ]]; then - return 1 - fi - - local namespace="" - local classname="" - - # Extract namespace - namespace=$(grep -m1 "^namespace " "$file" | sed 's/namespace \(.*\);/\1/' | tr -d ' ') - - # Extract class name - classname=$(grep -m1 "^class " "$file" | sed 's/class \([a-zA-Z0-9_]*\).*/\1/') - - if [[ -n "$namespace" && -n "$classname" ]]; then - echo "${namespace}\\${classname}" - fi -} - -# ----------------------------- -# Find test file path for a given test class -# ----------------------------- -find_test_class_path() { - local test_classname="$1" - local project_root="$2" - - # Convert class name to file path - local test_path="${test_classname//\\//}.php" - - echo $test_path - - local full_path="$project_root/tests/${test_path#*Tests/}" - - if [[ -f "$full_path" ]]; then - return 0 - fi - return 1 -} - -# ----------------------------- -# Run test for a specific class via Docker Compose -# ----------------------------- -run_test_for_class() { - local test_classname="$1" - local project_root="$2" - - local test_file=$(find_test_class_path "$test_classname" "$project_root") - - if [[ -z "$test_file" ]]; then - error "Test file not found for: $test_classname" - return 1 - fi - - local classname=$(basename "$test_file" .php) - - info "Running test: $test_classname" - info "File: $classname" - - # Run via Docker Compose - docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" sh -c "cd $PROJECT_PATH && php artisan test --filter='$classname'" - - if [[ $? -eq 0 ]]; then - success "Test passed: $test_classname" - return 0 - else - error "Test failed: $test_classname" - return 1 - fi -} - -# ----------------------------- -# Analyze file and run the corresponding test -# ----------------------------- -analyze_and_run_tests() { - local app_file="$1" - local project_root="$2" - - # Convert file path to class name - local normalized_classname=$(path_to_classname "$app_file") - - # Skip if class matches exclusion patterns - if ! should_be_tested "$normalized_classname"; then - warning "Class does not require testing: $normalized_classname" - echo "---" - return 0 - fi - - # Get expected test class - local expected_test=$(get_expected_test_classname "$normalized_classname") - - # Run the test - if run_test_for_class "$expected_test" "$project_root"; then - echo "---" - return 0 - else - echo "---" - return 1 - fi -} - -# ----------------------------- -# Main function -# ----------------------------- -main() { - local COMMAND="$1" - - if [ "$COMMAND" = "commit" ]; then - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - elif [ "$COMMAND" = "push" ]; then - BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) - if [ -z "$BRANCH" ] || [ "$BRANCH" = "HEAD" ]; then - error "Unable to determine current branch" - exit 1 - fi - # Check if origin/branch exists - if ! git ls-remote --exit-code origin "$BRANCH" >/dev/null 2>&1; then - warning "origin/$BRANCH does not exist — testing staged files" - ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - else - ALL_FILES=$(git diff --name-only origin/"$BRANCH" --diff-filter=ACM | grep '\.php$' || true) - fi - else - error "Unknown command: $COMMAND (expected 'commit' or 'push')" - exit 1 - fi - - if [ -z "$ALL_FILES" ]; then - success "[RunTests] No PHP files to test!" - exit 0 - fi - - local project_root=$(find_project_root) - local has_failures=0 - - while IFS= read -r app_file; do - if [[ -z "$app_file" ]]; then - continue - fi - - if ! analyze_and_run_tests "$app_file" "$project_root"; then - has_failures=1 - fi - done <<< "$ALL_FILES" - - if [ "$has_failures" -eq 1 ]; then - error "❗ One or more tests failed or are missing." - exit 1 - else - success "🎉 All tests for modified classes passed successfully!" - exit 0 - fi -} - -main "$@" diff --git a/shell/preparations/prepare-commit-description.sh b/shell/preparations/prepare-commit-description.sh deleted file mode 100644 index dbcc9f0..0000000 --- a/shell/preparations/prepare-commit-description.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -# Добавляем список файлов в тело коммита - -COMMIT_MSG_FILE=".git/COMMIT_EDITMSG" - -# Не модифицируем merge commit -if grep -qE '^Merge' "$COMMIT_MSG_FILE"; then - exit 0 -fi - -# Получаем staged изменения -CHANGES=$(git diff --cached --name-status) -[ -z "$CHANGES" ] && exit 0 - -{ - echo "" - echo "------------------------------" - echo "Изменённые файлы:" - echo "$CHANGES" | while read -r STATUS FILE; do - case "$STATUS" in - A) echo "Добавлен: $FILE" ;; - M) echo "Изменён: $FILE" ;; - D) echo "Удалён: $FILE" ;; - R) echo "Переименован: $FILE" ;; - *) echo "$STATUS: $FILE" ;; - esac - done -} >> "$COMMIT_MSG_FILE" diff --git a/shell/ssh_start_shellcheck.sh b/shell/ssh_start_shellcheck.sh deleted file mode 100644 index f8b07c7..0000000 --- a/shell/ssh_start_shellcheck.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -e - -# ----------------------------------------- -# SSH settings -# ----------------------------------------- -SERVER_USER="root" -SERVER_HOST="12.34.56.789" -REMOTE_SCRIPT="/home/project/scripts/check_scripts/check_shellcheck.sh" -# ----------------------------------------- - -# Connect to server and run the remote script -ssh "${SERVER_USER}@${SERVER_HOST}" "$REMOTE_SCRIPT" -exit_code=$? - -if [[ $exit_code -eq 0 ]]; then - echo -e "\033[0;32m✅ ShellCheck passed!\033[0m" - exit 0 -else - echo -e "\033[0;31m❌ ShellCheck found important issues. Commit blocked!\033[0m" - exit 1 -fi From 113eccd336e28870a84c50b18da764e50eacf11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:16:31 +0300 Subject: [PATCH 05/15] fixed check_phpstan.sh --- php/check_phpstan.sh | 121 ++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 77 deletions(-) mode change 100644 => 100755 php/check_phpstan.sh diff --git a/php/check_phpstan.sh b/php/check_phpstan.sh old mode 100644 new mode 100755 index f6371cb..6162702 --- a/php/check_phpstan.sh +++ b/php/check_phpstan.sh @@ -1,124 +1,91 @@ #!/bin/bash - +# ------------------------------------------------------------------------------ +# Runs PHPStan analysis on PHP files with progressive error reduction. +# Accepts strictness mode ("strict" or default) and list of files as arguments. +# Tracks error counts per file in .phpstan-error-count.json baseline. +# In default mode: allows commit if errors decreased by at least 1. +# In strict mode: requires zero errors. Fails if error threshold exceeded. +# ------------------------------------------------------------------------------ + +# ----------------------------- +# PARAMETERS +# ----------------------------- STRICTNESS="$1" shift 1 FILES=("$@") BASELINE_FILE=".phpstan-error-count.json" -LOCK_FILE=".phpstan-error-count.lock" BLOCK_COMMIT=0 -BASELINE_UPDATED=0 - -lock_baseline() { - local timeout=30 - local elapsed=0 - while [ -f "$LOCK_FILE" ] && [ $elapsed -lt $timeout ]; do - sleep 0.1 - elapsed=$((elapsed + 1)) - done - - if [ $elapsed -ge $timeout ]; then - echo "ERROR: Could not acquire lock after ${timeout}s" - exit 1 - fi - - touch "$LOCK_FILE" -} -unlock_baseline() { - rm -f "$LOCK_FILE" -} - -trap unlock_baseline EXIT - -# Init baseline -if [ ! -f "$BASELINE_FILE" ] || [ ! -s "$BASELINE_FILE" ]; then - echo '{}' > "$BASELINE_FILE" -fi - -if ! jq empty "$BASELINE_FILE" 2>/dev/null; then - echo '{}' > "$BASELINE_FILE" +# Initialize baseline if missing +if [ ! -f "$BASELINE_FILE" ]; then + echo "{}" > "$BASELINE_FILE" fi +# ----------------------------- +# CHECK IF FILES EXIST +# ----------------------------- if [ ${#FILES[@]} -eq 0 ]; then echo "[PHPStan] No PHP files to check." exit 0 fi +echo "[PHPStan] Checking ${#FILES[@]} files (strictness=$STRICTNESS)" + +# ----------------------------- +# LOOP THROUGH FILES +# ----------------------------- for FILE in "${FILES[@]}"; do + # Skip if file does not exist (safety check) if [ ! -f "$FILE" ]; then + echo "File not found, skipping: $FILE" continue fi echo "Checking: $FILE" + # Count current errors ERR_NEW=$(vendor/bin/phpstan analyse --error-format=raw --no-progress "$FILE" 2>/dev/null | grep -c '^') + ERR_OLD=$(jq -r --arg file "$FILE" '.[$file] // empty' "$BASELINE_FILE") - lock_baseline - ERR_OLD=$(jq -r --arg file "$FILE" '.[$file] // empty' "$BASELINE_FILE" 2>/dev/null) - unlock_baseline - - IS_NEW_FILE=0 if [ -z "$ERR_OLD" ]; then - IS_NEW_FILE=1 + echo "File not checked before. It has $ERR_NEW errors." ERR_OLD=$ERR_NEW fi + # Determine target errors if [ "$STRICTNESS" = "strict" ]; then TARGET=0 else - # Для следующего коммита требуем хотя бы на 1 ошибку меньше TARGET=$((ERR_OLD - 1)) [ "$TARGET" -lt 0 ] && TARGET=0 fi - SHOULD_UPDATE=0 - - if [ "$IS_NEW_FILE" -eq 1 ] && [ "$ERR_NEW" -gt 0 ]; then - echo " New file: $ERR_NEW errors found" - vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE" - echo " → Baseline set to $ERR_NEW errors. Fix at least 1 error before next commit." - SHOULD_UPDATE=1 - BLOCK_COMMIT=1 - elif [ "$ERR_NEW" -le "$TARGET" ]; then - echo " ✓ Progress: $ERR_OLD → $ERR_NEW errors" - SHOULD_UPDATE=1 + # Compare and report + if [ "$ERR_NEW" -le "$TARGET" ]; then + echo "OK: was $ERR_OLD, now $ERR_NEW" + # Update baseline + jq --arg file "$FILE" --argjson errors "$ERR_NEW" '.[$file] = $errors' "$BASELINE_FILE" \ + > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" else - if [ "$ERR_NEW" -eq "$ERR_OLD" ]; then - echo " ✗ No progress: still $ERR_NEW errors (need ≤ $TARGET)" - else - echo " ✗ Errors increased: $ERR_OLD → $ERR_NEW (need ≤ $TARGET)" - fi + echo "Too many errors: $ERR_NEW (must be <= $TARGET)" vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE" + # Keep old baseline + jq --arg file "$FILE" --argjson errors "$ERR_OLD" '.[$file] = $errors' "$BASELINE_FILE" \ + > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" BLOCK_COMMIT=1 - # НЕ обновляем baseline - fi - - if [ "$SHOULD_UPDATE" -eq 1 ]; then - lock_baseline - - if jq --arg file "$FILE" --argjson errors "$ERR_NEW" \ - '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" 2>/dev/null; then - mv "$BASELINE_FILE.tmp" "$BASELINE_FILE" - BASELINE_UPDATED=1 - else - rm -f "$BASELINE_FILE.tmp" - fi - - unlock_baseline fi - echo "" + echo "------------------" done -if [ "$BASELINE_UPDATED" -eq 1 ]; then - git add "$BASELINE_FILE" 2>/dev/null -fi - +# ----------------------------- +# BLOCK COMMIT IF NEEDED +# ----------------------------- if [ "$BLOCK_COMMIT" -eq 1 ]; then - echo "Commit blocked. Reduce error count to proceed!" + echo "Commit blocked. Reduce the number of errors according to strictness rules." exit 1 fi -echo "[PHPStan] All checks passed!" +echo "[PHPStan] Check completed successfully." exit 0 From 65c17ea27e37dff5b3a5469d15df3989d8dd071a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:16:44 +0300 Subject: [PATCH 06/15] fixed check_pint.sh --- php/laravel/check_pint.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/php/laravel/check_pint.sh b/php/laravel/check_pint.sh index 2460b87..4ac689a 100644 --- a/php/laravel/check_pint.sh +++ b/php/laravel/check_pint.sh @@ -1,4 +1,10 @@ #!/bin/bash +# ------------------------------------------------------------------------------ +# Runs Laravel Pint code style checker on PHP files. +# Accepts list of file paths as arguments, filters only .php files. +# Auto-fixes style issues and stages corrected files for commit. +# Always exits with 0 (non-blocking hook). +# ------------------------------------------------------------------------------ if [ $# -eq 0 ]; then echo "[Pint] No PHP files to check." From 3696eb81fbf93bf98f84a27c6eb0a29ce4b3cf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:17:04 +0300 Subject: [PATCH 07/15] fixed check_shellcheck.sh --- scripts/check_shellcheck.sh | 55 +++++++++++++++++++++++++ shell/check_shellcheck.sh | 82 +++++++++++++++++++------------------ 2 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 scripts/check_shellcheck.sh diff --git a/scripts/check_shellcheck.sh b/scripts/check_shellcheck.sh new file mode 100644 index 0000000..f617d96 --- /dev/null +++ b/scripts/check_shellcheck.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs ShellCheck locally on shell scripts provided as arguments. +# Reports warnings and errors using ShellCheck severity level "warning". +# Fails if any script contains issues. +# ------------------------------------------------------------------------------ + +set -e + +EXCLUDED_RULES=("SC2053") +EXCLUDE_STRING="" +if [[ ${#EXCLUDED_RULES[@]} -gt 0 ]]; then + EXCLUDE_STRING=$(IFS=,; echo "${EXCLUDED_RULES[*]}") +fi + +ERROR_FOUND=0 + +check_file() { + local FILE="$1" + + if [[ ! -f "$FILE" ]]; then + echo "File $FILE not found. Skipping." + return + fi + + if [[ "${FILE##*.}" != "sh" ]]; then + return + fi + + echo "Checking $FILE..." + + if [[ -n "$EXCLUDE_STRING" ]]; then + shellcheck --severity=warning --exclude="$EXCLUDE_STRING" "$FILE" || ERROR_FOUND=1 + else + shellcheck --severity=warning "$FILE" || ERROR_FOUND=1 + fi +} + +# Если файлов нет, просто выйти +if [[ $# -eq 0 ]]; then + echo "No files to check." + exit 0 +fi + +for FILE in "$@"; do + check_file "$FILE" +done + +if [[ $ERROR_FOUND -eq 0 ]]; then + echo -e "\nAll shell scripts passed ShellCheck!" +else + echo -e "\nShellCheck found issues!" +fi + +exit $ERROR_FOUND diff --git a/shell/check_shellcheck.sh b/shell/check_shellcheck.sh index eae9520..5729bb4 100644 --- a/shell/check_shellcheck.sh +++ b/shell/check_shellcheck.sh @@ -1,69 +1,73 @@ #!/bin/bash +# ------------------------------------------------------------------------------ +# Runs ShellCheck on shell scripts in configured directories via Docker. +# Scans predefined directories for .sh files and validates them. +# Reports warnings and errors using ShellCheck severity level "warning". +# Fails if any script contains issues. +# ------------------------------------------------------------------------------ + set -e # ----------------------------------------- # Configuration # ----------------------------------------- DOCKER_SERVICE="app" -PROJECT_DIR="/home/project" +PROJECT_DIR="/home/multichat" -# Directories to check (relative to PROJECT_DIR) -DIRS=( - "scripts" - # "docker/scripts" # add more if needed +# ShellCheck exclusions +EXCLUDED_RULES=( + "SC2053" ) # ----------------------------------------- -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' +ALL_FILES=("$@") + +# ----------------------------------------- +# Run ShellCheck +# ----------------------------------------- +cd "$PROJECT_DIR" || { echo "Cannot cd to $PROJECT_DIR"; exit 1; } -info() { echo -e "${BLUE}ℹ️ $1${NC}"; } -success() { echo -e "${GREEN}✅ $1${NC}"; } -warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } -error() { echo -e "${RED}❌ $1${NC}"; } +# Build exclude string from array +EXCLUDE_STRING="" +if [[ ${#EXCLUDED_RULES[@]} -gt 0 ]]; then + EXCLUDE_STRING=$(IFS=,; echo "${EXCLUDED_RULES[*]}") +fi ERROR_FOUND=0 -cd "$PROJECT_DIR" || { error "Cannot cd to $PROJECT_DIR"; exit 1; } - -for DIR in "${DIRS[@]}"; do - if [ ! -d "$DIR" ]; then - warning "Directory $DIR does not exist. Skipping." +for FILE in "${ALL_FILES[@]}"; do + if [[ ! -f "$FILE" ]]; then + echo "File $FILE not found. Skipping." continue fi - info "Checking directory: $DIR" - - # Find all shell scripts - sh_files=$(find "$DIR" -type f -name "*.sh") - if [ -z "$sh_files" ]; then - info "No .sh files found in $DIR" + if [[ "${FILE##*.}" != "sh" ]]; then continue fi - for file in $sh_files; do - info "Checking $file..." + echo "Checking $FILE..." - # Run ShellCheck inside Docker (warnings and errors only) - output=$(docker compose exec -T "$DOCKER_SERVICE" shellcheck --severity=warning "$file" 2>&1) || rc=$? - if [ -n "$output" ]; then - echo "$output" - fi + if [[ -n "$EXCLUDE_STRING" ]]; then + output=$(docker compose exec -T "$DOCKER_SERVICE" \ + shellcheck --severity=warning --exclude="$EXCLUDE_STRING" "$FILE" 2>&1) || rc=$? + else + output=$(docker compose exec -T "$DOCKER_SERVICE" \ + shellcheck --severity=warning "$FILE" 2>&1) || rc=$? + fi - if [ "${rc:-0}" -ne 0 ]; then - ERROR_FOUND=1 - fi - done + if [[ -n "$output" ]]; then + echo "$output" + fi + + if [[ "${rc:-0}" -ne 0 ]]; then + ERROR_FOUND=1 + fi done if [[ $ERROR_FOUND -eq 0 ]]; then - success "All shell scripts passed important ShellCheck checks!" + echo -e "All shell scripts passed ShellCheck!" else - error "ShellCheck found important issues!" + echo -e "ShellCheck found issues!" fi exit $ERROR_FOUND From 9ba084b2df18ea574a3b4caee082e533df96fb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:17:22 +0300 Subject: [PATCH 08/15] fixed find_test.sh --- php/find_test.sh | 164 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100755 php/find_test.sh diff --git a/php/find_test.sh b/php/find_test.sh new file mode 100755 index 0000000..adaa9db --- /dev/null +++ b/php/find_test.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Finds and validates unit test coverage for PHP classes. +# Checks if each modified PHP file has a corresponding unit test. +# Excludes common non-testable patterns (Controllers, Models, DTOs, etc.). +# Fails if any class is missing its expected test. +# ------------------------------------------------------------------------------ + +set -e + +# ----------------------------- +# CONFIG +# ----------------------------- +EXCLUDE_PATTERNS=( + "*Test" "*Search" "*Controller*" "*Console*" "*Jobs*" + "*Models*" "*Resources*" "*Requests*" "*DTO*" "*Dtos*" + "*Kernel*" "*Middleware*" "*config*" "*ValueObject*" + "*Enum*" "*Exception*" "*Migration*" "*Seeder*" + "*MockDto*" "*api*" "*Providers*" "*Abstract*" + "*Rules*" +) + +# ----------------------------- +# Find project root +# ----------------------------- +find_project_root() { + local current_dir="$PWD" + while [[ "$current_dir" != "/" ]]; do + if [[ -f "$current_dir/composer.json" ]]; then + echo "$current_dir" + return 0 + fi + current_dir=$(dirname "$current_dir") + done + echo -e 'Laravel project root not found (composer.json missing)\n' + exit 1 +} + +# ----------------------------- +# Determine if class should be tested +# ----------------------------- +should_be_tested() { + local classname="$1" + for pattern in "${EXCLUDE_PATTERNS[@]}"; do + if [[ "$classname" == $pattern ]]; then + return 1 + fi + done + return 0 +} + +# ----------------------------- +# Extract class name with namespace from a PHP file +# ----------------------------- +extract_classname_from_file() { + local file="$1" + + if [[ ! -f "$file" && -n "$PROJECT_ROOT" ]]; then + file="$PROJECT_ROOT/$file" + fi + + if [[ ! -f "$file" ]]; then + return 1 + fi + + local namespace + namespace=$(grep -m1 "^namespace " "$file" | sed 's/namespace \(.*\);/\1/' | tr -d ' ') + + local classname + classname=$(grep -m1 "^class " "$file" | sed 's/class \([a-zA-Z0-9_]*\).*/\1/') + + if [[ -n "$classname" ]]; then + if [[ -n "$namespace" ]]; then + echo -e "$namespace\\$classname" + else + echo -e "$classname" + fi + fi +} + +# ----------------------------- +# Find all test classes +# ----------------------------- +find_test_classes() { + local project_root="$1" + find "$project_root/tests" -type f -name "*Test.php" 2>/dev/null | + while IFS= read -r file; do + extract_classname_from_file "$file" + done | sort -u +} + +# ----------------------------- +# Analyze coverage for a single class +# ----------------------------- +analyze_coverage() { + local classname="$1" + shift + local test_classes=("$@") + + [[ ! $(should_be_tested "$classname"; echo $?) -eq 0 ]] && return 0 + + local expected_test="Tests\\Unit\\${classname}Test" + local found=0 + + for test_class in "${test_classes[@]}"; do + test_class="$(echo "$test_class" | tr -d '\r\n')" + if [[ "$test_class" == "$expected_test" ]]; then + found=1 + break + fi + done + + if [[ $found -eq 0 ]]; then + echo -e "No found $expected_test" + return 1 + fi + + return 0 +} + +# ----------------------------- +# Main +# ----------------------------- +main() { + if [[ "$#" -eq 0 ]]; then + echo 'No PHP files changed — skipping' + exit 0 + fi + + PROJECT_ROOT=$(find_project_root) + + TEST_CLASSES=() + while IFS= read -r line; do + [[ -n "$line" ]] && TEST_CLASSES+=("$line") + done < <(find_test_classes "$PROJECT_ROOT") + + HAS_MISSING_TESTS=0 + + for file in "$@"; do + if [[ -z "$file" ]]; then + continue + fi + + if [[ "${file##*.}" != "php" ]]; then + continue + fi + + classname=$(extract_classname_from_file "$file") + if [[ -z "$classname" ]]; then + continue + fi + + analyze_coverage "$classname" "${TEST_CLASSES[@]}" || HAS_MISSING_TESTS=1 + done + + if [[ $HAS_MISSING_TESTS -eq 1 ]]; then + echo -e "Some classes are missing tests! Failing CI." + exit 1 + fi + + exit 0 +} + +main "$@" From 370bdf3b8dbb3f66f23ce9e4b492f4fbc0dc52eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:18:22 +0300 Subject: [PATCH 09/15] fixed start_test_in_docker.sh --- php/start_test_in_docker.sh | 189 ++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 php/start_test_in_docker.sh diff --git a/php/start_test_in_docker.sh b/php/start_test_in_docker.sh new file mode 100644 index 0000000..5301fab --- /dev/null +++ b/php/start_test_in_docker.sh @@ -0,0 +1,189 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Runs PHPUnit tests inside Docker container for modified PHP files. +# Accepts list of PHP file paths as arguments. +# Maps each file to its corresponding Unit test class and executes via artisan. +# Excludes non-testable patterns (Controllers, Models, DTOs, etc.). +# Fails if any test fails or required test class is missing. +# ------------------------------------------------------------------------------ + +set -e + +COMPOSE_FILE="/home/project/docker-compose.yml" +SERVICE_NAME="app" +PROJECT_PATH="/var/www" + +# ----------------------------- +# CONFIG — list of exclusion patterns +# ----------------------------- +EXCLUDE_PATTERNS=( + "*Test" "*Search" "*Controller*" "*Console*" "*Jobs*" + "*Models*" "*Resources*" "*Requests*" "*DTO*" "*Dtos*" + "*Kernel*" "*Middleware*" "*config*" "*ValueObject*" + "*Enum*" "*Exception*" "*Migration*" "*Seeder*" + "*MockDto*" "*api*" "*Providers*" "*Abstract*" + "*Rules*" +) + +# ----------------------------- +# Helpers +# ----------------------------- +find_project_root() { + local current_dir="$PWD" + while [[ "$current_dir" != "/" ]]; do + if [[ -f "$current_dir/composer.json" ]]; then + echo "$current_dir" + return 0 + fi + current_dir=$(dirname "$current_dir") + done + + echo "Laravel project root not found (composer.json missing)" + exit 1 +} + +path_to_classname() { + local path="$1" + path="${path%.php}" + path="${path#app/}" + echo "${path//\//\\}" +} + +should_be_tested() { + classname="$1" + for pattern in "${EXCLUDE_PATTERNS[@]}"; do + if [[ "$classname" == $pattern ]]; then + return 1 + fi + done + return 0 +} + +get_expected_test_classname() { + local classname="$1" + echo "Tests\\Unit\\${classname}Test" +} + +extract_classname_from_file() { + local file="$1" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + # Extract namespace + local namespace + namespace=$(grep -m1 "^namespace " "$file" | sed 's/namespace \(.*\);/\1/' | tr -d ' ') + + # Extract class name + local classname + classname=$(grep -m1 "^class " "$file" | sed 's/class \([a-zA-Z0-9_]*\).*/\1/') + + if [[ -n "$namespace" && -n "$classname" ]]; then + echo "${namespace}\\${classname}" + fi +} + +find_test_class_path() { + local test_classname="$1" + local project_root="$2" + + local test_path="${test_classname//\\//}.php" + local full_path="$project_root/tests/${test_path#Tests/}" # убираем префикс "Tests" + + if [[ -f "$full_path" ]]; then + echo "$full_path" + return 0 + fi + + return 1 +} + +run_test_for_class() { + local test_classname="$1" + local project_root="$2" + + local test_file + test_file=$(find_test_class_path "$test_classname" "$project_root") + + if [[ -z "$test_file" ]]; then + echo "Test file not found for: $test_classname" + return 1 + fi + + local classname + classname=$(basename "$test_file" .php) + + echo "Running test: $test_classname" + echo "File: $classname" + + docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" sh -c "cd $PROJECT_PATH && php artisan test --filter='$classname'" + + if [[ $? -eq 0 ]]; then + echo "Test passed: $test_classname" + return 0 + else + echo "Test failed: $test_classname" + return 1 + fi +} + + +analyze_and_run_tests() { + local app_file="$1" + local project_root="$2" + + # Convert file path to class name + local normalized_classname + normalized_classname=$(path_to_classname "$app_file") + + # Skip if class matches exclusion patterns + if ! should_be_tested "$normalized_classname"; then + echo "Class does not require testing: $normalized_classname" + echo "---" + return 0 + fi + + # Get expected test class + local expected_test + expected_test=$(get_expected_test_classname "$normalized_classname") + + # Run the test + if run_test_for_class "$expected_test" "$project_root"; then + echo "---" + return 0 + else + echo "---" + return 1 + fi +} + +# ----------------------------- +# Main function — принимает список файлов +# ----------------------------- +main() { + local files=("$@") + if [[ ${#files[@]} -eq 0 ]]; then + echo "[RunTests] No PHP files to test!" + exit 0 + fi + + local project_root + project_root=$(find_project_root) + local has_failures=0 + + for app_file in "${files[@]}"; do + [[ -z "$app_file" ]] && continue + ! analyze_and_run_tests "$app_file" "$project_root" && has_failures=1 + done + + if [ "$has_failures" -eq 1 ]; then + echo "❗ One or more tests failed or are missing." + exit 1 + else + echo "🎉 All tests for modified classes passed successfully!" + exit 0 + fi +} + +main "$@" From aaebc57462c2bd02b46299a6b65bb2afb3546947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:18:57 +0300 Subject: [PATCH 10/15] add tests --- .../phpstan/cases/01_no_files_exits_zero.sh | 19 +++++++++++++ .../php/phpstan/cases/02_clean_file_passes.sh | 24 ++++++++++++++++ .../cases/03_progress_allows_commit.sh | 28 +++++++++++++++++++ .../cases/04_no_progress_blocks_commit.sh | 26 +++++++++++++++++ .../cases/05_strict_mode_requires_zero.sh | 23 +++++++++++++++ 5 files changed, 120 insertions(+) create mode 100755 tests/php/phpstan/cases/01_no_files_exits_zero.sh create mode 100755 tests/php/phpstan/cases/02_clean_file_passes.sh create mode 100755 tests/php/phpstan/cases/03_progress_allows_commit.sh create mode 100755 tests/php/phpstan/cases/04_no_progress_blocks_commit.sh create mode 100755 tests/php/phpstan/cases/05_strict_mode_requires_zero.sh diff --git a/tests/php/phpstan/cases/01_no_files_exits_zero.sh b/tests/php/phpstan/cases/01_no_files_exits_zero.sh new file mode 100755 index 0000000..fe36199 --- /dev/null +++ b/tests/php/phpstan/cases/01_no_files_exits_zero.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No files provided exits with 0 +# Expected: exit 0, message about no files +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "phpstan_no_files" + +mock_phpstan 0 + +run_hook "php/check_phpstan.sh" "default" + +assert_exit_code 0 +assert_output_contains "No PHP files to check" + +test_passed diff --git a/tests/php/phpstan/cases/02_clean_file_passes.sh b/tests/php/phpstan/cases/02_clean_file_passes.sh new file mode 100755 index 0000000..b4908fe --- /dev/null +++ b/tests/php/phpstan/cases/02_clean_file_passes.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Clean file with no errors passes +# Expected: exit 0, baseline updated with 0 errors +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "phpstan_clean_file" + +# Create test file +create_php_fixture "app/Clean.php" "CleanClass" "App" + +# Mock phpstan returning 0 errors +mock_phpstan 0 + +run_hook "php/check_phpstan.sh" "default" "app/Clean.php" + +assert_exit_code 0 +assert_output_contains "OK" +assert_baseline_value "app/Clean.php" 0 + +test_passed diff --git a/tests/php/phpstan/cases/03_progress_allows_commit.sh b/tests/php/phpstan/cases/03_progress_allows_commit.sh new file mode 100755 index 0000000..19fb0ae --- /dev/null +++ b/tests/php/phpstan/cases/03_progress_allows_commit.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Reducing errors allows commit (progress mode) +# Expected: exit 0 when errors decreased from baseline +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "phpstan_progress" + +# Create test file +create_php_fixture "app/Service.php" "Service" "App" + +# Set baseline: file had 5 errors before +echo '{"app/Service.php": 5}' > .phpstan-error-count.json + +# Mock phpstan returning 3 errors (reduced from 5) +mock_phpstan 3 + +run_hook "php/check_phpstan.sh" "default" "app/Service.php" + +assert_exit_code 0 +assert_output_contains "OK" +assert_output_contains "was 5, now 3" +assert_baseline_value "app/Service.php" 3 + +test_passed diff --git a/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh b/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh new file mode 100755 index 0000000..2104aca --- /dev/null +++ b/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: No progress blocks commit +# Expected: exit 1 when errors not reduced enough +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "phpstan_no_progress" + +# Create test file +create_php_fixture "app/Service.php" "Service" "App" + +# Set baseline: file had 5 errors before +echo '{"app/Service.php": 5}' > .phpstan-error-count.json + +# Mock phpstan returning 5 errors (same as before - no progress) +mock_phpstan 5 + +run_hook "php/check_phpstan.sh" "default" "app/Service.php" + +assert_exit_code 1 +assert_output_contains "Too many errors" + +test_passed diff --git a/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh b/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh new file mode 100755 index 0000000..6578c6a --- /dev/null +++ b/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Test: Strict mode requires zero errors +# Expected: exit 1 when any errors exist in strict mode +# ------------------------------------------------------------------------------ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/../../../lib/test_helper.bash" + +setup_test_env "phpstan_strict" + +# Create test file +create_php_fixture "app/Service.php" "Service" "App" + +# Mock phpstan returning 1 error +mock_phpstan 1 + +run_hook "php/check_phpstan.sh" "strict" "app/Service.php" + +assert_exit_code 1 +assert_output_contains "Too many errors" + +test_passed From bc3a53f7ae6510274d7c5dea144eb89e05cc86d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:22:21 +0300 Subject: [PATCH 11/15] create tests scripts --- .github/workflows/tests.yml | 53 ++++ .gitignore | 24 ++ {simple => git}/check_branch_name.sh | 7 + tests/lib/test_helper.bash | 275 +++++++++++++++++++++ tests/php/phpstan/fixtures/clean.php | 11 + tests/php/phpstan/fixtures/with_errors.php | 12 + tests/php/phpstan/run.sh | 26 ++ tests/run_all.sh | 52 ++++ 8 files changed, 460 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore rename {simple => git}/check_branch_name.sh (62%) create mode 100755 tests/lib/test_helper.bash create mode 100644 tests/php/phpstan/fixtures/clean.php create mode 100644 tests/php/phpstan/fixtures/with_errors.php create mode 100755 tests/php/phpstan/run.sh create mode 100755 tests/run_all.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..bf7870e --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,53 @@ +name: Git Hooks Tests + +on: + pull_request: + push: + branches: [ main ] + +jobs: + shellcheck: + name: ShellCheck Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run ShellCheck + run: | + find . -name "*.sh" -not -path "./vendor/*" -exec shellcheck {} + + + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: shellcheck + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer + + - name: Install dependencies + run: | + composer init -n + composer require --dev phpstan/phpstan + + - name: Prepare test environment + run: | + chmod +x php/*.sh + chmod +x php/laravel/*.sh 2>/dev/null || true + chmod +x shell/*.sh 2>/dev/null || true + chmod +x docker/*.sh 2>/dev/null || true + chmod +x simple/*.sh 2>/dev/null || true + chmod +x tests/run_all.sh + chmod +x tests/lib/*.bash + find tests -name "*.sh" -exec chmod +x {} + + + - name: Run all tests + run: ./tests/run_all.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..015da1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Dependencies +vendor/ +composer.lock + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# PHPStan baseline (project-specific, not for this repo) +.phpstan-error-count.json + +# Test artifacts +tests/**/tmp/ +*.tmp + +# Logs +*.log diff --git a/simple/check_branch_name.sh b/git/check_branch_name.sh similarity index 62% rename from simple/check_branch_name.sh rename to git/check_branch_name.sh index f244d52..9d24872 100644 --- a/simple/check_branch_name.sh +++ b/git/check_branch_name.sh @@ -1,4 +1,11 @@ #!/bin/bash +# ------------------------------------------------------------------------------ +# Validates current git branch name against naming convention. +# Expected format: {type}/{task-id}_{description} +# Allowed types: feature, bugfix, hotfix, release. +# Example: feature/dev-2212_order_service +# Fails if branch name does not match the required pattern. +# ------------------------------------------------------------------------------ # Получаем имя текущей ветки BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) diff --git a/tests/lib/test_helper.bash b/tests/lib/test_helper.bash new file mode 100755 index 0000000..186cd52 --- /dev/null +++ b/tests/lib/test_helper.bash @@ -0,0 +1,275 @@ +#!/bin/bash +# ------------------------------------------------------------------------------ +# Common test helper functions for hook testing. +# Provides environment setup, mocking, assertions, and cleanup. +# Source this file at the beginning of each test case. +# ------------------------------------------------------------------------------ + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Test state +TEST_DIR="" +TEST_NAME="" +LAST_EXIT_CODE=0 +LAST_OUTPUT="" + +# ----------------------------------------- +# Setup / Cleanup +# ----------------------------------------- + +setup_test_env() { + TEST_NAME="${1:-$(basename "$0" .sh)}" + TEST_DIR=$(mktemp -d) + + # Create basic structure + mkdir -p "$TEST_DIR"/{vendor/bin,app,.git} + + # Initialize git repo + git -C "$TEST_DIR" init -q + git -C "$TEST_DIR" config user.email "test@test.com" + git -C "$TEST_DIR" config user.name "Test" + + # Create composer.json + echo '{"name": "test/test"}' > "$TEST_DIR/composer.json" + + cd "$TEST_DIR" + + echo -e "${YELLOW}[TEST]${NC} $TEST_NAME" +} + +cleanup_test_env() { + if [[ -n "$TEST_DIR" && -d "$TEST_DIR" ]]; then + rm -rf "$TEST_DIR" + fi +} + +# Auto cleanup on exit +trap cleanup_test_env EXIT + +# ----------------------------------------- +# Fixtures +# ----------------------------------------- + +create_fixture() { + local path="$1" + local content="$2" + + mkdir -p "$(dirname "$path")" + echo "$content" > "$path" +} + +create_php_fixture() { + local path="$1" + local class_name="$2" + local namespace="${3:-App}" + + mkdir -p "$(dirname "$path")" + cat > "$path" << EOF + "$TEST_DIR/vendor/bin/phpstan" << EOF +#!/bin/bash +if [[ "\$*" == *"--error-format=raw"* ]]; then + count=$error_count + if [[ \$count -gt 0 ]]; then + for i in \$(seq 1 \$count); do + echo "Error \$i" + done + fi +elif [[ "\$*" == *"--error-format=table"* ]]; then + echo "$error_output" +fi +exit 0 +EOF + chmod +x "$TEST_DIR/vendor/bin/phpstan" +} + +mock_pint() { + local has_errors="$1" # 0 = no errors, 1 = has errors + + cat > "$TEST_DIR/vendor/bin/pint" << EOF +#!/bin/bash +if [[ "\$*" == *"--test"* ]]; then + exit $has_errors +else + exit 0 +fi +EOF + chmod +x "$TEST_DIR/vendor/bin/pint" +} + +mock_git_branch() { + local branch_name="$1" + + git -C "$TEST_DIR" checkout -q -b "$branch_name" 2>/dev/null || \ + git -C "$TEST_DIR" checkout -q "$branch_name" 2>/dev/null || true +} + +mock_docker_compose() { + mkdir -p "$TEST_DIR/bin" + cat > "$TEST_DIR/bin/docker" << 'EOF' +#!/bin/bash +# Mock docker - just pass through to local command or return success +if [[ "$1" == "compose" ]]; then + shift 2 # skip "compose exec" + shift 2 # skip "-T service" + eval "$@" +else + exit 0 +fi +EOF + chmod +x "$TEST_DIR/bin/docker" + export PATH="$TEST_DIR/bin:$PATH" +} + +# ----------------------------------------- +# Hook Runner +# ----------------------------------------- + +run_hook() { + local hook_path="$1" + shift + local args=("$@") + + # Get absolute path to hook + local script_dir + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + local full_hook_path="$script_dir/$hook_path" + + if [[ ! -f "$full_hook_path" ]]; then + echo -e "${RED}[ERROR]${NC} Hook not found: $full_hook_path" + return 1 + fi + + # Run hook and capture output + set +e + LAST_OUTPUT=$("$full_hook_path" "${args[@]}" 2>&1) + LAST_EXIT_CODE=$? + set -e +} + +# ----------------------------------------- +# Assertions +# ----------------------------------------- + +assert_exit_code() { + local expected="$1" + + if [[ "$LAST_EXIT_CODE" -eq "$expected" ]]; then + echo -e " ${GREEN}✓${NC} Exit code is $expected" + return 0 + else + echo -e " ${RED}✗${NC} Expected exit code $expected, got $LAST_EXIT_CODE" + echo -e " ${RED}Output:${NC} $LAST_OUTPUT" + exit 1 + fi +} + +assert_output_contains() { + local expected="$1" + + if [[ "$LAST_OUTPUT" == *"$expected"* ]]; then + echo -e " ${GREEN}✓${NC} Output contains: $expected" + return 0 + else + echo -e " ${RED}✗${NC} Output should contain: $expected" + echo -e " ${RED}Actual:${NC} $LAST_OUTPUT" + exit 1 + fi +} + +assert_output_not_contains() { + local unexpected="$1" + + if [[ "$LAST_OUTPUT" != *"$unexpected"* ]]; then + echo -e " ${GREEN}✓${NC} Output does not contain: $unexpected" + return 0 + else + echo -e " ${RED}✗${NC} Output should not contain: $unexpected" + echo -e " ${RED}Actual:${NC} $LAST_OUTPUT" + exit 1 + fi +} + +assert_file_exists() { + local path="$1" + + if [[ -f "$path" ]]; then + echo -e " ${GREEN}✓${NC} File exists: $path" + return 0 + else + echo -e " ${RED}✗${NC} File should exist: $path" + exit 1 + fi +} + +assert_file_contains() { + local path="$1" + local expected="$2" + + if [[ -f "$path" ]] && grep -q "$expected" "$path"; then + echo -e " ${GREEN}✓${NC} File $path contains: $expected" + return 0 + else + echo -e " ${RED}✗${NC} File $path should contain: $expected" + exit 1 + fi +} + +assert_baseline_value() { + local file="$1" + local expected_count="$2" + local baseline_file=".phpstan-error-count.json" + + if [[ ! -f "$baseline_file" ]]; then + echo -e " ${RED}✗${NC} Baseline file not found" + exit 1 + fi + + local actual + actual=$(jq -r --arg f "$file" '.[$f] // empty' "$baseline_file") + + if [[ "$actual" == "$expected_count" ]]; then + echo -e " ${GREEN}✓${NC} Baseline[$file] = $expected_count" + return 0 + else + echo -e " ${RED}✗${NC} Baseline[$file] expected $expected_count, got $actual" + exit 1 + fi +} + +# ----------------------------------------- +# Test Result +# ----------------------------------------- + +test_passed() { + echo -e "${GREEN}[PASS]${NC} $TEST_NAME" +} + +test_failed() { + local message="$1" + echo -e "${RED}[FAIL]${NC} $TEST_NAME: $message" + exit 1 +} diff --git a/tests/php/phpstan/fixtures/clean.php b/tests/php/phpstan/fixtures/clean.php new file mode 100644 index 0000000..a76de72 --- /dev/null +++ b/tests/php/phpstan/fixtures/clean.php @@ -0,0 +1,11 @@ + Date: Sun, 1 Feb 2026 20:22:37 +0300 Subject: [PATCH 12/15] create pre-commit tests --- .../prepare-commit-description.sh | 32 +++++++++++++++++++ scripts/pre-commit-check.sh | 23 +++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 git/preparations/prepare-commit-description.sh create mode 100644 scripts/pre-commit-check.sh diff --git a/git/preparations/prepare-commit-description.sh b/git/preparations/prepare-commit-description.sh new file mode 100644 index 0000000..29c9dcb --- /dev/null +++ b/git/preparations/prepare-commit-description.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# ------------------------------------------------------------------------------ +# Appends list of staged files to commit message body. +# Shows file status (Added, Modified, Deleted, Renamed) for each file. +# Skips merge commits to avoid modifying auto-generated messages. +# ------------------------------------------------------------------------------ + +COMMIT_MSG_FILE=".git/COMMIT_EDITMSG" + +# Skip merge commits +if grep -qE '^Merge' "$COMMIT_MSG_FILE"; then + exit 0 +fi + +# Get staged changes +CHANGES=$(git diff --cached --name-status) +[ -z "$CHANGES" ] && exit 0 + +{ + echo "" + echo "------------------------------" + echo "Changed files:" + echo "$CHANGES" | while read -r STATUS FILE; do + case "$STATUS" in + A) echo "Added: $FILE" ;; + M) echo "Modified: $FILE" ;; + D) echo "Deleted: $FILE" ;; + R) echo "Renamed: $FILE" ;; + *) echo "$STATUS: $FILE" ;; + esac + done +} >> "$COMMIT_MSG_FILE" diff --git a/scripts/pre-commit-check.sh b/scripts/pre-commit-check.sh new file mode 100644 index 0000000..0ebe105 --- /dev/null +++ b/scripts/pre-commit-check.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +ALL_FILE_ARRAY=() +while IFS= read -r line; do + ALL_FILE_ARRAY+=("$line") +done < <(git diff --cached --name-only --diff-filter=ACM || true) + +NEW_FILE_ARRAY=() +while IFS= read -r line; do + NEW_FILE_ARRAY+=("$line") +done < <(git diff --cached --name-only --diff-filter=A || true) + +#echo "ALL_FILE_ARRAY по индексам:" +#for i in "${!ALL_FILE_ARRAY[@]}"; do +# echo "[$i] = '${ALL_FILE_ARRAY[$i]}'" +#done + +echo "Checking shell scripts with ShellCheck..." +bash scripts/check_shellcheck.sh "${ALL_FILE_ARRAY[@]}" +echo "----------" + From f679d2195d58cdd6fa04d888f2614e85e356b922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:26:20 +0300 Subject: [PATCH 13/15] fixed readme files --- CLAUDE.md | 91 ++++++++++++++++++++++++++++++++++++ README.md | 119 ++++++++++++++++++++++++++++++++++++++++-------- tests/README.md | 51 +++++++++++++++++++++ 3 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 CLAUDE.md create mode 100644 tests/README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b0f9d16 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# CLAUDE.md + +## Project Overview + +A collection of reusable git hook scripts for automating code quality checks. Main focus: PHP/Laravel (PHPStan, Pint), shell script validation (ShellCheck), Docker linting, and commit message enhancement. + +## Directory Structure + +``` +docker/ # Docker-related hooks +├── check_hadolint.sh # Dockerfile linter + +git/ # Git workflow hooks +├── check_branch_name.sh # Branch naming convention validator +└── preparations/ # Commit message enhancers + ├── add_task_id_in_commit.sh + └── prepare-commit-description.sh + +php/ # PHP/Laravel hooks +├── check_phpstan.sh # PHPStan with progressive error reduction +├── find_test.sh # Unit test coverage validator +├── start_test_in_docker.sh # Run PHPUnit tests in Docker +└── laravel/ + └── check_pint.sh # Laravel Pint code style fixer + +shell/ # Shell script validation +└── check_shellcheck.sh # ShellCheck via Docker + +scripts/ # Local utility scripts +├── check_shellcheck.sh # ShellCheck (local execution) +└── pre-commit-check.sh # Pre-commit orchestrator + +tests/ # Test framework +├── run_all.sh # Run all test suites +├── lib/test_helper.bash # Test utilities and assertions +└── php/phpstan/ # PHPStan hook tests +``` + +## Script Conventions + +### Input Format +All scripts receive files to check as a list of arguments: +```bash +./php/check_phpstan.sh default file1.php file2.php +``` + +### Script Header +Every script must start with a description block after the shebang: + +```bash +#!/bin/bash +# ------------------------------------------------------------------------------ +# Brief description of what the script does. +# Input format and expected arguments. +# Exit conditions (success/failure criteria). +# ------------------------------------------------------------------------------ +``` + +**Requirements:** +- Language: English +- Format: comments inside `# ---...---` delimiters +- Content: 3-5 lines describing: + - What the script does (main action) + - Input format (files/arguments) + - Success/failure conditions (exit codes) + +## Dependencies + +- Bash 4.0+, Git, jq +- PHP 8.1+, Composer (for PHPStan/Pint) +- Docker (optional, for containerized checks) +- ShellCheck (for shell validation) + +## Testing + +Run all tests: +```bash +./tests/run_all.sh +``` + +Run specific test suite: +```bash +./tests/php/phpstan/run.sh +``` + +## CI/CD + +GitHub Actions (`.github/workflows/tests.yml`): +- ShellCheck linting on all scripts +- Unit tests execution +- Triggered on PR and push to main diff --git a/README.md b/README.md index 46f200a..8a890ee 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,117 @@ -# Dev Scripts Git Hooks +# Git Hooks Collection -**Коллекция полезных скриптов для разработчиков** -Автоматизация рутинных задач, git-хуки, bash-утилиты и другие мини-инструменты, которые ускоряют и упрощают ежедневную работу программиста. +A collection of reusable git hook scripts for automating code quality checks and developer workflows. -## Простые проверки +## Features -**./shell/preparations/add_task_id_in_commit.sh** +- **PHP/Laravel**: PHPStan analysis with progressive error reduction, Pint code style fixing, test coverage validation +- **Docker**: Dockerfile linting with Hadolint +- **Shell**: Script validation with ShellCheck +- **Git**: Branch naming conventions, automatic task ID injection in commits -Автоматически добавляет ID задачи из названия ветки в начало коммита. -Пример: ветка `feat/dev-123_filter` → коммит `dev-123 | добавил фильтр по типу`. +## Quick Start -**./shell/simple/check_branch_name.sh** +1. Clone the repository +2. Copy desired scripts to your project's `.git/hooks/` directory +3. Make scripts executable: `chmod +x .git/hooks/*` -Пример shell-скрипта, который проверяет, что текущая ветка Git соответствует заданной структуре "^(feature|bugfix|hotfix|release)\/[a-zA-Z0-9]+_[a-zA-Z0-9_-]+$". +## Available Scripts -## Docker compose +### PHP -**./shell/docker/check_docker.sh** +| Script | Description | +|--------|-------------| +| `php/check_phpstan.sh` | Runs PHPStan with progressive error reduction. Tracks error count per file and allows commits only when errors decrease. | +| `php/laravel/check_pint.sh` | Runs Laravel Pint, auto-fixes code style issues, and stages corrected files. | +| `php/find_test.sh` | Validates that modified PHP classes have corresponding unit tests. | +| `php/start_test_in_docker.sh` | Runs PHPUnit tests inside Docker for modified files. | -Проверка работы docker compose сборки (отключение, запуск, работа контейнеров). +### Git Workflow -## Laravel +| Script | Description | +|--------|-------------| +| `git/check_branch_name.sh` | Validates branch names match pattern: `{type}/{task-id}_{description}` | +| `git/preparations/add_task_id_in_commit.sh` | Prepends task ID from branch name to commit message. | +| `git/preparations/prepare-commit-description.sh` | Appends list of changed files to commit message. | -**./php/laravel/check_phpstan.sh** +### Docker -Проверка добавленных или изменённых файлов, через статический анализатор PHPStan. +| Script | Description | +|--------|-------------| +| `docker/check_hadolint.sh` | Lints Dockerfiles using Hadolint. | -**./php/laravel/check_pint.sh** +### Shell -Проверка и автоисправление code style добавленных или изменённых файлов, через модуль Pint. +| Script | Description | +|--------|-------------| +| `shell/check_shellcheck.sh` | Validates shell scripts using ShellCheck (via Docker). | +| `scripts/check_shellcheck.sh` | Validates shell scripts using ShellCheck (local). | -**./php/laravel/find_tests.sh** +## Usage Examples -Проверяет созданы ли тесты для классов, которые были добавлены в git индекс. +### PHPStan with Progressive Error Reduction +```bash +# Default mode: requires error count to decrease by at least 1 +./php/check_phpstan.sh default src/Service.php src/Helper.php + +# Strict mode: requires zero errors +./php/check_phpstan.sh strict src/Service.php +``` + +### Branch Name Validation + +```bash +# In pre-commit hook +./git/check_branch_name.sh +# Valid: feature/dev-123_user_auth, bugfix/issue-456_fix_login +# Invalid: main, my-branch, feature/no_task_id +``` + +### Task ID in Commits + +```bash +# In prepare-commit-msg hook +./git/preparations/add_task_id_in_commit.sh +# Branch: feature/dev-123_new_feature +# Commit: "add validation" → "dev-123 | add validation" +``` + +## Integration + +### Pre-commit Hook Example + +```bash +#!/bin/bash +# .git/hooks/pre-commit + +FILES=$(git diff --cached --name-only --diff-filter=ACM) +PHP_FILES=$(echo "$FILES" | grep '\.php$') +SH_FILES=$(echo "$FILES" | grep '\.sh$') + +[[ -n "$PHP_FILES" ]] && ./php/check_phpstan.sh default $PHP_FILES +[[ -n "$SH_FILES" ]] && ./scripts/check_shellcheck.sh $SH_FILES +``` + +## Testing + +```bash +# Run all tests +./tests/run_all.sh + +# Run specific test suite +./tests/php/phpstan/run.sh +``` + +## Requirements + +- Bash 4.0+ +- Git +- jq +- PHP 8.1+ with Composer (for PHP hooks) +- Docker (optional) +- ShellCheck (for shell validation) + +## License + +MIT diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..eef34b4 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,51 @@ +# Tests + +## Structure + +``` +tests/ +├── run_all.sh # Run all tests +├── lib/ +│ └── test_helper.bash # Common test functions +├── php/phpstan/ # PHPStan hook tests +├── shell/ # Shell script tests +├── docker/ # Docker hook tests +└── simple/ # Simple validator tests +``` + +## Running Tests + +```bash +# Run all tests +./tests/run_all.sh + +# Run specific test suite +./tests/php/phpstan/run.sh + +# Run single test case +./tests/php/phpstan/cases/01_new_file_blocks_commit.sh +``` + +## Writing Tests + +Each test case should: +1. Source `test_helper.bash` +2. Call `setup_test_env` to create isolated environment +3. Create fixtures and mocks as needed +4. Run the hook with `run_hook` +5. Assert results with `assert_*` functions +6. Call `cleanup_test_env` (automatic on exit) + +## Test Case Template + +```bash +#!/bin/bash +source "$(dirname "$0")/../../lib/test_helper.bash" + +setup_test_env + +# ... test logic ... + +assert_exit_code 0 +cleanup_test_env +``` From 82e8402d029468a0f7a7c0c4957d3f6a5feac836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:34:58 +0300 Subject: [PATCH 14/15] fixed CI errors --- php/find_test.sh | 1 + php/start_test_in_docker.sh | 25 ++----------------- .../phpstan/cases/01_no_files_exits_zero.sh | 1 + .../php/phpstan/cases/02_clean_file_passes.sh | 1 + .../cases/03_progress_allows_commit.sh | 1 + .../cases/04_no_progress_blocks_commit.sh | 1 + .../cases/05_strict_mode_requires_zero.sh | 1 + 7 files changed, 8 insertions(+), 23 deletions(-) diff --git a/php/find_test.sh b/php/find_test.sh index adaa9db..6187cf3 100755 --- a/php/find_test.sh +++ b/php/find_test.sh @@ -42,6 +42,7 @@ find_project_root() { should_be_tested() { local classname="$1" for pattern in "${EXCLUDE_PATTERNS[@]}"; do + # shellcheck disable=SC2053 # Glob matching is intentional if [[ "$classname" == $pattern ]]; then return 1 fi diff --git a/php/start_test_in_docker.sh b/php/start_test_in_docker.sh index 5301fab..f3a195f 100644 --- a/php/start_test_in_docker.sh +++ b/php/start_test_in_docker.sh @@ -52,6 +52,7 @@ path_to_classname() { should_be_tested() { classname="$1" for pattern in "${EXCLUDE_PATTERNS[@]}"; do + # shellcheck disable=SC2053 # Glob matching is intentional if [[ "$classname" == $pattern ]]; then return 1 fi @@ -64,26 +65,6 @@ get_expected_test_classname() { echo "Tests\\Unit\\${classname}Test" } -extract_classname_from_file() { - local file="$1" - - if [[ ! -f "$file" ]]; then - return 1 - fi - - # Extract namespace - local namespace - namespace=$(grep -m1 "^namespace " "$file" | sed 's/namespace \(.*\);/\1/' | tr -d ' ') - - # Extract class name - local classname - classname=$(grep -m1 "^class " "$file" | sed 's/class \([a-zA-Z0-9_]*\).*/\1/') - - if [[ -n "$namespace" && -n "$classname" ]]; then - echo "${namespace}\\${classname}" - fi -} - find_test_class_path() { local test_classname="$1" local project_root="$2" @@ -117,9 +98,7 @@ run_test_for_class() { echo "Running test: $test_classname" echo "File: $classname" - docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" sh -c "cd $PROJECT_PATH && php artisan test --filter='$classname'" - - if [[ $? -eq 0 ]]; then + if docker compose -f "$COMPOSE_FILE" exec -T "$SERVICE_NAME" sh -c "cd $PROJECT_PATH && php artisan test --filter='$classname'"; then echo "Test passed: $test_classname" return 0 else diff --git a/tests/php/phpstan/cases/01_no_files_exits_zero.sh b/tests/php/phpstan/cases/01_no_files_exits_zero.sh index fe36199..4b707fb 100755 --- a/tests/php/phpstan/cases/01_no_files_exits_zero.sh +++ b/tests/php/phpstan/cases/01_no_files_exits_zero.sh @@ -5,6 +5,7 @@ # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 source "$SCRIPT_DIR/../../../lib/test_helper.bash" setup_test_env "phpstan_no_files" diff --git a/tests/php/phpstan/cases/02_clean_file_passes.sh b/tests/php/phpstan/cases/02_clean_file_passes.sh index b4908fe..227cea6 100755 --- a/tests/php/phpstan/cases/02_clean_file_passes.sh +++ b/tests/php/phpstan/cases/02_clean_file_passes.sh @@ -5,6 +5,7 @@ # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 source "$SCRIPT_DIR/../../../lib/test_helper.bash" setup_test_env "phpstan_clean_file" diff --git a/tests/php/phpstan/cases/03_progress_allows_commit.sh b/tests/php/phpstan/cases/03_progress_allows_commit.sh index 19fb0ae..ada3df1 100755 --- a/tests/php/phpstan/cases/03_progress_allows_commit.sh +++ b/tests/php/phpstan/cases/03_progress_allows_commit.sh @@ -5,6 +5,7 @@ # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 source "$SCRIPT_DIR/../../../lib/test_helper.bash" setup_test_env "phpstan_progress" diff --git a/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh b/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh index 2104aca..b7bfee2 100755 --- a/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh +++ b/tests/php/phpstan/cases/04_no_progress_blocks_commit.sh @@ -5,6 +5,7 @@ # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 source "$SCRIPT_DIR/../../../lib/test_helper.bash" setup_test_env "phpstan_no_progress" diff --git a/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh b/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh index 6578c6a..c58243d 100755 --- a/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh +++ b/tests/php/phpstan/cases/05_strict_mode_requires_zero.sh @@ -5,6 +5,7 @@ # ------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 source "$SCRIPT_DIR/../../../lib/test_helper.bash" setup_test_env "phpstan_strict" From a3c0adaaae6c997841a436fd26dcfacb48107ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=9B=D1=8F=D1=89=D1=83=D0=BA?= <40496434+prog-time@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:39:59 +0300 Subject: [PATCH 15/15] fixed CI errors --- tests/php/phpstan/run.sh | 4 ++-- tests/run_all.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/php/phpstan/run.sh b/tests/php/phpstan/run.sh index e7d479b..89835b5 100755 --- a/tests/php/phpstan/run.sh +++ b/tests/php/phpstan/run.sh @@ -14,9 +14,9 @@ for case_file in "$SCRIPT_DIR"/cases/*.sh; do [[ -f "$case_file" ]] || continue if "$case_file"; then - ((PASSED++)) + ((PASSED++)) || true else - ((FAILED++)) + ((FAILED++)) || true fi done diff --git a/tests/run_all.sh b/tests/run_all.sh index 5c9747b..aec3b98 100755 --- a/tests/run_all.sh +++ b/tests/run_all.sh @@ -23,9 +23,9 @@ for suite in $(find "$SCRIPT_DIR" -mindepth 2 -name "run.sh" | sort); do echo "--- Suite: $suite_name ---" if "$suite"; then - ((PASSED++)) + ((PASSED++)) || true else - ((FAILED++)) + ((FAILED++)) || true FAILED_TESTS+=("$suite_name") fi echo ""