Skip to content

Commit

Permalink
Use u8 for internal verbosity level calculation
Browse files Browse the repository at this point in the history
This avoids integer overflow / underflow errors when calculating the
verbosity level. Prior to this change, constructing a `Verbosity` with
either parameter equal to `u8::MAX` would result in incorrect verbosity
level calculation.

This change adds tests to ensure that the verbosity level is correctly
calculated for each log level. These were run before and after the
change to ensure that the change is correct. The tests using 255 as
either input value were failing before the change and passing after the
change.
  • Loading branch information
joshka committed Sep 21, 2024
1 parent c621a6a commit 5165fdf
Showing 1 changed file with 177 additions and 16 deletions.
193 changes: 177 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,30 +126,32 @@ impl<L: LogLevel> Verbosity<L> {
self.log_level().is_none()
}

fn verbosity(&self) -> i8 {
level_value(L::default()) - (self.quiet as i8) + (self.verbose as i8)
fn verbosity(&self) -> u8 {
let default_verbosity = level_value(L::default());
let verbosity = default_verbosity as i16 - self.quiet as i16 + self.verbose as i16;
verbosity.clamp(0, u8::MAX as i16) as u8
}
}

fn level_value(level: Option<Level>) -> i8 {
fn level_value(level: Option<Level>) -> u8 {
match level {
None => -1,
Some(Level::Error) => 0,
Some(Level::Warn) => 1,
Some(Level::Info) => 2,
Some(Level::Debug) => 3,
Some(Level::Trace) => 4,
None => 0,
Some(Level::Error) => 1,
Some(Level::Warn) => 2,
Some(Level::Info) => 3,
Some(Level::Debug) => 4,
Some(Level::Trace) => 5,
}
}

fn level_enum(verbosity: i8) -> Option<Level> {
fn level_enum(verbosity: u8) -> Option<Level> {
match verbosity {
i8::MIN..=-1 => None,
0 => Some(Level::Error),
1 => Some(Level::Warn),
2 => Some(Level::Info),
3 => Some(Level::Debug),
4..=i8::MAX => Some(Level::Trace),
0 => None,
1 => Some(Level::Error),
2 => Some(Level::Warn),
3 => Some(Level::Info),
4 => Some(Level::Debug),
5..=u8::MAX => Some(Level::Trace),
}
}

Expand Down Expand Up @@ -235,4 +237,163 @@ mod test {
use clap::CommandFactory;
Cli::command().debug_assert();
}

#[test]
fn verbosity_error_level() {
let v: Verbosity<ErrorLevel> = Verbosity::new(0, 0);
assert_eq!(v.log_level(), Some(Level::Error));
assert_eq!(v.log_level_filter(), LevelFilter::Error);

let v: Verbosity<ErrorLevel> = Verbosity::new(1, 0);
assert_eq!(v.log_level(), Some(Level::Warn));
assert_eq!(v.log_level_filter(), LevelFilter::Warn);

let v: Verbosity<ErrorLevel> = Verbosity::new(2, 0);
assert_eq!(v.log_level(), Some(Level::Info));
assert_eq!(v.log_level_filter(), LevelFilter::Info);

let v: Verbosity<ErrorLevel> = Verbosity::new(3, 0);
assert_eq!(v.log_level(), Some(Level::Debug));
assert_eq!(v.log_level_filter(), LevelFilter::Debug);

let v: Verbosity<ErrorLevel> = Verbosity::new(4, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// overflows to trace
let v: Verbosity<ErrorLevel> = Verbosity::new(5, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// max verbosity is trace
let v: Verbosity<ErrorLevel> = Verbosity::new(255, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

let v: Verbosity<ErrorLevel> = Verbosity::new(0, 1);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// underflows to off
let v: Verbosity<ErrorLevel> = Verbosity::new(0, 2);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// max quiet is off
let v: Verbosity<ErrorLevel> = Verbosity::new(0, 255);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// This can only happen when vebosity is manually constructed due to the args being marked
// as conflicting
let v: Verbosity<ErrorLevel> = Verbosity::new(255, 255);
assert_eq!(v.log_level(), Some(Level::Error));
assert_eq!(v.log_level_filter(), LevelFilter::Error);
}

#[test]
fn verbosity_warn_level() {
let v: Verbosity<WarnLevel> = Verbosity::new(0, 0);
assert_eq!(v.log_level(), Some(Level::Warn));
assert_eq!(v.log_level_filter(), LevelFilter::Warn);

let v: Verbosity<WarnLevel> = Verbosity::new(1, 0);
assert_eq!(v.log_level(), Some(Level::Info));
assert_eq!(v.log_level_filter(), LevelFilter::Info);

let v: Verbosity<WarnLevel> = Verbosity::new(2, 0);
assert_eq!(v.log_level(), Some(Level::Debug));
assert_eq!(v.log_level_filter(), LevelFilter::Debug);

let v: Verbosity<WarnLevel> = Verbosity::new(3, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// overflows to trace
let v: Verbosity<WarnLevel> = Verbosity::new(4, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// max verbosity is trace
let v: Verbosity<WarnLevel> = Verbosity::new(255, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

let v: Verbosity<WarnLevel> = Verbosity::new(0, 1);
assert_eq!(v.log_level(), Some(Level::Error));
assert_eq!(v.log_level_filter(), LevelFilter::Error);

let v: Verbosity<WarnLevel> = Verbosity::new(0, 2);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// underflows to off
let v: Verbosity<WarnLevel> = Verbosity::new(0, 3);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// max quiet is off
let v: Verbosity<WarnLevel> = Verbosity::new(0, 255);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// This can only happen when vebosity is manually constructed due to the args being marked
// as conflicting
let v: Verbosity<WarnLevel> = Verbosity::new(255, 255);
assert_eq!(v.log_level(), Some(Level::Warn));
assert_eq!(v.log_level_filter(), LevelFilter::Warn);
}

#[test]
fn verbosity_info_level() {
let v: Verbosity<InfoLevel> = Verbosity::new(0, 0);
assert_eq!(v.log_level(), Some(Level::Info));
assert_eq!(v.log_level_filter(), LevelFilter::Info);

let v: Verbosity<InfoLevel> = Verbosity::new(1, 0);
assert_eq!(v.log_level(), Some(Level::Debug));
assert_eq!(v.log_level_filter(), LevelFilter::Debug);

let v: Verbosity<InfoLevel> = Verbosity::new(2, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// overflows to trace
let v: Verbosity<InfoLevel> = Verbosity::new(3, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

// max verbosity is trace
let v: Verbosity<InfoLevel> = Verbosity::new(255, 0);
assert_eq!(v.log_level(), Some(Level::Trace));
assert_eq!(v.log_level_filter(), LevelFilter::Trace);

let v: Verbosity<InfoLevel> = Verbosity::new(0, 1);
assert_eq!(v.log_level(), Some(Level::Warn));
assert_eq!(v.log_level_filter(), LevelFilter::Warn);

let v: Verbosity<InfoLevel> = Verbosity::new(0, 2);
assert_eq!(v.log_level(), Some(Level::Error));
assert_eq!(v.log_level_filter(), LevelFilter::Error);

let v: Verbosity<InfoLevel> = Verbosity::new(0, 3);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// underflows to off
let v: Verbosity<InfoLevel> = Verbosity::new(0, 4);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// max quiet is off
let v: Verbosity<InfoLevel> = Verbosity::new(0, 255);
assert_eq!(v.log_level(), None);
assert_eq!(v.log_level_filter(), LevelFilter::Off);

// This can only happen when vebosity is manually constructed due to the args being marked
// as conflicting
let v: Verbosity<InfoLevel> = Verbosity::new(255, 255);
assert_eq!(v.log_level(), Some(Level::Info));
assert_eq!(v.log_level_filter(), LevelFilter::Info);
}
}

0 comments on commit 5165fdf

Please sign in to comment.