mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
main: Add unit testing for logger infrastructure
Assisted-by: Claude:Opus-4.7 Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
@@ -144,3 +144,207 @@ impl log::Log for Logger {
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::Log;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A `Write` sink that appends to a shared byte buffer so tests can
|
||||
/// inspect what the logger wrote.
|
||||
#[derive(Clone, Default)]
|
||||
struct SharedBuffer(Arc<Mutex<Vec<u8>>>);
|
||||
|
||||
impl SharedBuffer {
|
||||
fn contents(&self) -> String {
|
||||
String::from_utf8(self.0.lock().unwrap().clone()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for SharedBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.lock().unwrap().extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn render(tokens: &[Token]) -> String {
|
||||
tokens
|
||||
.iter()
|
||||
.map(|t| match t {
|
||||
Token::Literal(s) => format!("L({s})"),
|
||||
Token::BootTime => "B".to_string(),
|
||||
Token::Thread => "T".to_string(),
|
||||
Token::Level => "V".to_string(),
|
||||
Token::Location => "O".to_string(),
|
||||
Token::Msg => "M".to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("|")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_plain_literal() {
|
||||
let tokens = parse_format("hello world").unwrap();
|
||||
assert_eq!(render(&tokens), "L(hello world)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_string() {
|
||||
let tokens = parse_format("").unwrap();
|
||||
assert!(tokens.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_all_known_tokens() {
|
||||
let tokens = parse_format("[{boottime}] <{thread}> {level} {location} -- {msg}").unwrap();
|
||||
assert_eq!(render(&tokens), "L([)|B|L(] <)|T|L(> )|V|L( )|O|L( -- )|M");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_default_format_succeeds() {
|
||||
let tokens = parse_format(DEFAULT_FORMAT).unwrap();
|
||||
// Default format has 5 tokens interleaved with literals.
|
||||
assert!(tokens.iter().any(|t| matches!(t, Token::BootTime)));
|
||||
assert!(tokens.iter().any(|t| matches!(t, Token::Thread)));
|
||||
assert!(tokens.iter().any(|t| matches!(t, Token::Level)));
|
||||
assert!(tokens.iter().any(|t| matches!(t, Token::Location)));
|
||||
assert!(tokens.iter().any(|t| matches!(t, Token::Msg)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_escaped_braces() {
|
||||
let tokens = parse_format("{{not-a-token}}").unwrap();
|
||||
assert_eq!(render(&tokens), "L({not-a-token})");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_escaped_braces_around_token() {
|
||||
let tokens = parse_format("{{{level}}}").unwrap();
|
||||
assert_eq!(render(&tokens), "L({)|V|L(})");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_unterminated_brace_errors() {
|
||||
match parse_format("hello {level") {
|
||||
Err(Error::UnterminatedBrace) => {}
|
||||
Err(other) => panic!("unexpected error: {other:?}"),
|
||||
Ok(_) => panic!("expected error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_unmatched_close_brace_errors() {
|
||||
match parse_format("hello }") {
|
||||
Err(Error::UnmatchedBrace) => {}
|
||||
Err(other) => panic!("unexpected error: {other:?}"),
|
||||
Ok(_) => panic!("expected error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_unknown_token_errors() {
|
||||
match parse_format("{nope}") {
|
||||
Err(Error::UnknownToken(name)) => assert_eq!(name, "nope"),
|
||||
Err(other) => panic!("unexpected error: {other:?}"),
|
||||
Ok(_) => panic!("expected error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logger_new_uses_default_format() {
|
||||
let buf = SharedBuffer::default();
|
||||
let logger = Logger::new(Box::new(buf.clone())).unwrap();
|
||||
// The default format has all 5 dynamic tokens.
|
||||
assert_eq!(
|
||||
logger
|
||||
.tokens
|
||||
.iter()
|
||||
.filter(|t| !matches!(t, Token::Literal(_)))
|
||||
.count(),
|
||||
5
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logger_enabled_always_true() {
|
||||
let buf = SharedBuffer::default();
|
||||
let logger = Logger::new(Box::new(buf)).unwrap();
|
||||
let metadata = log::Metadata::builder()
|
||||
.level(log::Level::Trace)
|
||||
.target("anything")
|
||||
.build();
|
||||
assert!(logger.enabled(&metadata));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logger_writes_expected_fields() {
|
||||
let buf = SharedBuffer::default();
|
||||
let logger = Logger::new(Box::new(buf.clone())).unwrap();
|
||||
|
||||
logger.log(
|
||||
&log::Record::builder()
|
||||
.args(format_args!("hello {}", "world"))
|
||||
.level(log::Level::Info)
|
||||
.target("unit_test_target")
|
||||
.file(Some("foo.rs"))
|
||||
.line(Some(42))
|
||||
.build(),
|
||||
);
|
||||
|
||||
let out = buf.contents();
|
||||
assert!(out.starts_with("cloud-hypervisor: "), "got: {out}");
|
||||
assert!(out.contains("INFO"), "got: {out}");
|
||||
assert!(out.contains("foo.rs:42"), "got: {out}");
|
||||
assert!(out.contains("hello world"), "got: {out}");
|
||||
assert!(out.ends_with("\r\n"), "got: {out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logger_uses_target_when_no_file() {
|
||||
let buf = SharedBuffer::default();
|
||||
let logger = Logger::new(Box::new(buf.clone())).unwrap();
|
||||
|
||||
logger.log(
|
||||
&log::Record::builder()
|
||||
.args(format_args!("no location"))
|
||||
.level(log::Level::Warn)
|
||||
.target("my_target")
|
||||
.file(None)
|
||||
.line(None)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let out = buf.contents();
|
||||
assert!(out.contains("my_target"), "got: {out}");
|
||||
assert!(!out.contains("foo.rs"), "got: {out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn logger_appends_each_record() {
|
||||
let buf = SharedBuffer::default();
|
||||
let logger = Logger::new(Box::new(buf.clone())).unwrap();
|
||||
|
||||
for i in 0..3 {
|
||||
logger.log(
|
||||
&log::Record::builder()
|
||||
.args(format_args!("entry-{i}"))
|
||||
.level(log::Level::Debug)
|
||||
.target("t")
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
let out = buf.contents();
|
||||
assert_eq!(out.matches("entry-").count(), 3, "got: {out}");
|
||||
assert_eq!(out.matches("\r\n").count(), 3, "got: {out}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user