Skip to content

test: cover the web layer, which the sanitizer build only appeared to… - #46

Merged
IronsDu merged 2 commits into
mainfrom
feat/web-layer-leak-coverage
Sep 18, 2026
Merged

IronsDu merged 2 commits into
mainfrom
feat/web-layer-leak-coverage

Conversation

@IronsDu

@IronsDu IronsDu commented Sep 17, 2026

Copy link
Copy Markdown
Owner

… cover

Every test target was core-only. test_full_flow links profiler_web, but that is a conditional link in CMake and the test references nothing from it, so drogon_adapter.cpp and web_resources.cpp were compiled under ASan and executed by nothing ctest ran -- the only thing that ran them was profiler_example. A sanitizer build that includes a translation unit and never executes it reports no leaks from it, which reads as coverage.

tests/test_web_layer.cpp starts the real server, drives it over HTTP and shuts it down, so route registration, the async delivery path, the embedded pages and the shutdown sequence all execute under whatever sanitizer configuration is active. A detail that gives the async assertions their meaning: the response is written from another thread, so a reply arriving at all proves the AsyncExecutor/queueInLoop hand-off completed rather than merely that a handler ran.

Two things learned while writing it, both now in the file:

  • The server starts once per suite, not per test. drogon::app() is a process-global singleton whose lifecycle is bind-once; a per-test SetUp/TearDown segfaults inside ListenerManager::addListener on the second test. I wrote it that way first and hit exactly that.
  • Binding port 0 and reading the port back from getListeners() keeps parallel ctest runs from colliding, so no fixed port and no RESOURCE_LOCK.

Requests use a raw socket instead of libcurl, to avoid a dependency that exists only for tests.

Also sets exitcode=86 in the sanitizer environment, in CI and in scripts/check-sanitizers.sh. This is a reading aid, not a behaviour change: LSan exits 1 by default, which is also what a test returns on a failed assertion, so the log currently cannot say which of the two happened. Worth recording why the flag is needed at all -- detect_leaks=1 alone prints the report and still exits 0, which CI reads as a pass; halt_on_error=1 is what turns a leak into a failing run.

Verified: 9/9 serial and with ctest -j6 in the normal build, 9/9 under ASan with detect_leaks=1:halt_on_error=1:exitcode=86 and the project's suppressions, no leak reported for the new test, and the exit-code semantics confirmed against a probe with an injected leak (86 with the leak, 0 without).

… cover

Every test target was core-only. test_full_flow links profiler_web, but that is a
conditional link in CMake and the test references nothing from it, so
drogon_adapter.cpp and web_resources.cpp were compiled under ASan and executed by
nothing ctest ran -- the only thing that ran them was profiler_example. A sanitizer
build that includes a translation unit and never executes it reports no leaks from
it, which reads as coverage.

tests/test_web_layer.cpp starts the real server, drives it over HTTP and shuts it
down, so route registration, the async delivery path, the embedded pages and the
shutdown sequence all execute under whatever sanitizer configuration is active. A
detail that gives the async assertions their meaning: the response is written from
another thread, so a reply arriving at all proves the AsyncExecutor/queueInLoop
hand-off completed rather than merely that a handler ran.

Two things learned while writing it, both now in the file:

- The server starts once per suite, not per test. `drogon::app()` is a process-global
  singleton whose lifecycle is bind-once; a per-test SetUp/TearDown segfaults inside
  ListenerManager::addListener on the second test. I wrote it that way first and hit
  exactly that.
- Binding port 0 and reading the port back from getListeners() keeps parallel ctest
  runs from colliding, so no fixed port and no RESOURCE_LOCK.

Requests use a raw socket instead of libcurl, to avoid a dependency that exists only
for tests.

Also sets `exitcode=86` in the sanitizer environment, in CI and in
scripts/check-sanitizers.sh. This is a reading aid, not a behaviour change: LSan
exits 1 by default, which is also what a test returns on a failed assertion, so the
log currently cannot say which of the two happened. Worth recording why the flag is
needed at all -- `detect_leaks=1` alone prints the report and still exits 0, which CI
reads as a pass; `halt_on_error=1` is what turns a leak into a failing run.

Verified: 9/9 serial and with ctest -j6 in the normal build, 9/9 under ASan with
detect_leaks=1:halt_on_error=1:exitcode=86 and the project's suppressions, no leak
reported for the new test, and the exit-code semantics confirmed against a probe with
an injected leak (86 with the leak, 0 without).
…both compilers

CI failed on the new web layer test:

  test_web_layer.cpp:190:8: error: suggest explicit braces to avoid ambiguous
  'else' [-Werror=dangling-else]

`EXPECT_TRUE(...) << ...` expands to an if/else, so an unbraced if right after it
dangles. Braces added.

I had not seen this locally because the -Werror configuration exists only in the
warnings-check job, which builds with the default GCC and -Wall -Wextra -Werror,
while my local builds carried no warning flags at all. Reproducing that job locally
turned up two more failures in the same configuration:

- test_full_flow.cpp: `sink` in burnCPUFor() and the older `result` in the gperftools
  test are "set but not used". volatile does not suppress that warning -- GCC sees
  through it. Both now feed an assertion (burnCPUFor() returns the accumulation and
  callers EXPECT_GT it), so the work is observable and nothing can elide the loop,
  rather than being silenced with a cast or an attribute.
- test_logger.cpp: clang rejects an unused lambda capture,
  [-Wunused-lambda-capture]. It was not actually unused: the lambda parameter `i`
  shadowed the captured `messages_per_thread`, so the capture never got read inside
  the body. Fixed by naming the loop variable `msg` and dropping the capture, which
  removes the shadowing too. Pre-existing, and invisible to the GCC job that exists
  today -- I found it only by running the same flags through clang.

Verified under all four configurations: GCC and clang with -Wall -Wextra -Werror
(0 warnings, 9/9 tests each), the normal build serial and with ctest -j6, and ASan
with the project's leak settings.
@IronsDu
IronsDu merged commit bc63b7d into main Sep 18, 2026
10 checks passed
@IronsDu
IronsDu deleted the feat/web-layer-leak-coverage branch September 18, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant