Skip to content

test(harness): TOCTOU-safe shared port/spawn helpers across all 33 server-spawning suites#284

Merged
pilotspacex-byte merged 2 commits into
mainfrom
fix/test-port-flake-sweep
Jul 11, 2026
Merged

test(harness): TOCTOU-safe shared port/spawn helpers across all 33 server-spawning suites#284
pilotspacex-byte merged 2 commits into
mainfrom
fix/test-port-flake-sweep

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Task #18: sweep all 33 server-spawning integration suites onto a shared TOCTOU-safe port/spawn harness. This is the flake class that cost PR #283 two full macOS CI rerolls this week (spsc_two_db: "server never accepted on port 50081: Connection refused" after 30s), and previously hit txn_kv_wiring (#16), sigterm/bgsave (#11), and others.

The defect class

Every suite carried a copy-pasted free_port() that binds :0, reads the port, and drops the listener before the server spawns:

  1. Port TOCTOU — between probe drop and moon's bind, another test's probe or a concurrent outbound connection's ephemeral source port takes the port → moon exits with EADDRINUSE.
  2. Dead-server blind poll — harnesses polled connect() for up to 30s without ever checking child liveness, so a lost bind race surfaced as "Connection refused" half a minute later, with the real error unread in the server's stderr log.

The fix — tests/common/mod.rs

  • reserve_port() — process-wide dedup set over kernel-chosen probe ports (kills intra-process reuse).
  • spawn_listening(spawn) — reserves a port, runs the caller's spawn closure, polls TCP accept while watching child.try_wait(), and respawns on a fresh port the moment a child dies (external steals can't be prevented, only recovered from; 3 attempts, then a loud panic pointing at the stderr log).

Protocol-level readiness (PING/AUTH) stays with each suite — spawn_listening only guarantees "listening".

Conversion rules held across all 33 files

  • Kill-9/SIGTERM/restart suites keep their deliberate same-port+same-dir restart legs — only the FIRST spawn of a lifecycle goes through spawn_listening (coordinator_local_leg_durability ×7, sharded_multi_exec_durability, sharded_multi_exec_routing, spsc_wake_floor_red, vector_db_isolation).
  • Expected-startup-failure tests (CLI validation in db_maxmemory_quota, admin_auth hard02) keep direct spawns on reserve_port() ports — spawn_listening would mask the assertion.
  • txn_kv_wiring's in-process async server routes its port through reserve_port() and keeps its await_server_ready poll (no Child to watch); its real-subprocess crash-recovery path uses spawn_listening.
  • No test assertions, timeouts, or CLI flags changed — plumbing only.

Verification

  • cargo test --no-run clean on default AND tokio matrices; fmt + clippy (--tests, both matrices) clean for every touched file.
  • All 33 suites green (--no-fail-fast).
  • 10-rep stress running 14 port-hungry suites concurrently (the CI contention pattern that produced the original flakes): all green.
  • Independent adversarial review of the full diff (restart semantics, dir-reuse-in-respawn, expected-failure paths, guard/Drop coverage, port staleness): zero confirmed defects.

