• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NVIDIA / nvrc / 20373283300

19 Dec 2025 02:40PM UTC coverage: 79.822% (+51.2%) from 28.618%
20373283300

Pull #84

github

web-flow
Merge 3ab21c41c into 5b8b670d9
Pull Request #84: NVRC complete code coverage

67 of 85 new or added lines in 11 files covered. (78.82%)

4 existing lines in 4 files now uncovered.

269 of 337 relevant lines covered (79.82%)

1.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/src/syslog.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright (c) NVIDIA CORPORATION
3

4
//! Minimal syslog sink for ephemeral init environments.
5
//!
6
//! Programs expect /dev/log to exist for logging. As a minimal init we provide
7
//! this socket and forward messages to the kernel log via trace!(). Severity
8
//! levels are stripped since all messages go to the same destination anyway.
9

10
use log::trace;
11
use nix::poll::{PollFd, PollFlags, PollTimeout};
12
use once_cell::sync::OnceCell;
13
use std::os::fd::AsFd;
14
use std::os::unix::net::UnixDatagram;
15
use std::path::Path;
16

17
/// Global syslog socket—lazily initialized on first poll().
18
/// OnceCell ensures thread-safe one-time init. Ephemeral init runs once,
19
/// no need for reset capability.
20
static SYSLOG: OnceCell<UnixDatagram> = OnceCell::new();
21

22
const DEV_LOG: &str = "/dev/log";
23

24
/// Create and bind a Unix datagram socket at the given path.
25
fn bind(path: &Path) -> std::io::Result<UnixDatagram> {
2✔
26
    UnixDatagram::bind(path)
2✔
27
}
28

29
/// Check socket for pending messages (non-blocking).
30
/// Returns None if no data available, Some(msg) if a message was read.
31
fn poll_socket(sock: &UnixDatagram) -> std::io::Result<Option<String>> {
1✔
32
    let mut fds = [PollFd::new(sock.as_fd(), PollFlags::POLLIN)];
1✔
33
    // Non-blocking poll—init loop calls this frequently, can't afford to block
34
    let count = nix::poll::poll(&mut fds, PollTimeout::ZERO)
2✔
35
        .map_err(|e| std::io::Error::other(e.to_string()))?;
1✔
36

37
    if count == 0 {
1✔
38
        return Ok(None); // No events, no data waiting
1✔
39
    }
40

41
    let Some(revents) = fds[0].revents() else {
2✔
NEW
42
        return Ok(None); // Shouldn't happen, but handle gracefully
×
43
    };
44

45
    if !revents.contains(PollFlags::POLLIN) {
1✔
NEW
46
        return Ok(None); // Event wasn't POLLIN (e.g., error flag)
×
47
    }
48

49
    // Read the message—4KB buffer matches typical syslog max message size
50
    let mut buf = [0u8; 4096];
1✔
51
    let (len, _) = sock.recv_from(&mut buf)?;
1✔
52
    let msg = String::from_utf8_lossy(&buf[..len]);
1✔
53
    Ok(Some(strip_priority(msg.trim_end()).to_string()))
2✔
54
}
55

56
/// Poll the global /dev/log socket, logging any message via trace!().
57
/// Lazily initializes /dev/log on first call.
58
/// Drains one message per call—rate-limited to prevent DoS by syslog flooding.
59
/// Caller loops at ~2 msg/sec (500ms sleep between calls).
60
pub fn poll() -> std::io::Result<()> {
2✔
61
    poll_at(Path::new(DEV_LOG))
2✔
62
}
63

64
/// Internal: poll a specific socket path (for unit tests).
65
/// Production code uses poll() which hardcodes /dev/log.
66
fn poll_at(path: &Path) -> std::io::Result<()> {
2✔
67
    let sock: &UnixDatagram = if path == Path::new(DEV_LOG) {
2✔
68
        SYSLOG.get_or_try_init(|| bind(path))?
8✔
69
    } else {
70
        // For testing: create a one-shot socket (caller manages lifecycle)
71
        return poll_once(path);
1✔
72
    };
73

74
    if let Some(msg) = poll_socket(sock)? {
×
75
        trace!("{}", msg);
×
76
    }
77

78
    Ok(())
×
79
}
80

81
/// One-shot poll for testing: bind, poll once, return.
82
/// Socket is dropped after call—suitable for tests with temp paths.
83
fn poll_once(path: &Path) -> std::io::Result<()> {
1✔
84
    let sock = bind(path)?;
1✔
85
    if let Some(msg) = poll_socket(&sock)? {
2✔
NEW
86
        trace!("{}", msg);
×
87
    }
88
    Ok(())
1✔
89
}
90

91
/// Strip the syslog priority prefix <N> from a message.
92
/// Priority levels are noise for us—all messages go to trace!() equally.
93
/// Example: "<6>hello" → "hello"
94
fn strip_priority(msg: &str) -> &str {
1✔
95
    msg.strip_prefix('<')
1✔
96
        .and_then(|s| s.find('>').map(|i| &s[i + 1..]))
5✔
97
        .unwrap_or(msg)
1✔
98
}
99

