Commit b77649b2 authored by Ivan Mazhukin's avatar Ivan Mazhukin

add multisystem tests

parent 2edfc216
...@@ -25,6 +25,20 @@ ...@@ -25,6 +25,20 @@
./epm-docker-test.sh ayugram fedora ./epm-docker-test.sh ayugram fedora
``` ```
Запуск одного приложения сразу на нескольких системах:
```bash
./epm-docker-test.sh ayugram fedora debian ubuntu
```
Запуск по пресету:
```bash
./epm-docker-test.sh ayugram --preset mainstream
./epm-docker-test.sh ayugram --preset russian
./epm-docker-test.sh ayugram --preset all
```
Запуск с явным путём к `eepm`: Запуск с явным путём к `eepm`:
```bash ```bash
...@@ -46,8 +60,9 @@ ...@@ -46,8 +60,9 @@
## Формат команды ## Формат команды
```bash ```bash
./epm-docker-test.sh [options] play <app> <system> ./epm-docker-test.sh [options] play <app> <system> [<system> ...]
./epm-docker-test.sh [options] <app> <system> ./epm-docker-test.sh [options] <app> <system> [<system> ...]
./epm-docker-test.sh [options] <app> --preset <name>
``` ```
Сейчас поддерживается только команда `play`. Сейчас поддерживается только команда `play`.
...@@ -57,6 +72,13 @@ ...@@ -57,6 +72,13 @@
- `<app>`: имя приложения для `epm play` - `<app>`: имя приложения для `epm play`
- `<system>`: целевая система или docker image - `<system>`: целевая система или docker image
Можно передать:
- одну систему;
- несколько систем через пробел;
- один или несколько `--preset`;
- комбинацию явных систем и `--preset`.
Примеры нормализации `<system>`: Примеры нормализации `<system>`:
- `fedora` -> `fedora:latest` - `fedora` -> `fedora:latest`
...@@ -74,8 +96,19 @@ ...@@ -74,8 +96,19 @@
- `--eepm-dir <path>`: явный путь к каталогу `eepm` - `--eepm-dir <path>`: явный путь к каталогу `eepm`
- `--builder-user <user>`: пользователь для builder64-источника - `--builder-user <user>`: пользователь для builder64-источника
- `--builder-path <path>`: явный builder64-путь вместо дефолта - `--builder-path <path>`: явный builder64-путь вместо дефолта
- `--preset <all|russian|mainstream>`: добавить набор систем
- `--log-root <path>`: каталог для логов - `--log-root <path>`: каталог для логов
## Пресеты
Сейчас определены такие пресеты:
- `mainstream`: `fedora`, `debian`, `ubuntu`, `opensuse/leap`, `alpine`, `void`
- `russian`: `alt:sisyphus`
- `all`: `mainstream` + `russian`
Повторы автоматически удаляются после нормализации имени системы.
## Источник eepm ## Источник eepm
### local ### local
...@@ -169,8 +202,10 @@ Runner: local docker (inside ssh session, user: builder-robot) ...@@ -169,8 +202,10 @@ Runner: local docker (inside ssh session, user: builder-robot)
Сейчас bootstrap завязан на семейство системы: Сейчас bootstrap завязан на семейство системы:
- `altlinux|alt`: `repo set etersoft`, `update`, затем установка `wget glibc-pthread file` - `altlinux|alt`: `repo set etersoft`, `update`, затем установка `wget glibc-pthread file`
- `debian|ubuntu`: `update`, затем установка `bash wget ca-certificates coreutils file` - `debian|ubuntu`: `update`, затем установка `wget ca-certificates coreutils file`
- все остальные: установка `wget file bash` - `alpine`: `apk update`, `apk add bash`, затем `update` и установка `wget file`
- `void`: `xbps-install -Syu`, `xbps-install -Sy bash`, затем `update` и установка `wget file`
- все остальные: `update`, затем установка `wget file bash`
## Логи ## Логи
...@@ -202,6 +237,8 @@ ayugram-fedora-latest-20260331-185448.log ...@@ -202,6 +237,8 @@ ayugram-fedora-latest-20260331-185448.log
При ошибке скрипт дополнительно выводит короткую выборку критичных строк из лога. При ошибке скрипт дополнительно выводит короткую выборку критичных строк из лога.
Если систем было несколько, в конце дополнительно печатается сводная таблица `PASS/FAIL` со ссылкой на лог каждого прогона.
## Типовые сценарии ## Типовые сценарии
Запустить локально, если Docker доступен: Запустить локально, если Docker доступен:
......
...@@ -10,7 +10,6 @@ DEFAULT_LOG_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test" ...@@ -10,7 +10,6 @@ DEFAULT_LOG_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test"
COMMAND="play" COMMAND="play"
APP_NAME="" APP_NAME=""
SYSTEM_INPUT=""
SYSTEM_IMAGE="" SYSTEM_IMAGE=""
RUN_MODE="auto" RUN_MODE="auto"
REMOTE_HOST="$DEFAULT_REMOTE_HOST" REMOTE_HOST="$DEFAULT_REMOTE_HOST"
...@@ -25,22 +24,19 @@ LOG_FILE="" ...@@ -25,22 +24,19 @@ LOG_FILE=""
REMOTE_ARGS=() REMOTE_ARGS=()
REMOTE_SYNC_DIR="" REMOTE_SYNC_DIR=""
RUN_TOKEN="$(date +%Y%m%d-%H%M%S)-$$" RUN_TOKEN="$(date +%Y%m%d-%H%M%S)-$$"
SYSTEM_INPUTS=()
# TODO(next): PRESET_NAMES=()
# - support one app across multiple systems in a single run: TARGET_SYSTEMS=()
# epm-docker-test.sh ayugram fedora:43 debian ubuntu alt:sisyphus SUMMARY_SYSTEMS=()
# - support system presets such as: SUMMARY_RESULTS=()
# --preset all SUMMARY_LOGS=()
# --preset russian
# --preset mainstream
# - print a final summary table with PASS/FAIL per system
# - keep multi-app runs out of scope for now; parallel shell is enough there
COLOR_RESET="" COLOR_RESET=""
COLOR_WARN="" COLOR_WARN=""
COLOR_ERROR="" COLOR_ERROR=""
COLOR_LOG_FATAL="" COLOR_LOG_FATAL=""
COLOR_LOG_ERROR="" COLOR_LOG_ERROR=""
COLOR_PASS=""
SSH_BASE_ARGS=( SSH_BASE_ARGS=(
-o BatchMode=yes -o BatchMode=yes
-o StrictHostKeyChecking=accept-new -o StrictHostKeyChecking=accept-new
...@@ -52,13 +48,15 @@ if [[ -t 2 && -z "${NO_COLOR:-}" ]]; then ...@@ -52,13 +48,15 @@ if [[ -t 2 && -z "${NO_COLOR:-}" ]]; then
COLOR_ERROR=$'\033[31m' COLOR_ERROR=$'\033[31m'
COLOR_LOG_FATAL=$'\033[31m' COLOR_LOG_FATAL=$'\033[31m'
COLOR_LOG_ERROR=$'\033[33m' COLOR_LOG_ERROR=$'\033[33m'
COLOR_PASS=$'\033[32m'
fi fi
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: Usage:
epm-docker-test.sh [options] play <app> <system> epm-docker-test.sh [options] play <app> <system> [<system> ...]
epm-docker-test.sh [options] <app> <system> epm-docker-test.sh [options] <app> <system> [<system> ...]
epm-docker-test.sh [options] <app> --preset <name>
Options: Options:
--mode <auto|local|remote> Runner mode. Default: auto --mode <auto|local|remote> Runner mode. Default: auto
...@@ -71,11 +69,15 @@ Options: ...@@ -71,11 +69,15 @@ Options:
--eepm-dir <path> Explicit eepm tree path --eepm-dir <path> Explicit eepm tree path
--builder-user <user> Preferred owner for ~/Projects/... lookup --builder-user <user> Preferred owner for ~/Projects/... lookup
--builder-path <path> Explicit builder64 source path --builder-path <path> Explicit builder64 source path
--preset <all|russian|mainstream>
Add a named system preset
--log-root <path> Directory for saved logs --log-root <path> Directory for saved logs
-h, --help Show this help -h, --help Show this help
Examples: Examples:
epm-docker-test.sh nginx fedora epm-docker-test.sh nginx fedora
epm-docker-test.sh nginx fedora debian ubuntu
epm-docker-test.sh nginx --preset mainstream
epm-docker-test.sh play nginx Fedora/43 epm-docker-test.sh play nginx Fedora/43
epm-docker-test.sh --remote-user builder-robot --eepm-source builder64 nginx fedora:43 epm-docker-test.sh --remote-user builder-robot --eepm-source builder64 nginx fedora:43
EOF EOF
...@@ -132,7 +134,7 @@ normalize_system_name() { ...@@ -132,7 +134,7 @@ normalize_system_name() {
image_tag="${normalized##*/}" image_tag="${normalized##*/}"
case "$image_tag" in case "$image_tag" in
[0-9]*|v[0-9]*) [0-9]*|v[0-9]*|p[0-9]*)
normalized="${image_name}:${image_tag}" normalized="${image_name}:${image_tag}"
;; ;;
*) *)
...@@ -154,6 +156,61 @@ normalize_system_name() { ...@@ -154,6 +156,61 @@ normalize_system_name() {
esac esac
} }
expand_preset_systems() {
case "$1" in
mainstream)
printf '%s\n' \
fedora \
debian \
ubuntu \
opensuse/leap \
alpine \
void
;;
russian)
printf '%s\n' \
alt:sisyphus
;;
all)
expand_preset_systems mainstream
expand_preset_systems russian
;;
*)
fatal "Unknown preset: $1"
;;
esac
}
build_target_systems() {
local raw_system
local normalized_system
local preset_name
local seen="|"
TARGET_SYSTEMS=()
for preset_name in "${PRESET_NAMES[@]}"; do
while IFS= read -r raw_system; do
[[ -n "$raw_system" ]] || continue
normalized_system="$(normalize_system_name "$raw_system")"
if [[ "$seen" != *"|$normalized_system|"* ]]; then
TARGET_SYSTEMS+=("$normalized_system")
seen="${seen}${normalized_system}|"
fi
done < <(expand_preset_systems "$preset_name")
done
for raw_system in "${SYSTEM_INPUTS[@]}"; do
normalized_system="$(normalize_system_name "$raw_system")"
if [[ "$seen" != *"|$normalized_system|"* ]]; then
TARGET_SYSTEMS+=("$normalized_system")
seen="${seen}${normalized_system}|"
fi
done
((${#TARGET_SYSTEMS[@]} > 0)) || fatal "No target systems were provided"
}
can_use_local_docker() { can_use_local_docker() {
command -v docker >/dev/null 2>&1 || return 1 command -v docker >/dev/null 2>&1 || return 1
docker info >/dev/null 2>&1 docker info >/dev/null 2>&1
...@@ -319,19 +376,63 @@ EOF ...@@ -319,19 +376,63 @@ EOF
} }
create_log_file() { create_log_file() {
local safe_app safe_system timestamp log_name local safe_app safe_system timestamp log_name log_root
if ! mkdir -p "$LOG_ROOT" 2>/dev/null; then log_root="$LOG_ROOT"
if ! mkdir -p "$log_root" 2>/dev/null || [[ ! -w "$log_root" ]]; then
warn "Cannot write to log root $LOG_ROOT; falling back to ${TMPDIR:-/tmp}/epm-docker-test" warn "Cannot write to log root $LOG_ROOT; falling back to ${TMPDIR:-/tmp}/epm-docker-test"
LOG_ROOT="${TMPDIR:-/tmp}/epm-docker-test" log_root="${TMPDIR:-/tmp}/epm-docker-test"
mkdir -p "$LOG_ROOT" || fatal "Could not create log root: $LOG_ROOT" mkdir -p "$log_root" || fatal "Could not create log root: $log_root"
[[ -w "$log_root" ]] || fatal "Could not write to log root: $log_root"
fi fi
safe_app="$(sanitize_name "$APP_NAME")" safe_app="$(sanitize_name "$APP_NAME")"
safe_system="$(sanitize_name "$SYSTEM_IMAGE")" safe_system="$(sanitize_name "$SYSTEM_IMAGE")"
timestamp="$(date +%Y%m%d-%H%M%S)" timestamp="$(date +%Y%m%d-%H%M%S)"
log_name="${safe_app}-${safe_system}-${timestamp}.log" log_name="${safe_app}-${safe_system}-${timestamp}.log"
printf '%s\n' "$LOG_ROOT/$log_name" printf '%s\n' "$log_root/$log_name"
}
record_summary_result() {
SUMMARY_SYSTEMS+=("$1")
SUMMARY_RESULTS+=("$2")
SUMMARY_LOGS+=("$3")
}
print_summary_table() {
local i
local system_width=6
local result_label
local result_output
((${#SUMMARY_SYSTEMS[@]} > 1)) || return 0
for i in "${!SUMMARY_SYSTEMS[@]}"; do
if ((${#SUMMARY_SYSTEMS[i]} > system_width)); then
system_width=${#SUMMARY_SYSTEMS[i]}
fi
done
printf '\nSummary:\n'
printf '%-*s %-6s %s\n' "$system_width" "System" "Result" "Log"
printf '%-*s %-6s %s\n' "$system_width" "$(printf '%*s' "$system_width" '' | tr ' ' '-')" "------" "---"
for i in "${!SUMMARY_SYSTEMS[@]}"; do
if ((SUMMARY_RESULTS[i] == 0)); then
result_label="PASS"
result_output="${COLOR_PASS}${result_label}${COLOR_RESET}"
else
result_label="FAIL"
result_output="${COLOR_ERROR}${result_label}${COLOR_RESET}"
fi
printf '%-*s %s %s\n' \
"$system_width" \
"${SUMMARY_SYSTEMS[i]}" \
"$result_output" \
"${SUMMARY_LOGS[i]}"
done
} }
print_failure_excerpt() { print_failure_excerpt() {
...@@ -571,6 +672,41 @@ run_once() { ...@@ -571,6 +672,41 @@ run_once() {
esac esac
} }
run_for_system() {
local system_image="$1"
local index="$2"
local total="$3"
local status
SYSTEM_IMAGE="$system_image"
LOG_FILE="$(create_log_file)"
if ((total > 1)); then
info "Run [$index/$total]: epm $COMMAND $APP_NAME on $SYSTEM_IMAGE"
fi
info "Log file: $LOG_FILE"
info "Normalized system: $SYSTEM_IMAGE"
info "Test command: epm $COMMAND $APP_NAME"
if run_once 2>&1 | tee "$LOG_FILE"; then
status=0
else
status=$?
fi
if ((status == 0)); then
printf '\nTest passed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE"
printf 'Log: %s\n' "$LOG_FILE"
else
printf '\nTest failed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE" >&2
printf 'Log: %s\n' "$LOG_FILE" >&2
print_failure_excerpt
fi
record_summary_result "$SYSTEM_IMAGE" "$status" "$LOG_FILE"
return "$status"
}
parse_args() { parse_args() {
local positional=() local positional=()
...@@ -623,6 +759,11 @@ parse_args() { ...@@ -623,6 +759,11 @@ parse_args() {
BUILDER_PATH="$2" BUILDER_PATH="$2"
shift 2 shift 2
;; ;;
--preset)
[[ $# -ge 2 ]] || fatal "--preset requires a value"
PRESET_NAMES+=("$2")
shift 2
;;
--log-root) --log-root)
[[ $# -ge 2 ]] || fatal "--log-root requires a value" [[ $# -ge 2 ]] || fatal "--log-root requires a value"
LOG_ROOT="$2" LOG_ROOT="$2"
...@@ -649,49 +790,56 @@ parse_args() { ...@@ -649,49 +790,56 @@ parse_args() {
*) *)
positional+=("$1") positional+=("$1")
shift shift
;; ;;
esac esac
done done
if ((${#positional[@]} != 2)); then if ((${#positional[@]} < 1)); then
usage >&2 usage >&2
exit 1 exit 1
fi fi
APP_NAME="${positional[0]}" APP_NAME="${positional[0]}"
SYSTEM_INPUT="${positional[1]}" SYSTEM_INPUTS=("${positional[@]:1}")
if ((${#SYSTEM_INPUTS[@]} == 0 && ${#PRESET_NAMES[@]} == 0)); then
fatal "At least one target system or --preset is required"
fi
} }
main() { main() {
local status local system_image
local status=0
local run_status
local total
local index=0
parse_args "$@" parse_args "$@"
SYSTEM_IMAGE="$(normalize_system_name "$SYSTEM_INPUT")"
if ((INTERNAL_LOCAL_RUN)); then if ((INTERNAL_LOCAL_RUN)); then
build_target_systems
if ((${#TARGET_SYSTEMS[@]} != 1)); then
fatal "Internal local run expects exactly one target system"
fi
SYSTEM_IMAGE="${TARGET_SYSTEMS[0]}"
run_once run_once
return 0 return 0
fi fi
LOG_FILE="$(create_log_file)" build_target_systems
info "Log file: $LOG_FILE" total="${#TARGET_SYSTEMS[@]}"
info "Normalized system: $SYSTEM_IMAGE"
info "Test command: epm $COMMAND $APP_NAME"
if run_once 2>&1 | tee "$LOG_FILE"; then for system_image in "${TARGET_SYSTEMS[@]}"; do
status=0 ((index += 1))
else if run_for_system "$system_image" "$index" "$total"; then
status=$? run_status=0
fi else
run_status=$?
status="$run_status"
fi
done
if ((status == 0)); then print_summary_table
printf '\nTest passed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE"
printf 'Log: %s\n' "$LOG_FILE"
else
printf '\nTest failed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE" >&2
printf 'Log: %s\n' "$LOG_FILE" >&2
print_failure_excerpt
fi
return "$status" return "$status"
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment