Merge #e96b284f: Fix portable metrics assertions and preserve failing r…

Fix portable metrics assertions and preserve failing relay logs

nostr:nevent1qqswj6egflzl5acvhrx2ld2gv8ucv4k654kyu5rh3sphxkjmgfw9hsqpz3mhxue69uhhyetvv9ujumn8d96zuer9wckj9pcq

PR-Author: DanConwayDev's Agent
nostr:npub1v47f74n2ycn66asev62nv8sas99akj0g0wg0fkup37u3ckwuzs4q7cwtp0

Integrate portable metrics assertions, Darwin listener inspection and
bounded relay diagnostics with the lifecycle and archive fixtures.
Preserve test coverage on each supported collector platform and reject
invalid listener descriptors.

All three merges apply cleanly. Resolve integration findings and run the
full workspace suite three times before recommending this branch.

Assisted-by: Codex (GPT-6)
This commit is contained in:
DanConwayDev
2026-09-21 08:27:05 +00:00
3 changed files with 92 additions and 7 deletions
+4 -2
View File
@@ -1216,10 +1216,12 @@ mod tests {
// render() recounts from disk)
let output = metrics.render();
assert!(output.contains("ngit_repositories_total 0"));
assert!(output.contains("process_cpu_seconds_total"));
assert!(output.contains("process_resident_memory_bytes"));
#[cfg(target_os = "linux")]
{
// ProcessCollector is registered only on Linux, just like the
// cgroup collectors below. Repository metrics are portable.
assert!(output.contains("process_cpu_seconds_total"));
assert!(output.contains("process_resident_memory_bytes"));
if let Some(directory) = cgroup_v2_directory() {
if cgroup_cpu_seconds(&directory).is_some() {
assert!(output.contains("ngit_cgroup_cpu_seconds_total"));
+57 -5
View File
@@ -38,12 +38,21 @@ fn listener_from_fd(fd: RawFd) -> Result<TcpListener> {
return Err(std::io::Error::last_os_error()).context("protect test listener from exec");
}
}
if !is_listening(owned)? || !listener.local_addr()?.ip().is_loopback() {
bail!("test listener must be a listening loopback TCP socket");
}
listener.set_nonblocking(true)?;
TcpListener::from_std(listener).context("adopt test listener")
}
#[cfg(not(target_vendor = "apple"))]
fn is_listening(fd: RawFd) -> Result<bool> {
let mut accepting: libc::c_int = 0;
let mut length = std::mem::size_of_val(&accepting) as libc::socklen_t;
// SAFETY: both pointers reference live, correctly sized writable values.
let result = unsafe {
libc::getsockopt(
owned,
fd,
libc::SOL_SOCKET,
libc::SO_ACCEPTCONN,
(&mut accepting as *mut libc::c_int).cast(),
@@ -53,11 +62,32 @@ fn listener_from_fd(fd: RawFd) -> Result<TcpListener> {
if result != 0 {
return Err(std::io::Error::last_os_error()).context("inspect test listener");
}
if accepting == 0 || !listener.local_addr()?.ip().is_loopback() {
bail!("test listener must be a listening loopback TCP socket");
Ok(accepting != 0)
}
#[cfg(target_vendor = "apple")]
fn is_listening(fd: RawFd) -> Result<bool> {
// XNU defines SO_ACCEPTCONN but does not support querying it with
// getsockopt. TCP_CONNECTION_INFO exposes the TCP state without accepting
// a queued connection or changing the inherited listener's backlog.
// SAFETY: tcp_connection_info consists entirely of integer fields.
let mut info: libc::tcp_connection_info = unsafe { std::mem::zeroed() };
let mut length = std::mem::size_of_val(&info) as libc::socklen_t;
// SAFETY: info and length are live, correctly sized writable values.
let result = unsafe {
libc::getsockopt(
fd,
libc::IPPROTO_TCP,
libc::TCP_CONNECTION_INFO,
(&mut info as *mut libc::tcp_connection_info).cast(),
&mut length,
)
};
if result != 0 {
return Err(std::io::Error::last_os_error()).context("inspect test listener");
}
listener.set_nonblocking(true)?;
TcpListener::from_std(listener).context("adopt test listener")
// TCPS_LISTEN from XNU's netinet/tcp_fsm.h (not exported by libc).
Ok(info.tcpi_state == 1)
}
#[cfg(test)]
@@ -89,5 +119,27 @@ mod tests {
let file = std::fs::File::open("/dev/null").unwrap();
assert!(listener_from_fd(file.as_raw_fd()).is_err());
assert!(listener_from_fd(-1).is_err());
let datagram = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
assert!(listener_from_fd(datagram.as_raw_fd()).is_err());
}
#[tokio::test]
async fn adoption_preserves_queued_connections_and_rejects_connected_streams() {
let reserved = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let client = tokio::time::timeout(
std::time::Duration::from_secs(5),
tokio::net::TcpStream::connect(reserved.local_addr().unwrap()),
)
.await
.unwrap()
.unwrap();
assert!(listener_from_fd(client.as_raw_fd()).is_err());
let listener = listener_from_fd(reserved.as_raw_fd()).unwrap();
drop(reserved);
let (_, peer) = tokio::time::timeout(std::time::Duration::from_secs(5), listener.accept())
.await
.unwrap()
.unwrap();
assert_eq!(peer, client.local_addr().unwrap());
}
}
+31
View File
@@ -1114,6 +1114,37 @@ impl Drop for TestRelay {
// Ensure process is killed when TestRelay is dropped
let _ = self.process.kill();
let _ = self.process.wait();
if std::thread::panicking() {
// Sandboxed builders discard /tmp after failure. Preserve a bounded
// tail from every participating relay in libtest's failure output.
// Diagnostic I/O must never cause a second panic during unwinding.
use std::io::{Read, Seek, SeekFrom, Write};
let path = self.log_path();
let tail = (|| -> std::io::Result<Vec<u8>> {
let mut file = std::fs::File::open(&path)?;
let start = file.metadata()?.len().saturating_sub(64 * 1024);
file.seek(SeekFrom::Start(start))?;
let mut bytes = Vec::new();
file.take(64 * 1024).read_to_end(&mut bytes)?;
Ok(bytes)
})();
let mut stderr = std::io::stderr().lock();
let _ = writeln!(
stderr,
"Relay {} failure log ({})",
self.port,
path.display()
);
match tail {
Ok(bytes) => {
let _ = stderr.write_all(&bytes);
let _ = writeln!(stderr);
}
Err(error) => {
let _ = writeln!(stderr, "Could not read relay log: {error}");
}
}
}
}
}