Skip to content

Latest commit

 

History

History
210 lines (148 loc) · 13.3 KB

File metadata and controls

210 lines (148 loc) · 13.3 KB

Тестирование расширения pg_query_stack

Структура тестов

Регрессионные тесты выполняются через pg_regress (make installcheck) на работающем сервере:

  • sql/ - входные SQL-файлы, один файл на тест
  • expected/ - ожидаемый вывод psql для каждого теста, сравнивается побайтно
  • results/ - фактический вывод последнего прогона, при расхождении рядом появляется regression.diffs

Комментарии из sql/ psql печатает в вывод, поэтому они входят в expected/: правка комментария в тесте меняет оба файла.

Описание тестов

001_setup - Установка расширения

  • CREATE EXTENSION pg_query_stack, проверка наличия функций pg_query_stack и pg_self_query
  • Единственный тест, который создает расширение, поэтому любой набор начинается с него

002_basic_functionality - Базовый вызов

  • SELECT * FROM pg_query_stack(0) возвращает одну строку: frame_number = 0 и текст самого запроса

003_skip_parameters - Параметр skip_count

  • pg_query_stack(1) и pg_query_stack() (умолчание 1) на верхнем уровне возвращают 0 строк

004_edge_cases - Граничные значения skip_count

  • Большие (1000 и выше) и отрицательные значения обрабатываются без ошибок

005_nested_calls_default - Вложенные вызовы, skip_count по умолчанию

  • Функция вызывает pg_query_stack(), виден только внешний вызов функции

006_nested_calls_full - Вложенные вызовы, полный стек

  • Функция вызывает pg_query_stack(0), видны обе строки: вызов функции и внутренний SELECT

007_error_recovery - Стабильность после ошибки

  • После деления на ноль расширение работает как прежде

008_subtransactions - Субтранзакции

  • EXCEPTION-блоки, вложенные субтранзакции, откаты, серия INFO со снимками стека

009_complex_nesting - Сложные вложения и временные таблицы

  • Вложенные функции, CREATE TEMP TABLE ... AS SELECT * FROM pg_query_stack(...), разные skip_count

010_loop - Нагрузочный цикл

  • 200 итераций, на каждой три TEMP TABLE и вызов стека, проверяется отсутствие ошибок
  • 200, а не больше: каждая TEMP TABLE держит AccessExclusiveLock до конца DO-блока

011_triggers - Триггеры

  • Стек внутри AFTER INSERT/UPDATE триггеров, каскадные операции с временными таблицами, REFERENCING

012_stack_overflow - Глубина больше MAX_QUERY_STACK_DEPTH

  • Рекурсия на 150 уровней при кольце в 100 слотов
  • Глубже лимита фреймы не записываются, в кольце остаются 100 внешних, счет остается согласованным

013_query_length_limit - Длинный текст запроса

  • Литерал в 70 КБ: query_text возвращается целиком, без усечения

014_cte_recursive - CTE и рекурсивные запросы

  • CTE, рекурсивные CTE и вложенные CTE внутри функций видны в стеке корректно

015_after_trigger_error_cleanup - Ошибка в AFTER-триггере

  • Ошибка на этапе ExecutorFinish перехвачена внешним EXCEPTION, завершившийся INSERT в стеке не остается

016_swallowed_subxact_trigger_error - Проглоченная ошибка во вложенном триггере

  • Цепочка AFTER UPDATE -> INSERT -> AFTER INSERT -> EXCEPTION WHEN OTHERS, после отката субтранзакции стек чист

017_lazy_materialize_deep - Глубокая вложенность plpgsql

  • Пять функций f1..f5 с EXECUTE между уровнями, все шесть фреймов имеют непустой текст, номера идут без пропусков

018_subxact_only_cleanup - SubXactCallback как единственная очистка error-path

  • Двойная вложенность EXCEPTION-блоков и CHECK-ошибка в AFTER-триггере, фреймы упавших запросов сняты по снимкам

019_chained_extensions - Цепочка хуков с другим расширением

  • LOAD 'auto_explain' внутри сессии, его хуки становятся снаружи наших, стек внутри запроса корректен

020_audit_trigger_realistic - Аудит-триггер по образцу OmniX

  • BEFORE-триггер с pg_query_stack(0): прямой INSERT, INSERT из функции, bulk INSERT на 50 строк, UPDATE и DELETE

022_columnar_basic - Citus columnar

  • Стек внутри запроса к columnar-таблице, при отсутствии citus_columnar тест пропускается с тем же выводом

023_audit_after_update_pg_self_query - AFTER UPDATE STATEMENT + pg_self_query()

  • Переходные таблицы, pg_self_query() ORDER BY frame_number DESC LIMIT 1 захватывает top-level UPDATE

024_audit_after_delete_pg_self_query - AFTER DELETE STATEMENT + pg_self_query()

  • То же для DELETE с переходной таблицей OLD TABLE

025_audit_nested_function_deepest_visible - UPDATE внутри функции

  • Аудит захватывает сам UPDATE (frame 1), а не вызывающий SELECT fn()

026_cursor_close_order - Порядок CLOSE курсоров

  • Два курсора закрываются в порядке открытия (SQL и plpgsql), стек от порядка CLOSE не зависит

