Skip to content

Unit & Integration Test Coverage

Current state of test coverage and gaps to fill.


Summary

CrateUnit (src/)Integration (tests/)Stub-modeTotal
ephpm-config1901919
ephpm-php40 (18 + 22 php_linked)01840
ephpm-server84 (79 + 5 php_linked)08484
ephpm-kv12549 (48 + 1 doctest)174174
ephpm-db5055
ephpm03 (php_linked)03
Total27352300325

“Stub-mode” = tests that run without PHP linked (the default cargo test experience).


ephpm-config (19 tests)

File: crates/ephpm-config/src/lib.rs

TestWhat it covers
test_default_configDefault configuration loads correctly
test_load_valid_tomlParses complete TOML config
test_load_partial_toml_fills_defaultsMissing fields use defaults
test_load_missing_file_uses_defaultsNon-existent file falls through to defaults
test_env_var_overrides_tomlEPHPM_ env vars override TOML values
test_env_var_override_without_fileEnv vars work standalone
test_ini_overrides_from_tomlPHP ini_overrides loaded from TOML
test_php_etag_cache_defaultsETag cache default values
test_php_etag_cache_from_tomlETag cache from TOML
test_php_etag_cache_indefinite_ttlETag cache with ttl=-1 (indefinite)
test_kv_compression_defaultsKV compression default settings
test_kv_compression_gzip_from_tomlGzip compression config
test_kv_compression_zstd_from_tomlZstd compression config
test_kv_compression_brotli_from_tomlBrotli compression config
test_env_var_overrides_php_etag_cacheEnv vars override ETag cache
test_env_var_overrides_kv_compressionEnv vars override KV compression
test_env_var_overrides_compression_levelEnv var overrides compression level
test_env_var_overrides_compression_min_sizeEnv var overrides min size
test_combined_php_etag_and_compression_configCombined ETag + compression config

ephpm-php (40 tests total, 18 stub-mode)

request.rs (12 tests)

TestWhat it covers
test_server_variables_core_fieldsREQUEST_METHOD, REQUEST_URI, SERVER_NAME, SERVER_PORT
test_server_variables_script_pathsSCRIPT_FILENAME, DOCUMENT_ROOT, SCRIPT_NAME
test_server_variables_rewritten_requestFallback rewrite sets correct paths
test_server_variables_http_header_mappingHTTP headers to $_SERVER mapping
test_server_variables_host_headerHost header to HTTP_HOST
test_server_variables_content_type_no_http_prefixCONTENT_TYPE (no HTTP_ prefix)
test_server_variables_content_length_no_http_prefixCONTENT_LENGTH header
test_server_variables_https_onHTTPS=on when is_https=true
test_server_variables_https_absent_when_falseHTTPS absent when is_https=false
test_cookie_string_foundCookie header extracted
test_cookie_string_missingMissing cookie returns empty
test_cookie_string_case_insensitiveCase-insensitive cookie lookup

lib.rs (6 stub-mode tests)

TestWhat it covers
test_stub_init_succeedsInit succeeds in stub mode
test_stub_init_is_idempotentDouble init is safe
test_stub_shutdown_after_initShutdown after init
test_stub_shutdown_without_initShutdown without init is no-op
test_stub_execute_returns_stub_htmlStub execution returns placeholder HTML
test_stub_execute_without_init_returns_not_initializedNot-initialized error

kv_bridge.rs (22 tests, all php_linked)

TestWhat it covers
get_missing_returns_zerokv_get on missing key
set_and_get_round_tripSet/get value round-trip
get_result_reflects_thread_local_after_getThread-local buffer state
set_stores_valuekv_set stores correctly
set_with_ttl_stores_value_with_expiryTTL sets expiry
set_with_zero_ttl_stores_without_expiryZero TTL = no expiry
set_handles_binary_valueBinary data including null bytes
del_existing_returns_oneDelete existing key
del_missing_returns_zeroDelete missing key
exists_present_returns_oneEXISTS present key
exists_absent_returns_zeroEXISTS absent key
incr_by_creates_keyINCR creates new key
incr_by_delta_accumulatesINCR accumulates
incr_by_negative_decrementsNegative delta decrements
incr_by_non_integer_returns_zeroNon-integer error
expire_sets_ttl_on_existing_keyEXPIRE sets TTL
expire_on_missing_key_returns_zeroEXPIRE on missing
expire_zero_or_negative_returns_zeroInvalid TTL
pttl_no_expiry_returns_minus_onePTTL no-expiry
pttl_missing_key_returns_minus_twoPTTL missing
pttl_with_expiry_returns_positivePTTL with expiry
get_buffer_is_thread_localThread isolation

ephpm-server (84 tests: 62 router + 11 static + 7 tls + 8 lib, minus 5 php_linked = 79 stub-mode)

router.rs (62 tests, 57 stub-mode + 5 php_linked)

GroupCountCoverage
Fallback resolution9Static file, PHP file, directory index (php+html), permalink fallback, 404 fallback, missing php, no-index fallback, subdirectory index
Helpers3Variable expansion, path/query splitting, PHP file detection
Security: hidden files3Dotfile detection (.env, .git, .htaccess), dot-only not hidden, deep nesting
Security: blocked paths5Exact path, wildcard directory, extension wildcard, empty list, multiple patterns
Security: PHP allow3Empty=all allowed, exact match, wildcard directory
Compression7Small body skip, non-compressible type, HTML, custom min_size, JSON, SVG, binary not compressed
is_compressible()3Text types, application types (JS/JSON/XML/SVG), binary rejection
segment_match()5Exact, star-any, prefix-star, suffix-star, prefix-star-suffix
is_php_file()2Case insensitive, non-PHP extensions
Trusted proxies / XFF2Rightmost untrusted IP, all-trusted leftmost fallback
Port parsing3Listen address, default on invalid, IPv6 listen
Glob matching5Exact, single segment, directory catch, directory prefix, no-wildcard exact
ETag caching8Cache key (with/without query), exact/wildcard/comma/whitespace match, empty match, strong ETag
PHP-linked ETag5Store on first req, 304 on match, no-cache bypass, POST skip, content change (php_linked)

static_files.rs (11 tests)

TestWhat it covers
test_serve_html_fileHTML content-type
test_serve_css_fileCSS content-type
test_serve_content_length_headerContent-Length set
test_serve_unknown_extensionFalls back to application/octet-stream
test_serve_missing_file_returns_404404 for missing
test_serve_path_traversal_blocked../ blocked
test_serve_javascript_fileJavaScript content-type
test_serve_png_imagePNG image content-type
test_serve_empty_fileEmpty file returns Content-Length: 0
test_serve_nested_pathNested directory paths
test_serve_binary_file_intactBinary data integrity (all 256 bytes)

tls.rs (7 tests)

TestWhat it covers
load_valid_rsa_cert_and_keyLoad RSA cert + key
load_valid_ec_cert_and_keyLoad EC cert + key
missing_cert_file_returns_errorMissing cert file error
missing_key_file_returns_errorMissing key file error
invalid_cert_pem_returns_errorMalformed cert error
invalid_key_pem_returns_errorMalformed key error
mismatched_cert_key_returns_errorCert/key mismatch error

lib.rs (8 tests)

TestWhat it covers
parse_memory_size_megabytes“256MB” -> 268435456
parse_memory_size_gigabytes“1GB” -> 1073741824
parse_memory_size_kilobytes“512KB” -> 524288
parse_memory_size_bytes_no_suffix“1024” -> 1024
parse_memory_size_lowercase“256mb” -> 268435456
parse_memory_size_with_whitespace" 256MB " trimming
parse_memory_size_invalid“notanumber” returns error
parse_memory_size_zero“0” -> 0

ephpm-kv (174 tests: 125 unit + 48 integration + 1 doctest)

command.rs (67 unit tests)

GroupCountCoverage
Connection5PING (bare, with message), ECHO, SELECT, QUIT, COMMAND
GET/SET12Round-trip, overwrite, EX/PX TTL, NX/XX flags, SET…GET option, SETEX (valid, invalid TTL, wrong args), SETNX
MSET/MGET2Multi-key set/get, odd-args error
DEL/EXISTS5Single/multi-key delete, existence checks, multiple keys count
INCR/DECR7Create, increment, decrement, INCRBY, DECRBY, non-integer value error, non-integer delta error
APPEND/STRLEN/GETSET5Create, concatenate, length, atomic swap, missing key
TTL/EXPIRE9TTL/PTTL semantics, EXPIRE/PEXPIRE set and missing, PERSIST remove + no-ttl, TYPE existing + missing
RENAME3Rename existing, missing key error, TTL preservation
KEYS/DBSIZE/FLUSH5Wildcard, pattern filter, count, FLUSHDB, FLUSHALL
INFO1Returns bulk with redis_version
Error handling7Unknown command, missing args (GET/SET/DEL/MGET/MSET), invalid expire time, case insensitivity
Additional6Edge cases: missing args for individual commands

store/mod.rs (39 unit tests)

GroupCountCoverage
Basic operations6set/get, missing key, overwrite, delete, exists, ttl_expiry
TTL / PTTL2PTTL no-expiry (-1), PTTL missing (None)
Increment2Counter increment, non-integer error
Append2Create key, concatenate existing
Flush1Clear all keys + mem_used resets
Pattern matching2KEYS with pattern, glob matching
Expiry pass (GC)1Cleanup removes expired entries
Compression6Gzip/Brotli/Zstd round-trip, below-min-size, INCR+APPEND on compressed
Eviction: policy parsing1All 4 variants + unknown fallback to AllKeysLru
Eviction: AllKeysLru3Evicts to make room, evicts oldest-accessed key, frees multiple keys
Eviction: VolatileLru2Only evicts TTL keys, fails with only persistent keys
Eviction: AllKeysRandom1Random eviction makes room
Eviction: NoEviction2Rejects writes when full, rejects at limit
Eviction: edge cases2Empty store fails, unlimited memory accepts any
Memory tracking3Insert/remove tracking, overwrite tracking, flush resets
Glob matching3? wildcard, combined */?, empty pattern

resp/frame.rs (7 unit tests)

Serialization of RESP frames: simple string, error, integer, bulk, null, array, empty bulk.

resp/parse.rs (12 unit tests)

Parsing of RESP wire format: simple/error/integer/bulk/null/array, empty array, incomplete input, invalid type byte, buffer consumption.

tests/resp_compat.rs (48 integration tests)

Full RESP protocol over TCP: PING, SET/GET (incl. binary, NX, XX, EX, PX, GET option), MSET/MGET, DEL, EXISTS, INCR/DECR/INCRBY/DECRBY, APPEND/STRLEN/GETSET, TTL/EXPIRE/PEXPIRE/PERSIST, TYPE, KEYS/DBSIZE/FLUSHDB/FLUSHALL, INFO, two-connection shared data, pipeline, SETNX, error handling.


ephpm-db (5 tests)

FileTestsCoverage
duration.rs2Parse valid durations (ms/s/m/h), reject invalid
url.rs3MySQL URL, PostgreSQL URL with encoded password, default ports

ephpm (3 integration tests, php_linked only)

File: crates/ephpm/tests/kv_sapi_integration.rs

TestWhat it covers
kv_sapi_set_getPHP KV set/get through SAPI
kv_sapi_delPHP KV delete through SAPI
kv_sapi_allFull KV SAPI integration

Coverage Matrix

AreaTestsStatus
Config: TOML, env vars, defaults19Good
PHP: $_SERVER variable mapping12Good
PHP: Stub mode init/execute/shutdown6Good
PHP: KV bridge (C FFI)22Good (php_linked)
Server: Routing fallback resolution9Good
Server: Security (hidden files, blocked paths, PHP allow)11Good
Server: Compression (gzip + is_compressible)10Good
Server: Trusted proxy / XFF2Good
Server: Port parsing3Good
Server: Glob matching5Good
Server: ETag caching10Good
Server: segment_match()5Good
Server: is_php_file()2Good
Server: Static file serving11Good
Server: TLS cert loading7Good
Server: Memory size parsing8Good
KV: Redis commands67Good
KV: In-memory store + compression23Good
KV: Eviction policies11Good
KV: Memory tracking3Good
KV: Glob matching3Good
KV: RESP serialize/parse19Good
KV: RESP integration (TCP)48Good
DB: URL/duration parsing5Good
Server: ACME provisioning0Gap
Cluster: Config parsing0Gap (struct not yet added)
Cluster: Gossip protocol0Gap (crate not yet created)
Cluster: Clustered store routing0Gap (crate not yet created)
CLI: Argument parsing0Gap (needs openssl-sys)
PHP: Thread pool (ZTS)0Gap (php_linked only)
PHP: SAPI callbacks0Gap
Server: Graceful shutdown0Gap
Server: Connection handling0Gap

Tests To Build

High Priority — Testable Now

1. TLS Certificate Loading (ephpm-server/src/tls.rs) – DONE

  • Load valid PEM certificate (RSA)
  • Load valid PEM certificate (EC)
  • Reject invalid/malformed certificate
  • Reject invalid/malformed key
  • Reject mismatched cert/key pair
  • Missing cert file returns clear error
  • Missing key file returns clear error

2. Memory Size Parsing (ephpm-server/src/lib.rs) – DONE

  • Parse “256MB” -> bytes
  • Parse “1GB” -> bytes
  • Parse “512KB” -> bytes
  • Parse raw bytes (no suffix)
  • Lowercase suffix (“256mb”)
  • Whitespace trimming (" 256MB “)
  • Invalid input returns error
  • Zero returns 0

3. KV Eviction Policies (ephpm-kv/src/store/mod.rs) – DONE

  • EvictionPolicy from-string parsing (all 4 variants + unknown fallback)
  • AllKeysLru: evicts to make room for new write
  • AllKeysLru: evicts least-recently-accessed key (not just oldest)
  • VolatileLru: only evicts keys with TTL set
  • VolatileLru: fails when only persistent keys exist
  • AllKeysRandom: random eviction succeeds
  • NoEviction: rejects writes when at memory limit
  • Eviction frees enough space for large writes (multiple keys)
  • Eviction on empty store fails (nothing to evict)
  • Unlimited memory (limit=0) accepts any size

4. KV Memory Tracking (ephpm-kv/src/store/mod.rs) – DONE

  • mem_used increases on insert, decreases on remove
  • mem_used adjusts on overwrite (larger and smaller values)
  • flush() resets mem_used to 0

5. CLI Argument Parsing (ephpm/src/main.rs)

  • No subcommand defaults to serve
  • serve with defaults
  • serve with all flags (–config, –listen, –document-root, -vv)
  • kv get <key> parses
  • kv set <key> <value> --ttl 60 parses
  • kv del without keys fails
  • kv del a b c multiple keys
  • kv incr defaults by=1
  • kv --host --port custom connection
  • kv keys defaults pattern=”*"
  • Invalid flag returns error
  • --version flag

Requires openssl-sys to link; can verify with cargo check --tests only.

6. Cluster Config Parsing (ephpm-config/src/lib.rs)

  • ClusterConfig defaults (bind, cluster_id)
  • ClusterConfig from TOML with all fields
  • ClusterKvConfig defaults
  • ClusterKvConfig from TOML
  • Env var overrides for cluster settings
  • Partial TOML fills defaults

Depends on ClusterConfig struct being added to the config crate.

7. Router Edge Cases (ephpm-server/src/router.rs) – DONE

  • is_compressible(): text types compressible, binary types not
  • segment_match(): exact, prefix-star, suffix-star, prefix-star-suffix
  • has_hidden_segment(): dot-only not hidden, deep nesting
  • is_php_file(): case insensitive, non-PHP returns false
  • gzip_compress(): JSON, SVG, disabled for binary
  • etag_matches_value(): empty If-None-Match, strong ETag
  • Blocked paths: empty list blocks nothing, multiple patterns
  • IPv6 listen address parsing
  • glob_match(): directory prefix, no-wildcard exact

8. Static File Edge Cases (ephpm-server/src/static_files.rs) – DONE

  • JavaScript file content-type
  • PNG image content-type
  • Empty file (Content-Length: 0)
  • Nested directory paths
  • Binary file data integrity

Medium Priority – Requires Infrastructure

9. ACME Certificate Provisioning (ephpm-server/src/acme.rs)

  • ACME config with staging/production directory
  • Certificate cache directory creation
  • Domain validation
  • Certificate renewal threshold

Requires mocking ACME directory or test server (pebble).

10. Server Connection Handling

  • HTTP/1.1 connection served
  • HTTP/2 via ALPN
  • Connection timeout
  • IPv6 listen

Requires spawning real server.

11. Graceful Shutdown

  • Shutdown signal sets flag
  • New connections rejected
  • In-flight requests complete
  • Timeout respected

Requires spawning real server.

Lower Priority – Requires php_linked

12. PHP Thread Pool / ZTS (ephpm-php/src/lib.rs)

  • TSRM thread registration on first spawn_blocking use
  • Concurrent PHP execution across multiple threads
  • AtomicBool fast-path check for initialization
  • Graceful shutdown (mutex-protected)
  • Concurrent dispatch returns correct results

13. PHP SAPI Callbacks (ephpm-php/src/sapi.rs)

  • sapi_header_handler sets response headers
  • sapi_send_headers flushes buffer
  • sapi_read_post reads request body
  • sapi_read_cookies reads cookie string
  • log_message routes to tracing

14. PHP Runtime Edge Cases

  • Request timeout (SIGPROF -> 504)
  • Memory limit exceeded -> 500
  • Concurrent requests get isolated state
  • Binary response body passed through intact

Resolved Bugs

KV Store DashMap Deadlock – FIXED

compression_below_min_size_not_compressed previously deadlocked because it held a DashMap read guard via s.data.get() then called s.get() which tries get_mut() on the same key. Fixed by dropping the guard before calling s.get().