2025-09-24 09:30:42 +09:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
# run.sh - スモークテストv2 単一エントリポイント
|
|
|
|
|
|
# Usage: ./run.sh --profile {quick|integration|full} [options]
|
|
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
# スクリプトディレクトリ
|
|
|
|
|
|
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
|
|
|
|
|
|
# デフォルト値
|
|
|
|
|
|
PROFILE="quick"
|
|
|
|
|
|
FORMAT="text"
|
|
|
|
|
|
JOBS=1
|
|
|
|
|
|
TIMEOUT=""
|
|
|
|
|
|
VERBOSE=false
|
|
|
|
|
|
DRY_RUN=false
|
|
|
|
|
|
FILTER=""
|
|
|
|
|
|
FORCE_CONFIG=""
|
|
|
|
|
|
|
|
|
|
|
|
# カラー定義
|
|
|
|
|
|
readonly RED='\033[0;31m'
|
|
|
|
|
|
readonly GREEN='\033[0;32m'
|
|
|
|
|
|
readonly YELLOW='\033[1;33m'
|
|
|
|
|
|
readonly BLUE='\033[0;34m'
|
|
|
|
|
|
readonly BOLD='\033[1m'
|
|
|
|
|
|
readonly NC='\033[0m'
|
|
|
|
|
|
|
|
|
|
|
|
# ログ関数
|
|
|
|
|
|
log_info() {
|
|
|
|
|
|
echo -e "${BLUE}[INFO]${NC} $*" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log_success() {
|
|
|
|
|
|
echo -e "${GREEN}[SUCCESS]${NC} $*" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log_warn() {
|
|
|
|
|
|
echo -e "${YELLOW}[WARN]${NC} $*" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log_error() {
|
|
|
|
|
|
echo -e "${RED}[ERROR]${NC} $*" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log_header() {
|
|
|
|
|
|
echo -e "${BOLD}$*${NC}" >&2
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ヘルプ表示
|
|
|
|
|
|
show_help() {
|
|
|
|
|
|
cat << 'EOF'
|
|
|
|
|
|
Smoke Tests v2 - Nyash 2-Pillar Testing System
|
|
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
|
./run.sh --profile PROFILE [options]
|
|
|
|
|
|
|
|
|
|
|
|
Profiles:
|
|
|
|
|
|
quick Development-time fast checks (1-2 min)
|
|
|
|
|
|
integration Basic VM ↔ LLVM parity checks (5-10 min)
|
|
|
|
|
|
full Complete matrix testing (15-30 min)
|
|
|
|
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
|
--profile PROFILE Test profile to run
|
|
|
|
|
|
--filter "PATTERN" Test filter (e.g., "boxes:string")
|
|
|
|
|
|
--format FORMAT Output format: text|json|junit
|
|
|
|
|
|
--jobs N Parallel execution count
|
|
|
|
|
|
--timeout SEC Timeout per test in seconds
|
|
|
|
|
|
--force-config CONFIG Force configuration: rust_vm_dynamic|llvm_static
|
|
|
|
|
|
--verbose Enable verbose output
|
|
|
|
|
|
--dry-run Show test list without execution
|
|
|
|
|
|
--help Show this help
|
|
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
# Quick development check
|
|
|
|
|
|
./run.sh --profile quick
|
|
|
|
|
|
|
|
|
|
|
|
# Integration with filter
|
|
|
|
|
|
./run.sh --profile integration --filter "plugins:*"
|
|
|
|
|
|
|
|
|
|
|
|
# Full testing with JSON output
|
|
|
|
|
|
./run.sh --profile full --format json --jobs 4 --timeout 300
|
|
|
|
|
|
|
|
|
|
|
|
# Dry run to see what would be tested
|
|
|
|
|
|
./run.sh --profile integration --dry-run
|
|
|
|
|
|
|
|
|
|
|
|
Environment Variables:
|
|
|
|
|
|
SMOKES_FORCE_CONFIG Force specific configuration
|
|
|
|
|
|
SMOKES_PLUGIN_MODE Plugin mode: dynamic|static
|
|
|
|
|
|
CI CI environment detection
|
|
|
|
|
|
EOF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 引数パース
|
|
|
|
|
|
parse_arguments() {
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
|
case $1 in
|
|
|
|
|
|
--profile)
|
|
|
|
|
|
PROFILE="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--filter)
|
|
|
|
|
|
FILTER="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--format)
|
|
|
|
|
|
FORMAT="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--jobs)
|
|
|
|
|
|
JOBS="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--timeout)
|
|
|
|
|
|
TIMEOUT="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--force-config)
|
|
|
|
|
|
FORCE_CONFIG="$2"
|
|
|
|
|
|
shift 2
|
|
|
|
|
|
;;
|
|
|
|
|
|
--verbose)
|
|
|
|
|
|
VERBOSE=true
|
|
|
|
|
|
shift
|
|
|
|
|
|
;;
|
|
|
|
|
|
--dry-run)
|
|
|
|
|
|
DRY_RUN=true
|
|
|
|
|
|
shift
|
|
|
|
|
|
;;
|
|
|
|
|
|
--help)
|
|
|
|
|
|
show_help
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
log_error "Unknown option: $1"
|
|
|
|
|
|
show_help
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
# プロファイル検証
|
|
|
|
|
|
case "$PROFILE" in
|
2025-09-24 21:45:27 +09:00
|
|
|
|
quick|integration|full|plugins)
|
2025-09-24 09:30:42 +09:00
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
log_error "Invalid profile: $PROFILE"
|
2025-09-24 21:45:27 +09:00
|
|
|
|
log_error "Valid profiles: quick, integration, full, plugins"
|
2025-09-24 09:30:42 +09:00
|
|
|
|
exit 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
# フォーマット検証
|
|
|
|
|
|
case "$FORMAT" in
|
|
|
|
|
|
text|json|junit)
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
log_error "Invalid format: $FORMAT"
|
|
|
|
|
|
log_error "Valid formats: text, json, junit"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 環境設定
|
|
|
|
|
|
setup_environment() {
|
|
|
|
|
|
log_info "Setting up environment for profile: $PROFILE"
|
|
|
|
|
|
|
|
|
|
|
|
# 共通ライブラリ読み込み
|
|
|
|
|
|
source "$SCRIPT_DIR/lib/test_runner.sh"
|
|
|
|
|
|
source "$SCRIPT_DIR/lib/plugin_manager.sh"
|
|
|
|
|
|
source "$SCRIPT_DIR/lib/result_checker.sh"
|
|
|
|
|
|
source "$SCRIPT_DIR/lib/preflight.sh"
|
|
|
|
|
|
|
|
|
|
|
|
# 設定読み込み
|
|
|
|
|
|
if [ -n "$FORCE_CONFIG" ]; then
|
|
|
|
|
|
export SMOKES_FORCE_CONFIG="$FORCE_CONFIG"
|
|
|
|
|
|
log_info "Forced configuration: $FORCE_CONFIG"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
source "$SCRIPT_DIR/configs/auto_detect.conf"
|
|
|
|
|
|
auto_configure "$PROFILE"
|
|
|
|
|
|
|
|
|
|
|
|
# プロファイル専用設定
|
|
|
|
|
|
export SMOKES_CURRENT_PROFILE="$PROFILE"
|
|
|
|
|
|
|
|
|
|
|
|
# コマンドライン引数の環境変数設定
|
|
|
|
|
|
if [ -n "$TIMEOUT" ]; then
|
|
|
|
|
|
export SMOKES_DEFAULT_TIMEOUT="$TIMEOUT"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$VERBOSE" = true ]; then
|
|
|
|
|
|
export NYASH_CLI_VERBOSE=1
|
|
|
|
|
|
export SMOKES_LOG_LEVEL="debug"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
export SMOKES_PARALLEL_JOBS="$JOBS"
|
|
|
|
|
|
export SMOKES_OUTPUT_FORMAT="$FORMAT"
|
|
|
|
|
|
export SMOKES_TEST_FILTER="$FILTER"
|
|
|
|
|
|
|
|
|
|
|
|
# 作業ディレクトリ移動(Nyashプロジェクトルートへ)
|
|
|
|
|
|
cd "$SCRIPT_DIR/../../.."
|
|
|
|
|
|
log_info "Working directory: $(pwd)"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# プリフライトチェック
|
|
|
|
|
|
run_preflight() {
|
|
|
|
|
|
log_info "Running preflight checks..."
|
|
|
|
|
|
|
|
|
|
|
|
if ! preflight_all; then
|
|
|
|
|
|
log_error "Preflight checks failed"
|
|
|
|
|
|
log_error "Run with --verbose for detailed information"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
log_success "Preflight checks passed"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# テストファイル検索
|
|
|
|
|
|
find_test_files() {
|
|
|
|
|
|
local profile_dir="$SCRIPT_DIR/profiles/$PROFILE"
|
|
|
|
|
|
local test_files=()
|
|
|
|
|
|
|
|
|
|
|
|
if [ ! -d "$profile_dir" ]; then
|
|
|
|
|
|
log_error "Profile directory not found: $profile_dir"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# テストファイル検索
|
|
|
|
|
|
while IFS= read -r -d '' file; do
|
|
|
|
|
|
# フィルタ適用
|
|
|
|
|
|
if [ -n "$FILTER" ]; then
|
|
|
|
|
|
local relative_path
|
|
|
|
|
|
relative_path=$(realpath --relative-to="$profile_dir" "$file")
|
|
|
|
|
|
if ! echo "$relative_path" | grep -q "$FILTER"; then
|
|
|
|
|
|
continue
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
test_files+=("$file")
|
|
|
|
|
|
done < <(find "$profile_dir" -name "*.sh" -type f -print0)
|
|
|
|
|
|
|
|
|
|
|
|
printf '%s\n' "${test_files[@]}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 単一テスト実行
|
|
|
|
|
|
run_single_test() {
|
|
|
|
|
|
local test_file="$1"
|
|
|
|
|
|
local test_name
|
|
|
|
|
|
test_name=$(basename "$test_file" .sh)
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$FORMAT" = "text" ]; then
|
|
|
|
|
|
echo -n "Running $test_name... "
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
local start_time end_time duration exit_code
|
|
|
|
|
|
start_time=$(date +%s.%N)
|
|
|
|
|
|
|
|
|
|
|
|
# タイムアウト付きテスト実行
|
|
|
|
|
|
local timeout_cmd=""
|
|
|
|
|
|
if [ -n "${SMOKES_DEFAULT_TIMEOUT:-}" ]; then
|
|
|
|
|
|
timeout_cmd="timeout ${SMOKES_DEFAULT_TIMEOUT}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2025-09-24 23:27:59 +09:00
|
|
|
|
# 詳細ログ: 失敗時のみテイル表示
|
|
|
|
|
|
local log_file
|
|
|
|
|
|
log_file="/tmp/nyash_smoke_$(date +%s)_$$.log"
|
|
|
|
|
|
if $timeout_cmd bash "$test_file" >"$log_file" 2>&1; then
|
2025-09-24 09:30:42 +09:00
|
|
|
|
exit_code=0
|
|
|
|
|
|
else
|
|
|
|
|
|
exit_code=$?
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
end_time=$(date +%s.%N)
|
|
|
|
|
|
duration=$(echo "$end_time - $start_time" | bc -l)
|
|
|
|
|
|
|
|
|
|
|
|
# 結果出力
|
|
|
|
|
|
case "$FORMAT" in
|
|
|
|
|
|
text)
|
|
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
|
|
|
|
echo -e "${GREEN}PASS${NC} (${duration}s)"
|
|
|
|
|
|
else
|
2025-09-24 23:27:59 +09:00
|
|
|
|
echo -e "${RED}FAIL${NC} (exit=$exit_code, ${duration}s)"
|
|
|
|
|
|
echo -e "${YELLOW}[WARN]${NC} Test file: $test_file"
|
|
|
|
|
|
local TAIL_N="${SMOKES_NOTIFY_TAIL:-80}"
|
|
|
|
|
|
echo "----- LOG (tail -n $TAIL_N) -----"
|
|
|
|
|
|
tail -n "$TAIL_N" "$log_file" || true
|
|
|
|
|
|
echo "----- END LOG -----"
|
2025-09-24 09:30:42 +09:00
|
|
|
|
fi
|
|
|
|
|
|
;;
|
|
|
|
|
|
json)
|
2025-09-24 23:27:59 +09:00
|
|
|
|
local status_json
|
|
|
|
|
|
status_json=$([ $exit_code -eq 0 ] && echo "pass" || echo "fail")
|
|
|
|
|
|
echo "{\"name\":\"$test_name\",\"path\":\"$test_file\",\"status\":\"$status_json\",\"duration\":$duration,\"exit\":$exit_code}"
|
2025-09-24 09:30:42 +09:00
|
|
|
|
;;
|
|
|
|
|
|
junit)
|
2025-09-24 23:27:59 +09:00
|
|
|
|
# JUnit形式は後でまとめて出力(pathも保持)
|
|
|
|
|
|
echo "$test_name:$exit_code:$duration:$test_file" >> /tmp/junit_results.txt
|
2025-09-24 09:30:42 +09:00
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
2025-09-24 23:27:59 +09:00
|
|
|
|
# 後始末
|
|
|
|
|
|
rm -f "$log_file" 2>/dev/null || true
|
2025-09-24 09:30:42 +09:00
|
|
|
|
return $exit_code
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# テスト実行
|
|
|
|
|
|
run_tests() {
|
|
|
|
|
|
local test_files
|
|
|
|
|
|
mapfile -t test_files < <(find_test_files)
|
|
|
|
|
|
|
|
|
|
|
|
if [ ${#test_files[@]} -eq 0 ]; then
|
|
|
|
|
|
log_warn "No test files found for profile: $PROFILE"
|
|
|
|
|
|
if [ -n "$FILTER" ]; then
|
|
|
|
|
|
log_warn "Filter applied: $FILTER"
|
|
|
|
|
|
fi
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
log_info "Found ${#test_files[@]} test files"
|
|
|
|
|
|
|
|
|
|
|
|
# Dry run
|
|
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
|
|
|
|
log_header "Test files that would be executed:"
|
|
|
|
|
|
for file in "${test_files[@]}"; do
|
|
|
|
|
|
echo " $(realpath --relative-to="$SCRIPT_DIR" "$file")"
|
|
|
|
|
|
done
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# テスト実行開始
|
|
|
|
|
|
log_header "Starting $PROFILE profile tests"
|
|
|
|
|
|
|
|
|
|
|
|
local passed=0
|
|
|
|
|
|
local failed=0
|
|
|
|
|
|
local start_time
|
|
|
|
|
|
start_time=$(date +%s.%N)
|
|
|
|
|
|
|
|
|
|
|
|
# JSON形式の場合はヘッダー出力
|
|
|
|
|
|
if [ "$FORMAT" = "json" ]; then
|
|
|
|
|
|
echo '{"profile":"'$PROFILE'","tests":['
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# JUnit用ファイル初期化
|
|
|
|
|
|
if [ "$FORMAT" = "junit" ]; then
|
|
|
|
|
|
echo -n > /tmp/junit_results.txt
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# テスト実行
|
|
|
|
|
|
local first_test=true
|
|
|
|
|
|
for test_file in "${test_files[@]}"; do
|
|
|
|
|
|
if [ "$FORMAT" = "json" ] && [ "$first_test" = false ]; then
|
|
|
|
|
|
echo ","
|
|
|
|
|
|
fi
|
|
|
|
|
|
first_test=false
|
|
|
|
|
|
|
|
|
|
|
|
if run_single_test "$test_file"; then
|
|
|
|
|
|
((passed++))
|
|
|
|
|
|
else
|
|
|
|
|
|
((failed++))
|
|
|
|
|
|
# Fast fail モード
|
|
|
|
|
|
if [ "${SMOKES_FAST_FAIL:-0}" = "1" ]; then
|
|
|
|
|
|
log_warn "Fast fail enabled, stopping on first failure"
|
|
|
|
|
|
break
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
# 結果出力
|
|
|
|
|
|
local end_time total_duration
|
|
|
|
|
|
end_time=$(date +%s.%N)
|
|
|
|
|
|
total_duration=$(echo "$end_time - $start_time" | bc -l)
|
|
|
|
|
|
|
|
|
|
|
|
case "$FORMAT" in
|
|
|
|
|
|
text)
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
log_header "Test Results Summary"
|
|
|
|
|
|
echo "Profile: $PROFILE"
|
|
|
|
|
|
echo "Total: $((passed + failed))"
|
|
|
|
|
|
echo "Passed: $passed"
|
|
|
|
|
|
echo "Failed: $failed"
|
|
|
|
|
|
echo "Duration: ${total_duration}s"
|
|
|
|
|
|
|
|
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
|
|
|
|
log_success "All tests passed! ✨"
|
|
|
|
|
|
else
|
|
|
|
|
|
log_error "$failed test(s) failed"
|
|
|
|
|
|
fi
|
|
|
|
|
|
;;
|
|
|
|
|
|
json)
|
|
|
|
|
|
echo '],"summary":{"total":'$((passed + failed))',"passed":'$passed',"failed":'$failed',"duration":'$total_duration'}}'
|
|
|
|
|
|
;;
|
|
|
|
|
|
junit)
|
|
|
|
|
|
cat << EOF
|
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
|
<testsuite name="smokes_$PROFILE" tests="$((passed + failed))" failures="$failed" time="$total_duration">
|
|
|
|
|
|
EOF
|
2025-09-24 23:27:59 +09:00
|
|
|
|
while IFS=':' read -r name exit_code duration path; do
|
2025-09-24 09:30:42 +09:00
|
|
|
|
if [ "$exit_code" = "0" ]; then
|
2025-09-24 23:27:59 +09:00
|
|
|
|
echo " <testcase name=\"$name\" time=\"$duration\" classname=\"$path\"/>"
|
2025-09-24 09:30:42 +09:00
|
|
|
|
else
|
2025-09-24 23:27:59 +09:00
|
|
|
|
echo " <testcase name=\"$name\" time=\"$duration\" classname=\"$path\"><failure message=\"exit=$exit_code\"/></testcase>"
|
2025-09-24 09:30:42 +09:00
|
|
|
|
fi
|
|
|
|
|
|
done < /tmp/junit_results.txt
|
|
|
|
|
|
echo "</testsuite>"
|
|
|
|
|
|
rm -f /tmp/junit_results.txt
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
|
|
# 終了コード
|
|
|
|
|
|
[ $failed -eq 0 ]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# メイン処理
|
|
|
|
|
|
main() {
|
|
|
|
|
|
# 引数パース
|
|
|
|
|
|
parse_arguments "$@"
|
|
|
|
|
|
|
|
|
|
|
|
# バナー表示
|
|
|
|
|
|
if [ "$FORMAT" = "text" ]; then
|
|
|
|
|
|
log_header "🔥 Nyash Smoke Tests v2 - 2-Pillar Testing System"
|
|
|
|
|
|
log_info "Profile: $PROFILE | Format: $FORMAT | Jobs: $JOBS"
|
|
|
|
|
|
if [ -n "$FILTER" ]; then
|
|
|
|
|
|
log_info "Filter: $FILTER"
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# 環境設定
|
|
|
|
|
|
setup_environment
|
|
|
|
|
|
|
|
|
|
|
|
# プリフライト
|
|
|
|
|
|
run_preflight
|
|
|
|
|
|
|
|
|
|
|
|
# テスト実行
|
|
|
|
|
|
run_tests
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# エラーハンドリング
|
|
|
|
|
|
trap 'log_error "Script interrupted"; exit 130' INT TERM
|
|
|
|
|
|
|
|
|
|
|
|
# メイン実行
|
2025-09-24 21:45:27 +09:00
|
|
|
|
main "$@"
|