mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
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:
+4
-2
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user