use std::fmt;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::str::FromStr;
use nonempty::NonEmpty;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct BenchmarkId {
pub segments: NonEmpty<String>,
}
impl BenchmarkId {
#[must_use]
pub fn new(segments: NonEmpty<String>) -> Self {
Self { segments }
}
#[must_use]
pub fn qualified(&self) -> String {
self.segments
.iter()
.map(String::as_str)
.collect::<Vec<_>>()
.join("/")
}
}
impl fmt::Display for BenchmarkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.qualified())
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(into = "String", try_from = "String")]
pub struct BenchmarkIdPrefix(String);
impl BenchmarkIdPrefix {
pub fn new(value: impl Into<String>) -> Result<Self, EmptyBenchmarkIdPrefix> {
let value = value.into();
if value.is_empty() {
return Err(EmptyBenchmarkIdPrefixValueError::new().into());
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for BenchmarkIdPrefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl FromStr for BenchmarkIdPrefix {
type Err = EmptyBenchmarkIdPrefix;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl TryFrom<String> for BenchmarkIdPrefix {
type Error = EmptyBenchmarkIdPrefix;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<BenchmarkIdPrefix> for String {
fn from(prefix: BenchmarkIdPrefix) -> Self {
prefix.0
}
}
#[ohno::error]
#[derive(Clone)]
#[no_constructors]
#[from(EmptyBenchmarkIdPrefixValueError)]
pub struct EmptyBenchmarkIdPrefix;
impl UnwindSafe for EmptyBenchmarkIdPrefix {}
impl RefUnwindSafe for EmptyBenchmarkIdPrefix {}
#[ohno::error]
#[display("a benchmark-id prefix must not be empty")]
struct EmptyBenchmarkIdPrefixValueError;
impl UnwindSafe for EmptyBenchmarkIdPrefixValueError {}
impl RefUnwindSafe for EmptyBenchmarkIdPrefixValueError {}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::error;
use std::fmt::Debug;
use std::panic::{RefUnwindSafe, UnwindSafe};
use nonempty::nonempty;
use ohno::ErrorExt;
use static_assertions::assert_impl_all;
use super::*;
assert_impl_all!(
EmptyBenchmarkIdPrefix: Clone,
Send,
Sync,
Debug,
error::Error,
UnwindSafe,
RefUnwindSafe
);
assert_impl_all!(
EmptyBenchmarkIdPrefixValueError: Send,
Sync,
Debug,
error::Error,
UnwindSafe,
RefUnwindSafe
);
#[test]
fn leading_segment_distinguishes_otherwise_equal_ids() {
let foo = BenchmarkId::new(nonempty![
"foo".to_owned(),
"a::bench".to_owned(),
"run".to_owned(),
]);
let bar = BenchmarkId::new(nonempty![
"bar".to_owned(),
"a::bench".to_owned(),
"run".to_owned(),
]);
assert_ne!(foo, bar);
}
#[test]
fn ids_sort_lexicographically_by_segments() {
let bar = BenchmarkId::new(nonempty!["bar".to_owned(), "z".to_owned()]);
let foo = BenchmarkId::new(nonempty!["foo".to_owned(), "a".to_owned()]);
let mut ids = vec![foo.clone(), bar.clone()];
ids.sort();
assert_eq!(ids, vec![bar, foo]);
}
#[test]
fn qualified_joins_segments() {
let id = BenchmarkId::new(nonempty![
"fast_time".to_owned(),
"a::group".to_owned(),
"capture".to_owned(),
"two_instants".to_owned(),
]);
assert_eq!(id.qualified(), "fast_time/a::group/capture/two_instants");
assert_eq!(id.to_string(), id.qualified());
}
#[test]
fn qualified_handles_a_single_segment() {
let id = BenchmarkId::new(nonempty!["a::group".to_owned()]);
assert_eq!(id.qualified(), "a::group");
}
#[test]
fn benchmark_id_prefix_rejects_an_empty_string() {
let error = BenchmarkIdPrefix::new("").unwrap_err();
assert!(
error
.find_source::<EmptyBenchmarkIdPrefixValueError>()
.is_some()
);
"".parse::<BenchmarkIdPrefix>().unwrap_err();
BenchmarkIdPrefix::try_from(String::new()).unwrap_err();
}
#[test]
fn benchmark_id_prefix_keeps_a_non_empty_value() {
let prefix = BenchmarkIdPrefix::new("foo/bar").unwrap();
assert_eq!(prefix.as_str(), "foo/bar");
assert_eq!(prefix.to_string(), "foo/bar");
}
#[test]
fn benchmark_id_prefix_serializes_as_a_plain_string() {
let prefix = BenchmarkIdPrefix::new("foo/bar").unwrap();
let json = serde_json::to_string(&prefix).unwrap();
assert_eq!(json, "\"foo/bar\"");
let restored: BenchmarkIdPrefix = serde_json::from_str(&json).unwrap();
assert_eq!(restored, prefix);
}
#[test]
fn benchmark_id_prefix_deserialization_rejects_an_empty_string() {
serde_json::from_str::<BenchmarkIdPrefix>("\"\"").unwrap_err();
}
}