Found en route (pre-existing, filed as task #37, not fixed here)

tests/admin_auth_cors_ratelimit.rs is #![cfg(feature = "console")]-gated and has 13 pre-existing compile errors from ureq API drift — invisible because the console-feature CI job is skipped on PRs (verified pre-existing via git stash). Two other untouched test files (graph_restart_id_aliasing, crash_recovery_disk_offload_no_aof) carry pre-existing clippy---tests debt that CI's Lint (which doesn't lint tests) never sees.

Closes task #18.

Summary by CodeRabbit

  • Bug Fixes

    • Reduced integration-test startup flakes caused by TCP port contention and unreliable readiness polling.
    • Improved server startup detection by confirming connections while monitoring process health.
    • Automatically retries failed launches on fresh ports.
  • Tests

    • Updated all 33 integration suites to use the more reliable startup and port-management flow.
    • Preserved existing test coverage and assertions while improving execution stability.

…pers

Task #18. Every integration suite spawning a real moon process carried its
own free_port() that binds :0 and DROPS the listener before the server
spawns. Two failure modes kept hitting CI (latest: spsc_two_db on PR #283,
which cost two full macOS rerolls):

1. Port TOCTOU — between probe drop and moon's bind, another test's probe
   or a concurrent outbound connection's ephemeral source port takes the
   port; moon exits with EADDRINUSE.
2. Dead-server blind poll — harnesses polled connect() for up to 30s
   without checking child liveness, so a lost bind race surfaced as
   "server never accepted: Connection refused" half a minute later with
   the real error unread in the server's stderr log.

Fix: new shared tests/common/mod.rs with
- reserve_port(): process-wide dedup set over kernel-chosen probe ports
  (kills intra-process reuse);
- spawn_listening(spawn): polls TCP accept WHILE watching child.try_wait(),
  respawning on a fresh port the moment the child dies (external steals
  cannot be prevented, only recovered from; 3 attempts, then a loud panic
  pointing at the server's stderr log).

All 33 suites converted. Conversion rules held throughout:
- protocol-level readiness (PING/AUTH) stays with each suite —
  spawn_listening only guarantees "listening";
- kill-9/SIGTERM/restart tests keep their deliberate same-port+same-dir
  restart legs (only the FIRST spawn of a lifecycle goes through
  spawn_listening): coordinator_local_leg_durability (7 legs),
  sharded_multi_exec_durability, sharded_multi_exec_routing,
  spsc_wake_floor_red, vector_db_isolation;
- expected-startup-failure tests (db_maxmemory_quota CLI validation,
  admin_auth hard02) keep direct spawns on reserve_port() ports;
- txn_kv_wiring's in-process async server routes port choice through
  reserve_port() and keeps its await_server_ready poll (no Child to watch);
  its real-subprocess crash-recovery path uses spawn_listening;
- no test assertions, timeouts, or CLI flags changed.

Found en route (filed separately, not fixed here): admin_auth_cors_ratelimit
is cfg(feature = "console")-gated and has 13 pre-existing compile errors
from ureq API drift — invisible because the console CI job is skipped on
PRs (task #37).

Verified: cargo test --no-run clean on default AND tokio matrices; all 33
suites green (--no-fail-fast); 10-rep stress running 14 port-hungry suites
CONCURRENTLY (the CI contention pattern) green; fmt clean.

Refs: task #18, follow-up to PR #283 CI rerolls
author: Tin Dang <tindang.ht97@gmail.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Integration test port-flake sweep

Layer / File(s) Summary
Shared port and readiness harness
tests/common/mod.rs, CHANGELOG.md
Adds process-wide port reservation and child-aware TCP listening retries, with changelog coverage for the migration.
Core integration startup migration
tests/admin_auth_cors_ratelimit.rs, tests/busy_poll_idle.rs, tests/client_tracking_invalidation.rs, tests/cmd_flush_dbsize_debug_memory.rs, tests/container_growth_memory_accounting.rs, tests/coordinator_local_leg_durability.rs, tests/cross_shard_consistency_red.rs, tests/db_maxmemory_quota.rs, tests/flush_cross_shard_scatter.rs
Replaces local free_port() and direct spawning with common::spawn_listening or common::reserve_port, including expected startup-failure cases.
Runtime, protocol, and shutdown migration
tests/ft_*.rs, tests/mem_watchdog.rs, tests/memory_*.rs, tests/msetnx_cross_shard_reject.rs, tests/multishard_serve_smoke.rs, tests/oom_bypass_closure.rs, tests/pubsub_*.rs, tests/resp3_hello.rs, tests/shard_panic_abort.rs, tests/sigterm_shutdown.rs
Updates server helpers and call sites to receive ports from shared listening startup while preserving test assertions.
Restart and specialized lifecycle migration
tests/sharded_multi_exec_*.rs, tests/shardslice_live.rs, tests/spsc_*.rs, tests/txn_kv_wiring.rs, tests/vector_*.rs, tests/wire_reachability_red.rs
Separates initial startup from same-port restarts, centralizes command construction, and updates transaction, vector, and wire tests to use returned ports.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • pilotspace/moon#217: Overlaps in the OOM test-suite spawn and port-selection helpers.
  • pilotspace/moon#276: Directly overlaps in the transaction harness port-reservation and readiness handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it omits the required Checklist, Performance Impact, and Notes sections from the template. Add the missing sections using the template headings, including the checklist items, performance impact summary, and reviewer notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main harness change across the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/test-port-flake-sweep

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pilotspacex-byte pilotspacex-byte merged commit 3e3b55a into main Jul 11, 2026
10 checks passed
@TinDang97 TinDang97 deleted the fix/test-port-flake-sweep branch July 11, 2026 14:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/common/mod.rs (1)

29-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Switch HANDED_OUT to parking_lot::Mutex and drop the unwrap()
A panic while holding this process-wide lock can poison later reserve_port() calls and cascade failures across unrelated tests. parking_lot is already available here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/common/mod.rs` around lines 29 - 49, The process-wide lock in
reserve_port should use parking_lot::Mutex to avoid poisoning after a panic.
Update the Mutex import and initialize HANDED_OUT with the parking_lot type,
then remove unwrap() from HANDED_OUT.lock() while preserving the existing insert
and retry behavior.

Source: Coding guidelines

tests/db_maxmemory_quota.rs (1)

64-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider consolidating spawn_moon_db_quota and spawn_moon_db_quota_no_spill.

The two functions are nearly identical, differing only in the --disk-offload disable flag. A single function with a disable_spill: bool parameter would eliminate the duplication.

♻️ Optional consolidation
 fn spawn_moon_db_quota(dir: &std::path::Path, db_entries: &[&str]) -> (ServerGuard, u16) {
-    let (child, port) = common::spawn_listening(|port| {
-        let mut cmd = Command::new(find_moon_binary());
-        cmd.args([
-            "--port", &port.to_string(),
-            "--dir", &dir.to_string_lossy(),
-            "--shards", "1",
-            "--appendonly", "no",
-            "--maxmemory", "0",
-            "--maxmemory-policy", "noeviction",
-            "--databases", "16",
-        ]);
-        for entry in db_entries {
-            cmd.args(["--db-maxmemory", entry]);
-        }
-        cmd.stdout(std::fs::File::create(dir.join("moon.stdout.log")).expect("stdout log"))
-            .stderr(std::fs::File::create(dir.join("moon.stderr.log")).expect("stderr log"))
-            .spawn()
-            .expect("spawn moon")
-    });
-    (ServerGuard(child), port)
-}
-
-fn spawn_moon_db_quota_no_spill(dir: &std::path::Path, db_entries: &[&str]) -> (ServerGuard, u16) {
+    spawn_moon_db_quota_inner(dir, db_entries, false)
+}
+
+fn spawn_moon_db_quota_no_spill(dir: &std::path::Path, db_entries: &[&str]) -> (ServerGuard, u16) {
+    spawn_moon_db_quota_inner(dir, db_entries, true)
+}
+
+fn spawn_moon_db_quota_inner(
+    dir: &std::path::Path,
+    db_entries: &[&str],
+    disable_spill: bool,
+) -> (ServerGuard, u16) {
     let (child, port) = common::spawn_listening(|port| {
         let mut cmd = Command::new(find_moon_binary());
         cmd.args([
             "--port", &port.to_string(),
             "--dir", &dir.to_string_lossy(),
             "--shards", "1",
             "--appendonly", "no",
             "--maxmemory", "0",
             "--maxmemory-policy", "noeviction",
             "--databases", "16",
+        ]);
+        if disable_spill {
+            cmd.args(["--disk-offload", "disable"]);
+        }
+        cmd.args([
             "--disk-offload",
             "disable",
         ]);
         for entry in db_entries {
             cmd.args(["--db-maxmemory", entry]);
         }
         cmd.stdout(std::fs::File::create(dir.join("moon.stdout.log")).expect("stdout log"))
             .stderr(std::fs::File::create(dir.join("moon.stderr.log")).expect("stderr log"))
             .spawn()
             .expect("spawn moon")
     });
     (ServerGuard(child), port)
 }

Also applies to: 107-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/db_maxmemory_quota.rs` around lines 64 - 91, Consolidate
spawn_moon_db_quota and spawn_moon_db_quota_no_spill into one helper that
accepts a disable_spill: bool parameter. Keep the shared command construction in
the unified function, and add --disk-offload disable only when disable_spill is
true; update all callers to pass the appropriate value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/common/mod.rs`:
- Around line 84-101: Update the error path around child.try_wait and the
ACCEPT_DEADLINE assertion in spawn_listening to explicitly kill the
still-running child before panicking. Preserve the existing diagnostic messages
and normal respawn behavior for an exited child, ensuring both panic paths clean
up the process and its resources first.

In `@tests/memory_prometheus_kinds.rs`:
- Around line 62-85: Move the common::reserve_port() call into the closure
passed to common::spawn_listening, and use that per-attempt value when building
the --admin-port argument. Preserve the returned admin_port value after
spawn_listening so it represents the winning attempt’s port.

In `@tests/vector_db_isolation.rs`:
- Around line 118-119: Update spawn_moon_first to redirect the spawned server’s
stdout and stderr to log files in the test temporary directory, matching the
existing setup in vector_del_unindex.rs and wire_reachability_red.rs; remove
both Stdio::null() destinations while preserving the current log filenames and
panic-diagnostic behavior expected by spawn_listening.

---

Nitpick comments:
In `@tests/common/mod.rs`:
- Around line 29-49: The process-wide lock in reserve_port should use
parking_lot::Mutex to avoid poisoning after a panic. Update the Mutex import and
initialize HANDED_OUT with the parking_lot type, then remove unwrap() from
HANDED_OUT.lock() while preserving the existing insert and retry behavior.

In `@tests/db_maxmemory_quota.rs`:
- Around line 64-91: Consolidate spawn_moon_db_quota and
spawn_moon_db_quota_no_spill into one helper that accepts a disable_spill: bool
parameter. Keep the shared command construction in the unified function, and add
--disk-offload disable only when disable_spill is true; update all callers to
pass the appropriate value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13eb24b9-972c-4fc1-9424-e94b4a27fa7e

📥 Commits

Reviewing files that changed from the base of the PR and between 66265a5 and 11e21d1.

📒 Files selected for processing (35)
  • CHANGELOG.md
  • tests/admin_auth_cors_ratelimit.rs
  • tests/busy_poll_idle.rs
  • tests/client_tracking_invalidation.rs
  • tests/cmd_flush_dbsize_debug_memory.rs
  • tests/common/mod.rs
  • tests/container_growth_memory_accounting.rs
  • tests/coordinator_local_leg_durability.rs
  • tests/cross_shard_consistency_red.rs
  • tests/db_maxmemory_quota.rs
  • tests/flush_cross_shard_scatter.rs
  • tests/ft_search_yield_red.rs
  • tests/ft_yield_chunk_ab.rs
  • tests/mem_watchdog.rs
  • tests/memory_doctor_response.rs
  • tests/memory_prometheus_kinds.rs
  • tests/msetnx_cross_shard_reject.rs
  • tests/multishard_serve_smoke.rs
  • tests/oom_bypass_closure.rs
  • tests/pubsub_burst_delivery.rs
  • tests/pubsub_kv_ordering.rs
  • tests/pubsub_multi_channel_acl.rs
  • tests/resp3_hello.rs
  • tests/shard_panic_abort.rs
  • tests/sharded_multi_exec_durability.rs
  • tests/sharded_multi_exec_locality.rs
  • tests/sharded_multi_exec_routing.rs
  • tests/shardslice_live.rs
  • tests/sigterm_shutdown.rs
  • tests/spsc_two_db.rs
  • tests/spsc_wake_floor_red.rs
  • tests/txn_kv_wiring.rs
  • tests/vector_db_isolation.rs
  • tests/vector_del_unindex.rs
  • tests/wire_reachability_red.rs

Comment thread tests/common/mod.rs
Comment on lines +84 to +101
match child.try_wait() {
Ok(Some(status)) => {
// Lost the bind race (or crashed at startup): give the
// next attempt a fresh port instead of polling a corpse.
eprintln!(
"spawn_listening: child exited {status} before accepting on \
port {port} (attempt {attempt}/{ATTEMPTS}) — respawning"
);
break;
}
Ok(None) => {}
Err(e) => panic!("spawn_listening: try_wait failed: {e}"),
}
assert!(
start.elapsed() < ACCEPT_DEADLINE,
"spawn_listening: live child never accepted on port {port} within \
{ACCEPT_DEADLINE:?} — check the server log in the test's --dir"
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Kill the child before panicking on both panic paths.

When try_wait returns Err (line 95) or the ACCEPT_DEADLINE assert fires (lines 97-101), the child is still alive but is dropped without kill(). std::process::Child::drop does NOT terminate the process — the server becomes an orphan, holding its port and resources, which can interfere with subsequent tests in the same binary.

🐛 Proposed fix
             match child.try_wait() {
                 Ok(Some(status)) => {
                     // Lost the bind race (or crashed at startup): give the
                     // next attempt a fresh port instead of polling a corpse.
                     eprintln!(
                         "spawn_listening: child exited {status} before accepting on \
                          port {port} (attempt {attempt}/{ATTEMPTS}) — respawning"
                     );
                     break;
                 }
                 Ok(None) => {}
-                Err(e) => panic!("spawn_listening: try_wait failed: {e}"),
+                Err(e) => {
+                    let _ = child.kill();
+                    let _ = child.wait();
+                    panic!("spawn_listening: try_wait failed: {e}");
+                }
             }
-            assert!(
-                start.elapsed() < ACCEPT_DEADLINE,
-                "spawn_listening: live child never accepted on port {port} within \
-                 {ACCEPT_DEADLINE:?} — check the server log in the test's --dir"
-            );
+            if start.elapsed() >= ACCEPT_DEADLINE {
+                let _ = child.kill();
+                let _ = child.wait();
+                panic!(
+                    "spawn_listening: live child never accepted on port {port} within \
+                     {ACCEPT_DEADLINE:?} — check the server log in the test's --dir"
+                );
+            }
             std::thread::sleep(Duration::from_millis(50));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match child.try_wait() {
Ok(Some(status)) => {
// Lost the bind race (or crashed at startup): give the
// next attempt a fresh port instead of polling a corpse.
eprintln!(
"spawn_listening: child exited {status} before accepting on \
port {port} (attempt {attempt}/{ATTEMPTS}) — respawning"
);
break;
}
Ok(None) => {}
Err(e) => panic!("spawn_listening: try_wait failed: {e}"),
}
assert!(
start.elapsed() < ACCEPT_DEADLINE,
"spawn_listening: live child never accepted on port {port} within \
{ACCEPT_DEADLINE:?} — check the server log in the test's --dir"
);
match child.try_wait() {
Ok(Some(status)) => {
// Lost the bind race (or crashed at startup): give the
// next attempt a fresh port instead of polling a corpse.
eprintln!(
"spawn_listening: child exited {status} before accepting on \
port {port} (attempt {attempt}/{ATTEMPTS}) — respawning"
);
break;
}
Ok(None) => {}
Err(e) => {
let _ = child.kill();
let _ = child.wait();
panic!("spawn_listening: try_wait failed: {e}");
}
}
if start.elapsed() >= ACCEPT_DEADLINE {
let _ = child.kill();
let _ = child.wait();
panic!(
"spawn_listening: live child never accepted on port {port} within \
{ACCEPT_DEADLINE:?} — check the server log in the test's --dir"
);
}
std::thread::sleep(Duration::from_millis(50));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/common/mod.rs` around lines 84 - 101, Update the error path around
child.try_wait and the ACCEPT_DEADLINE assertion in spawn_listening to
explicitly kill the still-running child before panicking. Preserve the existing
diagnostic messages and normal respawn behavior for an exited child, ensuring
both panic paths clean up the process and its resources first.

Comment on lines +62 to +85
let admin_port = common::reserve_port();
let tmp_dir = std::env::temp_dir().join(format!("moon-test-prom-{}", std::process::id()));
let _ = std::fs::create_dir_all(&tmp_dir);
let child = Command::new(&bin)
.args([
"--port",
&port.to_string(),
"--shards",
"1",
"--admin-port",
&admin_port.to_string(),
"--appendonly",
"no",
"--dir",
tmp_dir.to_str().unwrap(),
"--disk-offload",
"disable",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
let (child, port) = common::spawn_listening(|port| {
Command::new(&bin)
.args([
"--port",
&port.to_string(),
"--shards",
"1",
"--admin-port",
&admin_port.to_string(),
"--appendonly",
"no",
"--dir",
tmp_dir.to_str().unwrap(),
"--disk-offload",
"disable",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn moon")
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Admin port TOCTOU not covered by spawn_listening retry logic.

admin_port is reserved once outside the closure (line 62) and captured by reference, so all three retry attempts inside spawn_listening reuse the same admin port. If an external process steals that port between reserve_port() dropping its probe and the server binding it, every retry fails identically and spawn_listening panics — the retry logic can't help because only the main port changes.

Move reserve_port() inside the closure so each attempt gets a fresh admin port:

🛡️ Proposed fix
-    let admin_port = common::reserve_port();
     let tmp_dir = std::env::temp_dir().join(format!("moon-test-prom-{}", std::process::id()));
     let _ = std::fs::create_dir_all(&tmp_dir);
+    let mut admin_port = 0u16;
     let (child, port) = common::spawn_listening(|port| {
+        admin_port = common::reserve_port();
         Command::new(&bin)
             .args([
                 "--port",
                 &port.to_string(),
                 "--shards",
                 "1",
                 "--admin-port",
-                &admin_port.to_string(),
+                &admin_port.to_string(),
                 "--appendonly",
                 "no",
                 "--dir",
                 tmp_dir.to_str().unwrap(),
                 "--disk-offload",
                 "disable",
             ])
             .stdout(Stdio::null())
             .stderr(Stdio::null())
             .spawn()
             .expect("spawn moon")
     });

After spawn_listening returns, admin_port holds the winning attempt's value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let admin_port = common::reserve_port();
let tmp_dir = std::env::temp_dir().join(format!("moon-test-prom-{}", std::process::id()));
let _ = std::fs::create_dir_all(&tmp_dir);
let child = Command::new(&bin)
.args([
"--port",
&port.to_string(),
"--shards",
"1",
"--admin-port",
&admin_port.to_string(),
"--appendonly",
"no",
"--dir",
tmp_dir.to_str().unwrap(),
"--disk-offload",
"disable",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
let (child, port) = common::spawn_listening(|port| {
Command::new(&bin)
.args([
"--port",
&port.to_string(),
"--shards",
"1",
"--admin-port",
&admin_port.to_string(),
"--appendonly",
"no",
"--dir",
tmp_dir.to_str().unwrap(),
"--disk-offload",
"disable",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn moon")
});
let tmp_dir = std::env::temp_dir().join(format!("moon-test-prom-{}", std::process::id()));
let _ = std::fs::create_dir_all(&tmp_dir);
let mut admin_port = 0u16;
let (child, port) = common::spawn_listening(|port| {
admin_port = common::reserve_port();
Command::new(&bin)
.args([
"--port",
&port.to_string(),
"--shards",
"1",
"--admin-port",
&admin_port.to_string(),
"--appendonly",
"no",
"--dir",
tmp_dir.to_str().unwrap(),
"--disk-offload",
"disable",
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn moon")
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/memory_prometheus_kinds.rs` around lines 62 - 85, Move the
common::reserve_port() call into the closure passed to common::spawn_listening,
and use that per-attempt value when building the --admin-port argument. Preserve
the returned admin_port value after spawn_listening so it represents the winning
attempt’s port.

Comment on lines +118 to +119
.stdout(Stdio::null())
.stderr(Stdio::null())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Redirect server stdout/stderr to log files instead of Stdio::null().

spawn_listening panics with messages like "check the server log in the test's --dir" and "read the server stderr log in the test's --dir", but spawn_moon_first discards both streams via Stdio::null(). When startup fails there are no logs to inspect, making the panic messages misleading. The other two files in this cohort (vector_del_unindex.rs and wire_reachability_red.rs) redirect to log files in the temp dir.

🛠️ Proposed fix
-            .stdout(Stdio::null())
-            .stderr(Stdio::null())
+            .stdout(std::fs::File::create(tmp_dir.join("moon.stdout.log")).expect("stdout log"))
+            .stderr(std::fs::File::create(tmp_dir.join("moon.stderr.log")).expect("stderr log"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdout(std::fs::File::create(tmp_dir.join("moon.stdout.log")).expect("stdout log"))
.stderr(std::fs::File::create(tmp_dir.join("moon.stderr.log")).expect("stderr log"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/vector_db_isolation.rs` around lines 118 - 119, Update spawn_moon_first
to redirect the spawned server’s stdout and stderr to log files in the test
temporary directory, matching the existing setup in vector_del_unindex.rs and
wire_reachability_red.rs; remove both Stdio::null() destinations while
preserving the current log filenames and panic-diagnostic behavior expected by
spawn_listening.

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.

2 participants