100
#[cfg(test)]
101
mod tests {
102
    use super::*;
103
    use tempfile::TempDir;
104

105
    // === strip_priority tests ===
106

107
    #[test]
108
    fn test_strip_priority_normal() {
109
        assert_eq!(strip_priority("<6>test message"), "test message");
110
        assert_eq!(strip_priority("<13>another msg"), "another msg");
111
        assert_eq!(strip_priority("<191>high pri"), "high pri");
112
    }
113

114
    #[test]
115
    fn test_strip_priority_no_prefix() {
116
        assert_eq!(strip_priority("no prefix"), "no prefix");
117
    }
118

119
    #[test]
120
    fn test_strip_priority_edge_cases() {
121
        assert_eq!(strip_priority("<>empty"), "empty");
122
        assert_eq!(strip_priority("<6>"), "");
123
        assert_eq!(strip_priority(""), "");
124
        assert_eq!(strip_priority("<"), "<");
125
        assert_eq!(strip_priority("<6"), "<6"); // No closing >
126
    }
127

128
    // === bind tests ===
129

130
    #[test]
131
    fn test_bind_success() {
132
        let tmp = TempDir::new().unwrap();
133
        let path = tmp.path().join("test.sock");
134
        let sock = bind(&path);
135
        assert!(sock.is_ok());
136
    }
137

138
    #[test]
139
    fn test_bind_nonexistent_dir() {
140
        let path = Path::new("/nonexistent/dir/test.sock");
141
        let sock = bind(path);
142
        assert!(sock.is_err());
143
    }
144

145
    #[test]
146
    fn test_bind_already_exists() {
147
        let tmp = TempDir::new().unwrap();
148
        let path = tmp.path().join("test.sock");
149
        let _sock1 = bind(&path).unwrap();
150
        // Binding again to same path should fail
151
        let sock2 = bind(&path);
152
        assert!(sock2.is_err());
153
    }
154

155
    // === poll_socket tests ===
156

157
    #[test]
158
    fn test_poll_socket_no_data() {
159
        let tmp = TempDir::new().unwrap();
160
        let path = tmp.path().join("test.sock");
161
        let sock = bind(&path).unwrap();
162

163
        let result = poll_socket(&sock).unwrap();
164
        assert_eq!(result, None);
165
    }
166

167
    #[test]
168
    fn test_poll_socket_with_data() {
169
        let tmp = TempDir::new().unwrap();
170
        let path = tmp.path().join("test.sock");
171
        let server = bind(&path).unwrap();
172

173
        let client = UnixDatagram::unbound().unwrap();
174
        client.send_to(b"<6>hello world", &path).unwrap();
175

176
        let result = poll_socket(&server).unwrap();
177
        assert_eq!(result, Some("hello world".to_string()));
178
    }
179

180
    #[test]
181
    fn test_poll_socket_strips_priority() {
182
        let tmp = TempDir::new().unwrap();
183
        let path = tmp.path().join("test.sock");
184
        let server = bind(&path).unwrap();
185

186
        let client = UnixDatagram::unbound().unwrap();
187
        client.send_to(b"<3>error message", &path).unwrap();
188

189
        let result = poll_socket(&server).unwrap();
190
        assert_eq!(result, Some("error message".to_string()));
191
    }
192

193
    #[test]
194
    fn test_poll_socket_multiple_messages() {
195
        let tmp = TempDir::new().unwrap();
196
        let path = tmp.path().join("test.sock");
197
        let server = bind(&path).unwrap();
198

199
        let client = UnixDatagram::unbound().unwrap();
200
        client.send_to(b"<6>first", &path).unwrap();
201
        client.send_to(b"<6>second", &path).unwrap();
202

203
        // poll_socket drains one at a time
204
        let result1 = poll_socket(&server).unwrap();
205
        assert_eq!(result1, Some("first".to_string()));
206

207
        let result2 = poll_socket(&server).unwrap();
208
        assert_eq!(result2, Some("second".to_string()));
209

210
        // No more messages
211
        let result3 = poll_socket(&server).unwrap();
212
        assert_eq!(result3, None);
213
    }
214

215
    #[test]
216
    fn test_poll_socket_trims_trailing_whitespace() {
217
        let tmp = TempDir::new().unwrap();
218
        let path = tmp.path().join("test.sock");
219
        let server = bind(&path).unwrap();
220

221
        let client = UnixDatagram::unbound().unwrap();
222
        client.send_to(b"<6>message with newline\n", &path).unwrap();
223

224
        let result = poll_socket(&server).unwrap();
225
        assert_eq!(result, Some("message with newline".to_string()));
226
    }
227

228
    // === poll_at / poll_once tests ===
229

230
    #[test]
231
    fn test_poll_once_no_data() {
232
        let tmp = TempDir::new().unwrap();
233
        let path = tmp.path().join("test.sock");
234

235
        // poll_once will bind and poll - should succeed with no messages
236
        let result = poll_once(&path);
237
        assert!(result.is_ok());
238
    }
239

240
    #[test]
241
    fn test_poll_once_with_data() {
242
        let tmp = TempDir::new().unwrap();
243
        let path = tmp.path().join("test.sock");
244

245
        // Create server socket first
246
        let server = bind(&path).unwrap();
247

248
        // Send data
249
        let client = UnixDatagram::unbound().unwrap();
250
        client.send_to(b"<6>poll_once test", &path).unwrap();
251

252
        // poll_socket on the server
253
        let result = poll_socket(&server).unwrap();
254
        assert_eq!(result, Some("poll_once test".to_string()));
255
    }
256

257
    #[test]
258
    fn test_poll_at_custom_path() {
259
        let tmp = TempDir::new().unwrap();
260
        let path = tmp.path().join("custom.sock");
261

262
        // poll_at with non-/dev/log path uses poll_once internally
263
        let result = poll_at(&path);
264
        assert!(result.is_ok());
265
    }
266

267
    #[test]
268
    fn test_poll_dev_log() {
269
        // poll() tries to bind /dev/log - may fail if already bound or no permission
270
        // Just exercise the code path, don't assert success
271
        let _ = poll();
272
    }
273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc