Loading...
No commits yet
Not committed History
Blame
sync_tests_with_source.sh • 7.6 KB
#!/bin/bash
# Timestamp: "2025-11-18 11:07:00 (ywatanabe)"
# File: ./tests/sync_tests_with_scripts.sh

ORIG_DIR="$(pwd)"
THIS_DIR="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)"
LOG_PATH="$THIS_DIR/.$(basename $0).log"
echo > "$LOG_PATH"

GIT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"

GRAY='\033[0;90m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
NC='\033[0m'

echo_info() { echo -e "${GRAY}INFO: $1${NC}"; }
echo_success() { echo -e "${GREEN}SUCC: $1${NC}"; }
echo_warning() { echo -e "${YELLOW}WARN: $1${NC}"; }
echo_error() { echo -e "${RED}ERRO: $1${NC}"; }
echo_header() { echo_info "=== $1 ==="; }

ROOT_DIR="$(realpath $THIS_DIR/..)"

DO_MOVE=false
SRC_DIR="$(realpath "${ROOT_DIR}/scripts")"
TESTS_DIR="$(realpath "${ROOT_DIR}/tests")"

usage() {
    echo "Usage: $0 [options]"
    echo
    echo "Synchronizes test files with script files, maintaining test code structure."
    echo
    echo "Options:"
    echo "  -m, --move         Move stale test files to .old directory instead of just reporting (default: $DO_MOVE)"
    echo "  -s, --source DIR   Specify custom source directory (default: $SRC_DIR)"
    echo "  -t, --tests DIR    Specify custom tests directory (default: $TESTS_DIR)"
    echo "  -h, --help         Display this help message"
    echo
    echo "Example:"
    echo "  $0 --move"
    echo "  $0 --source /path/to/scripts --tests /path/to/tests"
    exit 1
}

while [[ $# -gt 0 ]]; do
    case $1 in
        -m|--move)
            DO_MOVE=true
            shift
            ;;
        -s|--source)
            SRC_DIR="$2"
            shift 2
            ;;
        -t|--tests)
            TESTS_DIR="$2"
            shift 2
            ;;
        -h|--help)
            usage
            ;;
        *)
            echo "Unknown option: $1"
            usage
            ;;
    esac
done

prepare_tests_structure_as_source() {
    [ ! -d "$SRC_DIR" ] && echo_error "Source directory not found: $SRC_DIR" && exit 1
    construct_blacklist_patterns
    find "$SRC_DIR" -type d "${FIND_EXCLUDES[@]}" | while read -r dir; do
        tests_dir="${dir/$SRC_DIR/$TESTS_DIR}"
        mkdir -p "$tests_dir"
    done
}

extract_test_code() {
    local test_file=$1
    local temp_file=$(mktemp)

    sed -n '/if __name__ == "__main__":/q;p' "$test_file" > "$temp_file"

    if [ -s "$temp_file" ]; then
        sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$temp_file"
        cat "$temp_file"
    fi
    rm "$temp_file"
}

get_pytest_guard() {
    echo ''
    echo 'if __name__ == "__main__":'
    echo '    import os'
    echo ''
    echo '    import pytest'
    echo ''
    echo '    pytest.main([os.path.abspath(__file__)])'
}

update_test_file() {
    local test_file=$1
    local src_file=$2

    if [ ! -f "$test_file" ]; then
        echo_info "Creating test file: $test_file"
        mkdir -p "$(dirname "$test_file")"

        cat > "$test_file" << EOL
# Add your tests here

$(get_pytest_guard)
EOL
    else
        local temp_file=$(mktemp)
        local test_code=$(extract_test_code "$test_file")

        if [ -n "$test_code" ]; then
            echo "$test_code" > "$temp_file"
            [[ "$(tail -c 1 "$temp_file")" != "" ]] && echo "" >> "$temp_file"
        else
            echo "# Add your tests here" > "$temp_file"
            echo "" >> "$temp_file"
        fi

        get_pytest_guard >> "$temp_file"

        mv "$temp_file" "$test_file"
    fi
}

construct_blacklist_patterns() {
    local EXCLUDE_PATHS=(
        "*/.*"
        "*/.*/*"
        "*/deprecated*"
        "*/archive*"
        "*/backup*"
        "*/tmp*"
        "*/temp*"
        "*_out"
        "*_out/*"
        "*/RUNNING"
        "*/RUNNING/*"
        "*/FINISHED"
        "*/FINISHED/*"
        "*/FINISHED_SUCCESS"
        "*/FINISHED_SUCCESS/*"
        "*/FINISHED_FAILED"
        "*/FINISHED_FAILED/*"
        "*/2025Y*"
        "*/2024Y*"
        "*/__pycache__"
        "*/__pycache__/*"
        "*/template.py"
    )

    FIND_EXCLUDES=()
    PRUNE_ARGS=()
    for path in "${EXCLUDE_PATHS[@]}"; do
        FIND_EXCLUDES+=( -not -path "$path" )
        PRUNE_ARGS+=( -path "$path" -o )
    done
    unset 'PRUNE_ARGS[${#PRUNE_ARGS[@]}-1]'
}

find_files() {
    local search_path=$1
    local type=$2
    local name_pattern=$3

    construct_blacklist_patterns
    find "$search_path" \
        \( "${PRUNE_ARGS[@]}" \) -prune -o -type "$type" -name "$name_pattern" -print
}

move_stale_test_files_to_old() {
    local timestamp="$(date +%Y%m%d_%H%M%S)"

    find "$TESTS_DIR" -name "test_*.py" -not -path "*.old*" | while read -r test_path; do

        [[ "$test_path" =~ ^${TESTS_DIR}/custom ]] && continue

        test_rel_path="${test_path#$TESTS_DIR/}"
        test_rel_dir="$(dirname $test_rel_path)"
        test_filename="$(basename $test_rel_path)"

        src_filename="${test_filename#test_}"
        src_rel_dir="$test_rel_dir"
        src_rel_path="$src_rel_dir/$src_filename"
        src_path="$SRC_DIR/$src_rel_path"

        if [ ! -f "$src_path" ] && [ -f "$test_path" ]; then

            stale_test_path=$test_path
            stale_test_filename="$(basename $stale_test_path)"
            stale_test_path_dir="$(dirname $stale_test_path)"
            old_dir_with_timestamp="$stale_test_path_dir/.old-$timestamp"
            tgt_path="$old_dir_with_timestamp/$stale_test_filename"

            echo_warning "Stale Test: $stale_test_path"
            echo_warning "To remove: $0 -m"

            if [ "$DO_MOVE" = "true" ]; then
                mkdir -p "$old_dir_with_timestamp"
                mv "$stale_test_path" "$tgt_path"
                echo_success "Moved: $stale_test_path -> $tgt_path"
            fi

        fi

    done
}

remove_hidden_test_files_and_dirs() {
    find "$TESTS_DIR" -type f -name ".*" -delete 2>/dev/null
    find "$TESTS_DIR" -type d -name ".*" -not -path "$TESTS_DIR/.old" -not -path "$TESTS_DIR/.old/*" -exec rm -rf {} \; 2>/dev/null
}

cleanup_unnecessary_test_files() {
    find "$TESTS_DIR" -type d -name "*RUNNING*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*FINISHED*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*FINISHED_SUCCESS*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*FINISHED_FAILED*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*2024Y*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*2025Y*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*_out" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type d -name "*__pycache__*" -exec rm -rf {} \; 2>/dev/null
    find "$TESTS_DIR" -type f -name "*.pyc" -delete 2>/dev/null
}

chmod_python_scripts_as_executable() {
    construct_blacklist_patterns
    find "$SRC_DIR" -type f -name "*.py" "${FIND_EXCLUDES[@]}" -exec chmod +x {} \;
}

main() {
    echo_header "Sync Tests with Source"
    echo_info "SRC_DIR: $SRC_DIR"
    echo_info "TESTS_DIR: $TESTS_DIR"
    echo ""

    remove_hidden_test_files_and_dirs
    prepare_tests_structure_as_source
    chmod_python_scripts_as_executable
    cleanup_unnecessary_test_files

    find_files "$SRC_DIR" f "*.py" | while read -r src_file; do
        rel="${src_file#$SRC_DIR/}"
        rel_dir=$(dirname "$rel")
        src_base=$(basename "$rel")

        tests_dir="$TESTS_DIR/$rel_dir"
        mkdir -p "$tests_dir"

        test_file="$tests_dir/test_$src_base"

        update_test_file "$test_file" "$src_file"
    done

    remove_hidden_test_files_and_dirs
    move_stale_test_files_to_old

    echo ""
    echo_header "Test Directory Structure"
    tree "$TESTS_DIR" 2>&1 | tee -a "$LOG_PATH"

    echo ""
    echo_success "Synchronization complete!"
}

main "$@"
cd $ORIG_DIR

# EOF