Skip to content

Make the unit tests hermetic: no real iptables, Docker, or shared /tmp #44

Description

@FAZuH

Part of the whole-repo test-suite audit (2026-09-30). These tests report green while asserting nothing, which makes every other result in the suite untrustworthy. Not production-code bugs.

What to build

Unit tests that run against fakes the repo already owns, and that cannot interfere with each other or with the host.

Findings

  • The host firewall is wired into unit tests. test_app_state() at crates/natmap/src/api.rs:949 builds a real IptablesManager (api.rs:952). Two tests then early-return on error, so they pass without asserting anything: add_dnat_duplicate_is_idempotent (api.rs:1067, if result.is_err() { return; }) and add_mapping_with_target_ip_success (api.rs:1163). crates/natmap/src/client.rs:219 (14 tests) and crates/natmap/src/daemon.rs:1047 do the same. FakeIptables already exists 120 lines away at daemon.rs:828, and test_app_state_with at daemon.rs:1027 already accepts an injected one. make_daemon with three fakes exists at daemon.rs:958 and is used by 24 tests in the same file.
  • Real Docker socket plus a discarded error. crates/auto-discover/src/daemon.rs:765,780 and crates/natmap/src/daemon.rs:1054,1067 build real clients (DockerClient, ConsulClient::from_env, NatmapClient::default_socket), run them with let _ = ....await discarding the error, then assert logs_contain("container.id=123456789012") — the literal the test itself passed in. They pass whether or not Docker exists. natmap/daemon.rs:1072 additionally .unwrap()s Docker::connect_with_local_defaults(), so it panics on any box without Docker.
  • A test that asserts nothing at all. tests/auto_discover/recovery.rs:476 large_config_many_services builds a shell script into let _script and returns. No run(), no assertion. It also duplicates registration.rs:391 concurrent_starts_all_registered, which does run.
  • A silently skipped Docker test. tests/natmap_docker.rs:189 does which ip6tables || (echo "SKIP: ..." && exit 0), so the test passes on any host without ip6tables and says nothing.
  • An assertion helper that cannot fail. tests/auto_discover/mod.rs:101 assert_pass checks output.contains("PASS"), but run() at mod.rs:44 already panics on non-zero exit and every script reaches its echo "PASS" only after its exit 1 checks. It is a tautology in 46 of 47 uses. Keep the helper only for the handful of tests that can genuinely soft-skip, and make those report SKIP visibly in cargo test output.
  • Non-deterministic port allocation. crates/lab-lib/src/port.rs:255,267,285 use "127.0.0.1:0", which asks the OS for any port, so each call allocates a different random one. Bind port 0, read the real port back, assert on that.
  • Overlapping hardcoded port bands. port.rs:219 uses 21000 + t*40 + i and crates/natmap/src/daemon.rs:952 uses 21000 + (pid % 400) * 24. They collide.
  • Shared mutable state across tests. crates/natmap/src/api.rs:955,959 and daemon.rs:1017,1036 hardcode /tmp/natmap-test-state.json and /tmp/natmap.sock for about 20 tests. client.rs:226 correctly overrides both with a tempfile::TempDir; api.rs:949 does not.

Acceptance criteria

  • No test in a #[cfg(test)] mod tests constructs IptablesManager, connects to Docker, or reaches Consul over the network
  • Every test that can fail has at least one assertion that can fail; no early return on the subject under test
  • cargo test -p lab-ops_natmap --lib and cargo test -p lab-ops_auto-discover --lib pass on a host with no Docker daemon and no iptables write permission
  • No test depends on a fixed path under /tmp
  • No test binds port 0 without reading back the allocated port
  • The soft-skipping ip6tables test reports SKIP in cargo test output rather than passing silently

Blocked by

Per the test-writing guidelines: a flaky test is worse than no test, because it erodes trust in the whole suite until failures are ignored. Do not add #[ignore] as a way out; use the existing fakes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions