fix(tests): сторож документации называет непрошедшую проверку, кластерная больше не виснет

Зачем.
Сторож `tests/docs-guards.sh` падал через `set -e`: единственным следом была
единица в коде возврата, а какое именно утверждение о README перестало быть
правдой — не сообщалось. Со стороны файл выглядел набором несвязанных grep
без объяснения, зачем каждый из них нужен.

Отдельно: `make smoke-cluster` мог зависнуть навсегда. У запроса INSERT
clickhouse-client дочитывает данные из стандартного ввода и ждёт его конца;
при запуске не из терминала, а из фонового процесса с открытым вводом конец
не наступает никогда. Проверка молча висела больше двадцати минут.

Что.
- Проверки собраны в именованные функции, каждая с комментарием, зачем она
  существует и что ломается, когда она краснеет.
- Обёртка `check` печатает утверждение и при успехе, и при провале, считает
  пройденные и выходит с понятным сообщением.
- Ввод запросов ClickHouse закрыт через `</dev/null`, рядом — объяснение
  причины.
- README: раздел «Какую проверку когда запускать» — лесенка от дешёвой
  статической проверки к дорогой интеграционной, с ответом, зачем внутри
  `make smoke-guards` три прогона `make smoke`.

Проверка.
make config-test — пройдено 3, 3 и 6, ошибок 0.
Фальсификация сторожа: порт ноды 2 в README изменён — «ОШИБКА: не
подтвердилось: README перечисляет HTTP- и нативные порты обеих нод», код 1;
двоеточие в строке про перезапуск ClickHouse заменено на тире — «ОШИБКА: не
подтвердилось: README требует перезапуск ClickHouse после изменения настройки
метрик», код 1. README восстановлен из индекса.
make smoke-cluster с открытым стандартным вводом — 8 проверок за 7 с; до
починки та же команда висела 23 минуты и была снята вручную.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 14:08:47 +03:00
co-authored by Claude Opus 5
parent 64f3418378
commit 812b398ce2
3 changed files with 135 additions and 41 deletions
+21
View File
@@ -67,6 +67,27 @@ Superset, создаёт администратора и импортирует
останавливает Prometheus с Kafka. Общая проверка должна назвать Prometheus и останавливает Prometheus с Kafka. Общая проверка должна назвать Prometheus и
оба пробника, после чего завершиться с ошибкой. В конце стенд восстанавливается. оба пробника, после чего завершиться с ошибкой. В конце стенд восстанавливается.
### Какую проверку когда запускать
Проверки выстроены лесенкой: чем дороже прогон, тем больше связей он трогает.
- `make config-test` — секунды, стенд поднимать не нужно. Видит только то, что
есть в файлах, и о работоспособности не говорит ничего. Дёшево настолько, что
можно гонять перед каждым коммитом.
- `make smoke` — минута-две на поднятом стенде. Дороже, но проверяет связи
между службами, а не отдельные файлы: это интеграционная проверка.
- `make smoke-cluster` — около минуты. Одна связь, зато до дна: межнодовое
устройство ClickHouse.
- `make smoke-guards` — около пяти минут, и отвечает на другой вопрос. Не
«работает ли стенд», а «умеют ли проверки падать»: она намеренно ломает стенд
и смотрит, покраснеет ли `make smoke` и назовёт ли виновника, потом чинит и
убеждается, что стенд снова зелёный. Отсюда и три прогона `make smoke`
внутри — до поломки, во время неё и после починки.
Обычный рабочий цикл — `make config-test` и `make smoke`. `make smoke-guards`
нужна тому, кто правит сами проверки или пробники: без неё легко завести
проверку, которая зелена всегда.
Остановить контейнеры без удаления данных можно командой `make down`. Для Остановить контейнеры без удаления данных можно командой `make down`. Для
полного сброса с удалением всех именованных томов используйте `make clean`. полного сброса с удалением всех именованных томов используйте `make clean`.
Повторный `make up` безопасен: одноразовая подготовка приложений идемпотентна. Повторный `make up` безопасен: одноразовая подготовка приложений идемпотентна.
+5 -1
View File
@@ -10,10 +10,14 @@ compose() {
docker compose --project-directory "$ROOT_DIR" "$@" docker compose --project-directory "$ROOT_DIR" "$@"
} }
# Ввод закрыт намеренно: у запроса INSERT clickhouse-client дочитывает данные
# из стандартного ввода и ждёт его конца. Если проверку запустили не из
# терминала, а из фонового процесса с открытым вводом, конца не наступает
# никогда, и команда висит без сообщений.
query() { query() {
local service="$1" local service="$1"
local sql="$2" local sql="$2"
compose exec -T "$service" clickhouse-client --query "$sql" compose exec -T "$service" clickhouse-client --query "$sql" </dev/null
} }
query_with_timeout() { query_with_timeout() {
+93 -24
View File
@@ -1,48 +1,117 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Сторож документации.
#
# README устаревает молча: порт поменяли в compose.yaml, а в описании остался
# старый — и это выясняется через месяц, когда кто-то по нему подключается.
# Здесь собраны утверждения README, которые дёшево проверить текстом и дорого
# обнаружить сломанными. Стенд поднимать не нужно; запускается в составе
# `make config-test`.
#
# Чего сторож НЕ делает: он не проверяет, что README понятен или полон. Только
# то, что перечисленные ниже факты не разошлись с кодом.
#
# Добавляя проверку, формулируй утверждение так, как оно должно читаться в
# отчёте: строка печатается и при успехе, и при провале, поэтому по красной
# строке сразу видно, что именно перестало быть правдой.
set -euo pipefail set -euo pipefail
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly README="$ROOT_DIR/README.md" readonly README="$ROOT_DIR/README.md"
readonly SMOKE="$ROOT_DIR/scripts/stand-smoke.sh"
passed=0
grep -Eq 'нода 1.*28123.*29000' "$README" fail() {
grep -Eq 'нода 2.*28124.*29001' "$README" printf 'ОШИБКА: %s\n' "$1" >&2
printf 'ЗЕЛЁНО: README перечисляет HTTP- и нативные порты обеих нод.\n' exit 1
}
grep -Eq 'После первого запуска.*`make clean`' "$README" # check «утверждение» команда... — запускает команду и считает результат.
printf 'ЗЕЛЁНО: README объясняет сброс томов после смены исходных учётных данных.\n' # Успех: «ЗЕЛЁНО: утверждение». Провал: «ОШИБКА: не подтвердилось: утверждение»
# и выход с кодом 1. Раньше проверки падали через `set -e` молча: код возврата
# был единственным следом, и какая именно проверка не прошла — не сообщалось.
check() {
local claim="$1"
shift
if "$@"; then
passed=$((passed + 1))
printf 'ЗЕЛЁНО: %s\n' "$claim"
else
fail "не подтвердилось: $claim"
fi
}
grep -Fxq -- '- нода 2 — `http://127.0.0.1:28124`, нативный порт `29001`;' "$README" # Порты обеих нод. Ломается ровно тогда, когда порт поменяли в compose.yaml и
printf 'ЗЕЛЁНО: список портов остаётся единым списком.\n' # забыли документацию — самая частая причина расхождения.
ports_documented() {
grep -Eq 'нода 1.*28123.*29000' "$README" &&
grep -Eq 'нода 2.*28124.*29001' "$README"
}
awk ' # Совет про сброс томов. Пароли Postgres и Grafana применяются при создании
# тома: без `make clean` смена значений в .env ничего не даёт, и человек
# полчаса ищет, почему его не пускает.
clean_advice_present() {
grep -Eq 'После первого запуска.*`make clean`' "$README"
}
# Список портов остаётся единым списком. Совпадение точное намеренно: проверка
# стережёт не сам факт (он проверен выше), а то, что строку не выдернули из
# списка в отдельный абзац при правке соседнего текста.
ports_stay_one_list() {
grep -Fxq -- '- нода 2 — `http://127.0.0.1:28124`, нативный порт `29001`;' "$README"
}
# Состав `make config-test`. Разбор пробников добавлен в него отдельной
# проверкой, и README должен называть её: иначе читатель считает, что дешёвая
# ступень трогает только Compose, и гоняет полный стенд ради того, что видно
# без него. Проверка стоит на двух соседних строках — см. ниже про перенос.
probe_checks_documented() {
awk '
previous == "`make config-test` проверяет Compose, синтаксис Bash и Python, малые проверки" && previous == "`make config-test` проверяет Compose, синтаксис Bash и Python, малые проверки" &&
$0 == "логики пробников и пробельные ошибки в diff без запуска стенда." { $0 == "логики пробников и пробельные ошибки в diff без запуска стенда." {
found = 1 found = 1
} }
{previous = $0} {previous = $0}
END {exit !found} END {exit !found}
' "$README" ' "$README"
printf 'ЗЕЛЁНО: README перечисляет малые проверки пробников в составе config-test.\n' }
awk ' # Урок из предшественника: `make up` не трогает уже созданные контейнеры, и
# после правки настройки метрик серверы молча работают со старой
# конфигурацией. README обязан требовать явный перезапуск. Проверка сверяет
# две соседние строки целиком, а не подстроку: так фразу нельзя незаметно
# разорвать переносом или переписать наполовину.
restart_lesson_present() {
awk '
previous == "После изменения `infra/clickhouse/config.d/prometheus.xml` выполните" && previous == "После изменения `infra/clickhouse/config.d/prometheus.xml` выполните" &&
$0 == "`docker compose restart clickhouse-01 clickhouse-02`: обычный `make up` не" { $0 == "`docker compose restart clickhouse-01 clickhouse-02`: обычный `make up` не" {
found = 1 found = 1
} }
{previous = $0} {previous = $0}
END {exit !found} END {exit !found}
' "$README" ' "$README"
printf 'ЗЕЛЁНО: README требует перезапуск ClickHouse после изменения настройки метрик.\n' }
grep_status=0 # Единица памяти в отчёте проверки — русская «ГБ», а не латинская «GB»
grep -q '3,4 GB' "$ROOT_DIR/scripts/stand-smoke.sh" || grep_status=$? # (контракт языка из AGENTS.md). Здесь успех — это отсутствие образца, поэтому
if [[ "$grep_status" -eq 0 ]]; then # код возврата grep разбирается вручную: 1 — не нашли, и это хорошо; 0 — нашли
printf 'ОШИБКА: отчёт проверки использует латинское обозначение GB.\n' >&2 # латинское; больше 1 — сам grep не отработал, и молчать об этом нельзя.
exit 1 smoke_uses_russian_unit() {
elif [[ "$grep_status" -ne 1 ]]; then local status=0
printf 'ОШИБКА: не удалось проверить обозначение единицы памяти.\n' >&2 grep -q '3,4 GB' "$SMOKE" || status=$?
exit 1 case "$status" in
fi 1) return 0 ;;
printf 'ЗЕЛЁНО: отчёт проверки использует русское обозначение ГБ.\n' 0) return 1 ;;
*) fail "не удалось проверить обозначение единицы памяти в $SMOKE" ;;
esac
}
printf 'ИТОГ: пройдено 6, ошибок 0\n' check 'README перечисляет HTTP- и нативные порты обеих нод' ports_documented
check 'README объясняет сброс томов после смены исходных учётных данных' clean_advice_present
check 'список портов остаётся единым списком' ports_stay_one_list
check 'README перечисляет малые проверки пробников в составе config-test' probe_checks_documented
check 'README требует перезапуск ClickHouse после изменения настройки метрик' restart_lesson_present
check 'отчёт проверки использует русское обозначение ГБ' smoke_uses_russian_unit
printf 'ИТОГ: пройдено %d, ошибок 0\n' "$passed"