Test coverage

make coverage measures how well the test suite exercises ProvSQL, at two granularities:

  • Function-call coverage (plpgsql + C). With track_functions = all, the server records a call count per function in pg_stat_user_functions. After the run, coverage/zero_call.txt lists provsql plpgsql/C functions with zero calls (from zero_call_functions.sql). The coverage run drops the extension_upgrade test from the schedule, because its DROP EXTENSION would purge these counters (they are keyed by function OID).

    pg_stat_user_functions only counts functions entered through the executor, so zero_call_functions.sql filters out the categories it structurally never counts, leaving genuine coverage gaps: LANGUAGE sql functions (inlined), type input/output/cast functions and aggregate support functions (detected from the catalogs), and ProvSQL’s planner-rewritten placeholders/markers — provenance(), the comparison operators, the | (predicate) conditioning forms — which are declared for the parser but rewritten away before execution (listed explicitly in the query; keep it in sync with the source’s “Never executes” / “Placeholder” comments). A few C rows reached only via DirectFunctionCall/SPI from the C core can still slip through, so cross-check a C entry against the gcovr line report; plpgsql rows are reliable.

  • C/C++ line and branch coverage. The extension is rebuilt with gcov instrumentation (--coverage, LTO off, -O0); gcovr then produces a per-file line and branch report under coverage/ (open index.html).

make coverage needs gcovr. Install it isolated with pipx install gcovr (a system gcovr can clash with an unrelated jinja2 in ~/.local); override with make coverage GCOVR=/path/to/gcovr if needed.

How it works

Measuring coverage against a shared PostgreSQL server is awkward: provsql is in shared_preload_libraries, so the postmaster holds the loaded .so (an instrumented build only takes effect after a restart); the gcov .gcda files are written by the backend processes next to the .gcno under src/, so the server must run as the user who built the tree; the long-lived postmaster / background-worker counters only flush on a clean stop; and installing an instrumented build system-wide needs root and disturbs the running server.

test/coverage/run-coverage.sh sidesteps all of that. It stages the instrumented extension into a private prefix under /tmp with make install DESTDIR=... (no sudo, nothing written to the system PostgreSQL), then runs the suite against a throwaway cluster it creates under /tmp/provsql_coverage, owned by whoever runs make coverage. The cluster is pointed at the staged build with extension_control_path (for CREATE EXTENSION), dynamic_library_path, and an absolute shared_preload_libraries; the staged control file’s module_pathname is rewritten to the staged .so so the extension’s C functions load the instrumented copy. It sets track_functions=all, runs the regression schedule (installcheck, under the tdkc supervisor), and is stopped cleanly so every .gcda is flushed before gcovr runs.

This relies on extension_control_path, so it needs PostgreSQL >= 18. Override the cluster/staging location or port with PROVSQL_COVERAGE_DIR / PROVSQL_COVERAGE_STAGE / PROVSQL_COVERAGE_PORT.

After a run

Nothing is installed into the system PostgreSQL, so there is nothing to undo there. The local build tree is left instrumented; rebuild the normal optimised objects when you next need them with:

make

Troubleshooting

  • gcovr reports 0 % everywhere / no .gcda. The backends could not write .gcda (wrong user) or ran an uninstrumented library. The temp-cluster script avoids both; if you measure by hand against your own server instead, it must run the instrumented build and run as the user who owns src/.
  • ImportError: cannot import name 'Markup' from 'jinja2'. An old gcovr against jinja2 >= 3.1. Use a pipx-installed gcovr and point the target at it with GCOVR=.

plpgsql line/branch coverage (not wired in)

pg_stat_user_functions gives plpgsql functions only a call count, not statement coverage. For per-statement / per-branch plpgsql coverage, install plpgsql_check and use its profiler (plpgsql_coverage_statements(), plpgsql_coverage_branches()) over the regression database after a run.