Skip to content

Commit

Permalink
test: check all logger level case variants
Browse files Browse the repository at this point in the history
Add unit test to cover all cases of LevelFilter.
Remove redundant logic from levelfilter_from_str because
levelfilter_from_str_all_variants is a better place to put the logic.

Signed-off-by: Jonathan Woollett-Light <jcawl@amazon.co.uk>
Signed-off-by: Sudan Landge <sudanl@amazon.com>
  • Loading branch information
Sudan Landge committed Nov 24, 2023
1 parent 11c9e32 commit a875a93
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 34 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ vm-fdt = "0.2.0"
criterion = { version = "0.5.0", default-features = false }
device_tree = "1.1.0"
proptest = { version = "1.0.0", default-features = false, features = ["std"] }
itertools = "0.12.0"

[features]
tracing = ["log-instrument"]
Expand Down
96 changes: 62 additions & 34 deletions src/vmm/src/logger/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::sync::{Mutex, OnceLock};
use std::thread;

use log::{Log, Metadata, Record};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use utils::time::LocalTime;

use super::metrics::{IncMetric, METRICS};
Expand Down Expand Up @@ -200,25 +200,19 @@ pub struct LoggerConfig {
/// the log level filter. It would be a breaking change to no longer support this. In the next
/// breaking release this should be removed (replaced with `log::LevelFilter` and only supporting
/// its default deserialization).
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum LevelFilter {
/// [`log::LevelFilter:Off`]
#[serde(alias = "OFF")]
Off,
/// [`log::LevelFilter:Trace`]
#[serde(alias = "TRACE")]
Trace,
/// [`log::LevelFilter:Debug`]
#[serde(alias = "DEBUG")]
Debug,
/// [`log::LevelFilter:Info`]
#[serde(alias = "INFO")]
Info,
/// [`log::LevelFilter:Warn`]
#[serde(alias = "WARN", alias = "WARNING", alias = "Warning")]
Warn,
/// [`log::LevelFilter:Error`]
#[serde(alias = "ERROR")]
Error,
}
impl From<LevelFilter> for log::LevelFilter {
Expand All @@ -233,6 +227,25 @@ impl From<LevelFilter> for log::LevelFilter {
}
}
}
impl<'de> Deserialize<'de> for LevelFilter {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let key = String::deserialize(deserializer)?;
let level = match key.to_lowercase().as_str() {
"off" => Ok(LevelFilter::Off),
"trace" => Ok(LevelFilter::Trace),
"debug" => Ok(LevelFilter::Debug),
"info" => Ok(LevelFilter::Info),
"warn" | "warning" => Ok(LevelFilter::Warn),
"error" => Ok(LevelFilter::Error),
_ => Err(D::Error::custom("Invalid LevelFilter")),
};
level
}
}

/// Error type for [`<LevelFilter as FromStr>::from_str`].
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
Expand Down Expand Up @@ -287,37 +300,52 @@ mod tests {
log::LevelFilter::Error
);
}

#[test]
fn levelfilter_from_str() {
fn levelfilter_from_str_all_variants() {
use itertools::Itertools;

#[derive(Deserialize)]
struct Foo {
#[allow(dead_code)]
level: LevelFilter,
}

for (level, level_enum) in [
("off", LevelFilter::Off),
("trace", LevelFilter::Trace),
("debug", LevelFilter::Debug),
("info", LevelFilter::Info),
("warn", LevelFilter::Warn),
("warning", LevelFilter::Warn),
("error", LevelFilter::Error),
] {
let multi = level.chars().map(|_| 0..=1).multi_cartesian_product();
for combination in multi {
let variant = level
.chars()
.zip_eq(combination)
.map(|(c, v)| match v {
0 => c.to_ascii_lowercase(),
1 => c.to_ascii_uppercase(),
_ => unreachable!(),
})
.collect::<String>();

let ex = format!("{{ \"level\": \"{}\" }}", variant);
assert_eq!(LevelFilter::from_str(&variant), Ok(level_enum));
assert!(serde_json::from_str::<Foo>(&ex).is_ok(), "{ex}");
}
}
let ex = "{{ \"level\": \"blah\" }}".to_string();
assert!(
serde_json::from_str::<Foo>(&ex).is_err(),
"expected error got {ex:#?}"
);
assert_eq!(
LevelFilter::from_str("bad"),
Err(LevelFilterFromStrError(String::from("bad")))
);

assert_eq!(LevelFilter::from_str("Off"), Ok(LevelFilter::Off));
assert_eq!(LevelFilter::from_str("Trace"), Ok(LevelFilter::Trace));
assert_eq!(LevelFilter::from_str("Debug"), Ok(LevelFilter::Debug));
assert_eq!(LevelFilter::from_str("Info"), Ok(LevelFilter::Info));
assert_eq!(LevelFilter::from_str("Warn"), Ok(LevelFilter::Warn));
assert_eq!(LevelFilter::from_str("Error"), Ok(LevelFilter::Error));

assert_eq!(LevelFilter::from_str("off"), Ok(LevelFilter::Off));
assert_eq!(LevelFilter::from_str("trace"), Ok(LevelFilter::Trace));
assert_eq!(LevelFilter::from_str("debug"), Ok(LevelFilter::Debug));
assert_eq!(LevelFilter::from_str("info"), Ok(LevelFilter::Info));
assert_eq!(LevelFilter::from_str("warn"), Ok(LevelFilter::Warn));
assert_eq!(LevelFilter::from_str("error"), Ok(LevelFilter::Error));

assert_eq!(LevelFilter::from_str("OFF"), Ok(LevelFilter::Off));
assert_eq!(LevelFilter::from_str("TRACE"), Ok(LevelFilter::Trace));
assert_eq!(LevelFilter::from_str("DEBUG"), Ok(LevelFilter::Debug));
assert_eq!(LevelFilter::from_str("INFO"), Ok(LevelFilter::Info));
assert_eq!(LevelFilter::from_str("WARN"), Ok(LevelFilter::Warn));
assert_eq!(LevelFilter::from_str("ERROR"), Ok(LevelFilter::Error));

assert_eq!(LevelFilter::from_str("warning"), Ok(LevelFilter::Warn));
assert_eq!(LevelFilter::from_str("Warning"), Ok(LevelFilter::Warn));
assert_eq!(LevelFilter::from_str("WARNING"), Ok(LevelFilter::Warn));
}

#[test]
Expand Down

0 comments on commit a875a93

Please sign in to comment.