Test Strategy & Suites

Four suites at different layers. All are independent of the full cmake build — each compiles with a single g++ command. Makefile.transport-tests drives them. See also: story/06-testing.

Suite 0 — GTest coretest (cmake-integrated)

File: src/test/TransportTest.cpp → compiled into coretest binary

Run: cd build && ctest -R Coretests --output-on-failure

Covers: pws_is_transport_url, extract_scheme, file: plugin end-to-end via PWScore.

Suite 1 — Standalone transport infrastructure

File: src/test/transport_standalone_test.cpp

Build: g++ -std=c++17 -DDEVELOPMENT -I src -o /tmp/transport_standalone_test src/test/transport_standalone_test.cpp src/os/unix/transport.cpp -ldl

No network required. 9 sections, 59 assertions. Covers plugin loading, scheme detection, file: round-trips, cache path generation, FILE* map, identity pre-scan rejection, roundtrip byte identity.

Suite 2 — WebDAV plugin (live server)

File: src/test/transport_webdav_test.cpp — 11 sections, 59 assertions

Build: g++ -std=c++17 -o /tmp/transport_webdav_test src/test/transport_webdav_test.cpp -ldl

Skips (exit 77) if PWSAFE_WEBDAV_TEST_URL is unset. Run from build/: PWSAFE_WEBDAV_TEST_URL=https://webdav.critchley.biz/test /tmp/transport_webdav_test

Key sections:

• §7 — store while locked: the If: (<token>) header regression. Server returns 423 if the header is missing; this was the original data-loss bug.

• §11 — lock contention: EBUSY when another client holds the lock; failed lock must not populate token_out or affect the internal map.

Suite 3 — Lock lifecycle (standalone + live)

File: src/test/transport_lock_lifecycle_test.cpp — 6 sections (A–F), 49 assertions (15 offline, 34 live)

Build: g++ -std=c++17 -DDEVELOPMENT -I src -o /tmp/transport_lock_lifecycle_test src/test/transport_lock_lifecycle_test.cpp src/os/unix/transport.cpp src/os/unix/transport_lockd.cpp -ldl

Links transport.cpp and transport_lockd.cpp directly — tests the full daemon IPC.

Key sections:

• §A — registry unit tests: pws_lock_register/has_lock/unregister in isolation, no network

• §C — crash simulation: registry cleared without plugin unlock → server lock still held; subsequent lock() returns EBUSY

• §D — SafeUnlockFile simulation: the exact guard (if IsLockedFile → UnlockFile) that was broken before the fix

• §E — daemon EBUSY path: lock contention propagated correctly through the pipe round-trip

• §F — store regression: t->store() directly in parent → EBUSY (demonstrates the bug); pws_lockd_store() → 0 (demonstrates the fix)

Running the suites

# Offline tests only (default):
make -f Makefile.transport-tests

# All suites with a local wsgidav server (no remote dependency):
make -f Makefile.transport-tests local-live

# All suites against the remote server:
make -f Makefile.transport-tests live

.NOTPARALLEL in the Makefile ensures suites run sequentially — they share /tmp paths and ~/.cache/pwsafe/, and suite 3 forks the lock daemon which would conflict with a concurrent run.

The local wsgidav server

Files: src/test/webdav_test_server.py + src/test/run_local_webdav_tests.sh

Uses wsgidav 4.3.3 + cheroot 11.1.2. DAV class 2 (locking), anonymous write access. Pre-seeds pwsafe_test.psafe3 (required by suite 2 §2 and §4). Prints READY once the port accepts connections.

run_local_webdav_tests.sh starts the server, polls for readiness, runs suites 2 and 3 with PWSAFE_WEBDAV_TEST_URL=http://127.0.0.1:18080, tears down via trap cleanup EXIT.

Install wsgidav (if needed): ALL_PROXY=socks5h://127.0.0.1:1080 pip install --user --break-system-packages wsgidav cheroot

version2
updated2026-02-27