Test harness: Lightning invoice tests need a non-VoidWallet backend without hanging teardown #40

Open
opened 2026-06-06 22:19:16 +00:00 by padreug · 0 comments
Owner

What

The libra integration test harness (tests/conftest.py) boots LNbits in-process inside an asgi_lifespan.LifespanManager. Lightning-invoice-generation tests need a real-ish backend (i.e. not VoidWallet, which 500s with "VoidWallet cannot create invoices.").

FakeWallet is the right candidate per CLAUDE.md and works in standalone Python — but switching the test harness to it (settings.lnbits_backend_wallet_class = "FakeWallet" or LNBITS_BACKEND_WALLET_CLASS=FakeWallet) makes the LifespanManager teardown hang indefinitely under anyio's TestRunner. The Lightning-side background tasks (probably one of invoice_listener, internal_invoice_listener, or the FakeWallet pending_invoices notifier) don't unwind cleanly when the event loop is shut down by pytest.

Symptoms

  • Switch lnbits_backend_wallet_class to FakeWallet.
  • Run any test that doesn't even touch Lightning — e.g. test_smoke.py.
  • Pytest reports the test passing, then never exits. The default pytest timeout never fires; only an external timeout 90 SIGTERM kills it.
  • Process inspection shows the pytest worker alive long after the last test assertion.

What we've tried

  • Setting the env var LNBITS_BACKEND_WALLET_CLASS=FakeWallet at the top of conftest.py (before any lnbits import) — env var is picked up but settings are already constructed; pytest's plugin discovery transitively imports lnbits.settings first.
  • Forcing lnbits_settings.lnbits_backend_wallet_class = "FakeWallet" immediately after import — flips the in-memory value successfully (verified via print()), the LNbits boot log then says Funding source: FakeWallet, invoice generation succeeds. But teardown hangs.
  • Adding the override to _settings_cleanup's post-PURE reapply step — same result: works at runtime, hangs at teardown.

What we've shipped instead

tests/test_lightning_api.py keeps 4 tests that don't need invoice generation (auth gates, 404 on unknown payment_hash, 401 on missing auth). The 3 tests that need an actual generated invoice are marked pytest.mark.skip with a NEEDS_LIGHTNING_BACKEND marker pointing at this issue:

  • test_user_can_generate_invoice_for_own_balance
  • test_super_user_can_generate_invoice_for_another_user
  • test_record_payment_pending_invoice_returns_400

The skipped tests carry full implementations — flipping them back on is a one-line change once the teardown issue is fixed.

Possible directions

  1. Find the offending background task in LNbits and ensure it has a proper cancel/await pair on lifespan shutdown. Upstream candidate fix to lnbits, not libra.
  2. Subprocess-based runner for LN tests — launch a separate pytest process per LN test with FakeWallet; no shared event loop with the rest of the suite. Heaviest option, but the most insulated.
  3. Mock LNbits's invoice service at the libra boundary — replace create_payment_request / get_standalone_payment with hand-written async functions that return canned LNbits-shape payloads. No real LNbits Lightning machinery involved. Doesn't catch breakage in libra's call to LNbits, but covers libra's own logic.

(3) is probably the cheapest path back to coverage. (1) is the right long-term fix if the bug is in lnbits.

Scope

  • No production code change. Test-harness only.
  • Verify whether the teardown hang reproduces with stock lnbits/main/tests (it may already be a known LNbits-side issue).
## What The libra integration test harness (`tests/conftest.py`) boots LNbits in-process inside an `asgi_lifespan.LifespanManager`. Lightning-invoice-generation tests need a real-ish backend (i.e. not `VoidWallet`, which 500s with `"VoidWallet cannot create invoices."`). `FakeWallet` is the right candidate per CLAUDE.md and works in standalone Python — but switching the test harness to it (`settings.lnbits_backend_wallet_class = "FakeWallet"` or `LNBITS_BACKEND_WALLET_CLASS=FakeWallet`) makes the `LifespanManager` teardown hang indefinitely under anyio's `TestRunner`. The Lightning-side background tasks (probably one of `invoice_listener`, `internal_invoice_listener`, or the FakeWallet `pending_invoices` notifier) don't unwind cleanly when the event loop is shut down by pytest. ## Symptoms - Switch `lnbits_backend_wallet_class` to `FakeWallet`. - Run any test that doesn't even touch Lightning — e.g. `test_smoke.py`. - Pytest reports the test passing, then never exits. The default `pytest` timeout never fires; only an external `timeout 90` SIGTERM kills it. - Process inspection shows the pytest worker alive long after the last test assertion. ## What we've tried - Setting the env var `LNBITS_BACKEND_WALLET_CLASS=FakeWallet` at the top of `conftest.py` (before any lnbits import) — env var is picked up but settings are already constructed; pytest's plugin discovery transitively imports `lnbits.settings` first. - Forcing `lnbits_settings.lnbits_backend_wallet_class = "FakeWallet"` immediately after import — flips the in-memory value successfully (verified via `print()`), the LNbits boot log then says `Funding source: FakeWallet`, invoice generation succeeds. But teardown hangs. - Adding the override to `_settings_cleanup`'s post-PURE reapply step — same result: works at runtime, hangs at teardown. ## What we've shipped instead `tests/test_lightning_api.py` keeps 4 tests that don't need invoice generation (auth gates, 404 on unknown payment_hash, 401 on missing auth). The 3 tests that need an actual generated invoice are marked `pytest.mark.skip` with a `NEEDS_LIGHTNING_BACKEND` marker pointing at this issue: - `test_user_can_generate_invoice_for_own_balance` - `test_super_user_can_generate_invoice_for_another_user` - `test_record_payment_pending_invoice_returns_400` The skipped tests carry full implementations — flipping them back on is a one-line change once the teardown issue is fixed. ## Possible directions 1. **Find the offending background task** in LNbits and ensure it has a proper cancel/await pair on lifespan shutdown. Upstream candidate fix to lnbits, not libra. 2. **Subprocess-based runner for LN tests** — launch a separate pytest process per LN test with FakeWallet; no shared event loop with the rest of the suite. Heaviest option, but the most insulated. 3. **Mock LNbits's invoice service** at the libra boundary — replace `create_payment_request` / `get_standalone_payment` with hand-written async functions that return canned LNbits-shape payloads. No real LNbits Lightning machinery involved. Doesn't catch breakage in libra's call to LNbits, but covers libra's own logic. (3) is probably the cheapest path back to coverage. (1) is the right long-term fix if the bug is in lnbits. ## Scope - No production code change. Test-harness only. - Verify whether the teardown hang reproduces with stock `lnbits/main/tests` (it may already be a known LNbits-side issue).
padreug referenced this issue from a commit 2026-06-07 13:39:52 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/libra#40
No description provided.