use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use crate::{BenchmarkId, BenchmarkIdPrefix};
pub const BLESS_SCHEMA_VERSION: u32 = 2;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct BlessingRecord {
pub schema_version: u32,
pub commit: String,
pub issued_at: Timestamp,
pub prefixes: Vec<BenchmarkIdPrefix>,
pub tool_version: String,
}
impl BlessingRecord {
#[must_use]
pub fn new(
commit: String,
issued_at: Timestamp,
prefixes: Vec<BenchmarkIdPrefix>,
tool_version: String,
) -> Self {
Self {
schema_version: BLESS_SCHEMA_VERSION,
commit,
issued_at,
prefixes,
tool_version,
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
#[must_use]
pub fn matches(&self, id: &BenchmarkId) -> bool {
if self.prefixes.is_empty() {
return true;
}
let qualified = id.qualified();
self.prefixes
.iter()
.any(|prefix| qualified.starts_with(prefix.as_str()))
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use nonempty::NonEmpty;
use super::*;
fn ts(seconds: i64) -> Timestamp {
Timestamp::from_second(seconds).unwrap()
}
fn id(
package: Option<&str>,
group: &str,
case: Option<&str>,
value: Option<&str>,
) -> BenchmarkId {
let segments = [package, Some(group), case, value]
.into_iter()
.flatten()
.map(ToOwned::to_owned)
.collect();
BenchmarkId::new(NonEmpty::from_vec(segments).unwrap())
}
fn record(prefixes: &[&str]) -> BlessingRecord {
BlessingRecord::new(
"deadbeef".to_owned(),
ts(1_700_000_100),
prefixes
.iter()
.map(|prefix| BenchmarkIdPrefix::new(*prefix).unwrap())
.collect(),
"0.0.1".to_owned(),
)
}
#[test]
fn json_round_trips() {
let original = BlessingRecord::new(
"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_owned(),
ts(1_700_000_100),
vec![BenchmarkIdPrefix::new("all_the_time/read_cell").unwrap()],
"1.2.3".to_owned(),
);
let json = original.to_json().unwrap();
let parsed = BlessingRecord::from_json(&json).unwrap();
assert_eq!(parsed, original);
}
#[test]
fn empty_prefix_list_accepts_every_benchmark() {
let blessing = record(&[]);
assert!(blessing.matches(&id(Some("all_the_time"), "read_cell", None, None)));
assert!(blessing.matches(&id(None, "anything", Some("else"), None)));
}
#[test]
fn prefix_matches_exact_and_family() {
let blessing = record(&["all_the_time/read_cell"]);
assert!(blessing.matches(&id(Some("all_the_time"), "read_cell", None, None)));
assert!(blessing.matches(&id(Some("all_the_time"), "read_cell", Some("warm"), None)));
assert!(!blessing.matches(&id(Some("all_the_time"), "write_cell", None, None)));
}
#[test]
fn partial_segment_prefix_matches_a_family() {
let blessing = record(&["overhead/groups_"]);
assert!(blessing.matches(&id(None, "overhead", Some("groups_10"), None)));
assert!(blessing.matches(&id(None, "overhead", Some("groups_100"), None)));
assert!(!blessing.matches(&id(None, "overhead", Some("single"), None)));
}
#[test]
fn any_matching_prefix_accepts() {
let blessing = record(&["foo/bar", "baz/qux"]);
assert!(blessing.matches(&id(None, "baz", Some("qux"), None)));
}
}