027_refcursor_outlives_caller - refcursor живет дольше вызвавшего SELECT

  • Функция возвращает открытый курсор, SELECT завершен, аудит видит UPDATE, FETCH показывает запрос курсора как фрейм

028_cursor_closed_in_aborted_subxact - CLOSE внутри откатившейся субтранзакции

  • SAVEPOINT / ROLLBACK TO и EXCEPTION-блок: снятый слот курсора не воскресает

029_failed_cursor_never_ends - Курсор со сбоем FETCH

  • FETCH падает внутри субтранзакции, портал становится PORTAL_FAILED и ExecutorEnd не получает, CLOSE и аудит безопасны

030_idle_cursor_not_a_frame - Простаивающий курсор

  • Открытый курсор без FETCH в стеке не виден, аудит получает UPDATE как frame 0, во время FETCH запрос курсора виден

031_holdable_cursor_and_commit_drop - WITH HOLD и сброс на COMMIT

  • Holdable-курсор дочитывается на COMMIT и виден фреймом внутри дочитывания, пять открытых курсоров сбрасываются на COMMIT

032_subxact_snapshot_overflow - Больше 256 вложенных субтранзакций

  • 300 SAVEPOINT через \gexec, ошибка в EXCEPTION-блоке на глубине 301: стек до конца транзакции пуст
  • Сбой внутри функции под живым внешним SELECT, затем PREPARE TRANSACTION или COMMIT: следующая транзакция чистая
  • Ветка PREPARE выполняется только при max_prepared_transactions > 0, иначе тест идет через COMMIT с тем же выводом

Запуск тестов

Локальный сервер

Тестовый PostgreSQL 16 живет в pg_data_test/ на порту 5433. LC_ALL=C обязателен, иначе PG 16 на macOS падает при старте с postmaster became multithreaded during startup.

LC_ALL=C /opt/homebrew/opt/postgresql@16/bin/pg_ctl \
  -D pg_data_test -l pg_data_test/logfile -o "-p 5433" start

Чтобы ветка PREPARE в тесте 032 действительно выполнялась, в postgresql.conf тестового сервера нужен max_prepared_transactions = 10 (или другое значение больше 0).

Сборка и установка

make PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config
make install PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config

После make install сервер нужно перезапустить: загруженная библиотека остается в памяти процессов. make clean удаляет каталог results/ вместе с отслеживаемыми снимками, поэтому перед коммитом их возвращает git checkout -- results/.

Все тесты

LC_ALL=C make installcheck PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config PGPORT=5433

./run_tests.sh делает то же самое вместе со сборкой и установкой (sudo make install, pg_config из PATH) и печатает сводку по каждому тесту.

Один тест

PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config PGPORT=5433 LC_ALL=C ./run_single_test.sh 026_cursor_close_order

Скрипт сам добавляет 001_setup перед выбранным тестом: pg_regress пересоздает базу перед прогоном, а расширение создает только 001_setup. Без скрипта список задается вручную:

LC_ALL=C make installcheck REGRESS="001_setup 026_cursor_close_order" \
  PG_CONFIG=/opt/homebrew/opt/postgresql@16/bin/pg_config PGPORT=5433

Ручной прогон через psql

psql -p 5433 -d your_database
\i sql/001_setup.sql
\i sql/002_basic_functionality.sql

Так вывод не сравнивается с expected/, это только для отладки отдельного сценария.

Интерпретация результатов

Успешное выполнение

# using postmaster on 127.0.0.1, port 5433
ok 1         - 001_setup                                  17 ms
ok 2         - 002_basic_functionality                    13 ms
...
ok 31        - 032_subxact_snapshot_overflow              44 ms
# All 31 tests passed.

Ошибки

При расхождении pg_regress пишет:

  • results/<test>.out - фактический вывод теста
  • regression.diffs - diff между expected/<test>.out и results/<test>.out
  • regression.out - протокол прогона

Падение сервера (Assert в cassert-сборке, SIGSEGV) видно как server closed the connection unexpectedly в results/<test>.out и как TRAP: или terminated by signal в логе сервера. Следующие тесты в этом прогоне могут упасть только из-за восстановления сервера, их надо перепрогнать отдельно.

Добавление новых тестов

  1. Создайте sql/NNN_name.sql. Комментарии в файле попадают в вывод, пишите их в настоящем времени.
  2. Создайте expected/NNN_name.out. Удобно снять вывод psql -X -a -q < sql/NNN_name.sql и выправить значения до ожидаемых, так формат таблиц psql совпадет с pg_regress.
  3. Добавьте имя теста в REGRESS в Makefile, в список test_files в run_tests.sh и в подсказку в run_single_test.sh.
  4. Опишите тест в этом файле.

Требования к детерминизму:

  • Прогон идет с LC_ALL=C, вывод не должен зависеть от локали и от версии PostgreSQL (16, 17 и 18).
  • Тексты запросов в выводе лучше сравнивать через LIKE, left() или длину, а не печатать целиком.
  • Форматирование и пробелы в expected/ должны совпадать с выводом psql побайтно.
  • Ветки, зависящие от окружения (например max_prepared_transactions), оформляются через \if так, чтобы вывод обеих веток совпадал.