use std::fmt;
use serde::Serialize;
use super::constants::{OBJECTS_SEGMENT, STORAGE_VERSION};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Engine {
Criterion,
Callgrind,
AllocTracker,
AllTheTime,
}
impl Engine {
pub const ALL: [Self; 4] = [
Self::Callgrind,
Self::Criterion,
Self::AllocTracker,
Self::AllTheTime,
];
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Criterion => "criterion",
Self::Callgrind => "callgrind",
Self::AllocTracker => "alloc_tracker",
Self::AllTheTime => "all_the_time",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name {
"criterion" => Some(Self::Criterion),
"callgrind" => Some(Self::Callgrind),
"alloc_tracker" => Some(Self::AllocTracker),
"all_the_time" => Some(Self::AllTheTime),
_ => None,
}
}
#[must_use]
pub fn is_hardware_dependent(self) -> bool {
match self {
Self::Criterion | Self::AllTheTime => true,
Self::Callgrind | Self::AllocTracker => false,
}
}
}
impl fmt::Display for Engine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct DiscriminantSet {
pub engine: String,
pub target_triple: String,
pub machine_key: String,
}
impl DiscriminantSet {
#[must_use]
pub fn new(engine: Engine, target_triple: &str, machine_key: Option<&str>) -> Self {
Self {
engine: engine.as_str().to_owned(),
target_triple: sanitize_segment(target_triple),
machine_key: machine_key.map_or_else(|| "synthetic".to_owned(), sanitize_segment),
}
}
#[must_use]
pub fn is_synthetic(&self) -> bool {
self.machine_key == "synthetic"
}
#[must_use]
pub fn partition_prefix(&self, project: &str) -> String {
let project = sanitize_segment(project);
let engine = &self.engine;
let triple = &self.target_triple;
let machine_key = &self.machine_key;
format!("{STORAGE_VERSION}/{project}/{OBJECTS_SEGMENT}/{engine}/{triple}/{machine_key}")
}
#[must_use]
pub fn clean_key(&self, project: &str, commit: &str) -> String {
let prefix = self.partition_prefix(project);
let commit = sanitize_segment(commit);
format!("{prefix}/{commit}/clean.json")
}
#[must_use]
pub fn dirty_key(&self, project: &str, commit: &str, observation_unix: i64) -> String {
let prefix = self.partition_prefix(project);
let commit = sanitize_segment(commit);
format!("{prefix}/{commit}/dirty-{observation_unix}.json")
}
#[must_use]
pub fn bless_key(&self, project: &str, commit: &str, issued_unix: i64) -> String {
let prefix = self.partition_prefix(project);
let commit = sanitize_segment(commit);
format!("{prefix}/{commit}/bless-{issued_unix}.json")
}
#[must_use]
pub fn commit_prefix(&self, project: &str, commit: &str) -> String {
let prefix = self.partition_prefix(project);
let commit = sanitize_segment(commit);
format!("{prefix}/{commit}/")
}
}
impl fmt::Display for DiscriminantSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}/{}/{}",
self.engine, self.target_triple, self.machine_key
)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StorageKey {
pub project: String,
pub set: DiscriminantSet,
pub commit: String,
pub file: String,
}
impl StorageKey {
#[must_use]
pub fn is_dirty(&self) -> bool {
self.file.starts_with("dirty-")
}
#[must_use]
pub fn is_bless(&self) -> bool {
self.file.starts_with("bless-")
}
#[must_use]
pub fn bless_key(&self, issued_unix: i64) -> String {
self.set.bless_key(&self.project, &self.commit, issued_unix)
}
}
#[must_use]
pub fn parse_key(key: &str) -> Option<StorageKey> {
let parts: Vec<&str> = key.split('/').collect();
let [
version,
project,
objects,
engine,
target_triple,
machine_key,
commit,
file,
] = parts.as_slice()
else {
return None;
};
if *version != STORAGE_VERSION || *objects != OBJECTS_SEGMENT {
return None;
}
if parts.iter().any(|segment| segment.is_empty()) {
return None;
}
Some(StorageKey {
project: (*project).to_owned(),
set: DiscriminantSet {
engine: (*engine).to_owned(),
target_triple: (*target_triple).to_owned(),
machine_key: (*machine_key).to_owned(),
},
commit: (*commit).to_owned(),
file: (*file).to_owned(),
})
}
#[must_use]
pub fn sanitize_segment(raw: &str) -> String {
let mangled: String = raw
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.') {
c
} else {
'_'
}
})
.collect();
if mangled.is_empty() || mangled.chars().all(|c| c == '.') {
return "_".to_owned();
}
mangled
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn hardware_dependence_matches_engine() {
assert!(Engine::Criterion.is_hardware_dependent());
assert!(Engine::AllTheTime.is_hardware_dependent());
assert!(!Engine::Callgrind.is_hardware_dependent());
assert!(!Engine::AllocTracker.is_hardware_dependent());
}
#[test]
fn alloc_tracker_uses_a_synthetic_partition() {
let set = DiscriminantSet::new(Engine::AllocTracker, "x86_64-pc-windows-msvc", None);
assert!(set.is_synthetic());
assert_eq!(
set.partition_prefix("folo"),
"v1/folo/objects/alloc_tracker/x86_64-pc-windows-msvc/synthetic"
);
}
#[test]
fn all_the_time_partitions_by_machine_key() {
let set =
DiscriminantSet::new(Engine::AllTheTime, "x86_64-pc-windows-msvc", Some("abc123"));
assert_eq!(
set.partition_prefix("folo"),
"v1/folo/objects/all_the_time/x86_64-pc-windows-msvc/abc123"
);
}
#[test]
fn machine_key_appears_in_partition() {
let set = DiscriminantSet::new(Engine::Criterion, "x86_64-pc-windows-msvc", Some("abc123"));
assert_eq!(
set.partition_prefix("folo"),
"v1/folo/objects/criterion/x86_64-pc-windows-msvc/abc123"
);
}
#[test]
fn is_synthetic_is_false_for_a_machine_keyed_set() {
let set =
DiscriminantSet::new(Engine::AllTheTime, "x86_64-pc-windows-msvc", Some("abc123"));
assert!(!set.is_synthetic());
}
#[test]
fn display_formats_engine_triple_and_machine_key() {
let set = DiscriminantSet::new(Engine::Criterion, "x86_64-pc-windows-msvc", Some("abc123"));
assert_eq!(set.to_string(), "criterion/x86_64-pc-windows-msvc/abc123");
let synthetic = DiscriminantSet::new(Engine::AllocTracker, "x86_64-pc-windows-msvc", None);
assert_eq!(
synthetic.to_string(),
"alloc_tracker/x86_64-pc-windows-msvc/synthetic"
);
}
#[test]
fn clean_key_is_named_by_commit() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
assert_eq!(
set.clean_key("folo", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/\
deadbeefdeadbeefdeadbeefdeadbeefdeadbeef/clean.json"
);
}
#[test]
fn dirty_key_is_named_by_commit_and_observation_time() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
assert_eq!(
set.dirty_key(
"folo",
"deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
1_700_000_000
),
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/\
deadbeefdeadbeefdeadbeefdeadbeefdeadbeef/dirty-1700000000.json"
);
}
#[test]
fn bless_key_targets_the_commit_directory() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
assert_eq!(
set.bless_key("folo", "abc123", 1_700_000_000),
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/abc123/bless-1700000000.json"
);
}
#[test]
fn commit_prefix_enumerates_one_commit_directory() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
assert_eq!(
set.commit_prefix("folo", "dead/beef"),
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/dead_beef/"
);
}
#[test]
fn engine_display_matches_as_str() {
assert_eq!(Engine::Criterion.to_string(), "criterion");
assert_eq!(Engine::Callgrind.to_string(), "callgrind");
assert_eq!(Engine::AllocTracker.to_string(), "alloc_tracker");
assert_eq!(Engine::AllTheTime.to_string(), "all_the_time");
}
#[test]
fn engine_from_name_roundtrips() {
for engine in Engine::ALL {
assert_eq!(Engine::from_name(engine.as_str()), Some(engine));
}
assert_eq!(Engine::from_name("dhat"), None);
}
#[test]
fn sanitize_segment_keeps_safe_characters() {
assert_eq!(
sanitize_segment("x86_64-unknown-linux-gnu"),
"x86_64-unknown-linux-gnu"
);
assert_eq!(sanitize_segment("my.project-1"), "my.project-1");
}
#[test]
fn sanitize_segment_replaces_separators_and_specials() {
assert_eq!(sanitize_segment("team/app"), "team_app");
assert_eq!(sanitize_segment(r"team\app"), "team_app");
assert_eq!(sanitize_segment("weird:name"), "weird_name");
assert_eq!(sanitize_segment("with space"), "with_space");
assert_eq!(sanitize_segment("café"), "caf_");
}
#[test]
fn sanitize_segment_maps_empty_and_dot_only_to_underscore() {
assert_eq!(sanitize_segment(""), "_");
assert_eq!(sanitize_segment("."), "_");
assert_eq!(sanitize_segment(".."), "_");
}
#[test]
fn new_sanitizes_partition_components() {
let set = DiscriminantSet::new(Engine::Criterion, "weird/triple", Some("machine/one"));
assert_eq!(
set.partition_prefix("team/app"),
"v1/team_app/objects/criterion/weird_triple/machine_one"
);
assert_eq!(set.partition_prefix("team/app").split('/').count(), 6);
}
#[test]
fn clean_key_sanitizes_the_commit() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
let object = set.clean_key("folo", "dead/beef");
assert_eq!(
object,
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/dead_beef/clean.json"
);
assert_eq!(object.split('/').count(), 8);
}
#[test]
fn dirty_key_sanitizes_the_commit() {
let set = DiscriminantSet::new(Engine::Callgrind, "x86_64-unknown-linux-gnu", None);
let object = set.dirty_key("folo", "dead/beef", 1_700_000_000);
assert_eq!(
object,
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/dead_beef/dirty-1700000000.json"
);
assert_eq!(object.split('/').count(), 8);
}
#[test]
fn parse_key_decomposes_a_clean_key() {
let parsed = parse_key(
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/abc123/clean.json",
)
.unwrap();
assert_eq!(parsed.project, "folo");
assert_eq!(parsed.set.engine, "callgrind");
assert_eq!(parsed.set.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(parsed.set.machine_key, "synthetic");
assert_eq!(parsed.commit, "abc123");
assert_eq!(parsed.file, "clean.json");
assert!(!parsed.is_dirty());
assert!(!parsed.is_bless());
}
#[test]
fn parse_key_recognizes_a_dirty_snapshot() {
let parsed = parse_key(
"v1/folo/objects/criterion/x86_64-pc-windows-msvc/m1/abc123/dirty-1700000000.json",
)
.unwrap();
assert!(parsed.is_dirty());
assert_eq!(parsed.set.target_triple, "x86_64-pc-windows-msvc");
assert_eq!(parsed.set.machine_key, "m1");
}
#[test]
fn parse_key_recognizes_a_blessing_sidecar() {
let parsed = parse_key(
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/abc123/bless-1700000000.json",
)
.unwrap();
assert!(parsed.is_bless());
assert!(!parsed.is_dirty());
assert_eq!(parsed.commit, "abc123");
}
#[test]
fn bless_key_targets_the_sets_commit_directory() {
let parsed = parse_key(
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/abc123/clean.json",
)
.unwrap();
assert_eq!(
parsed.bless_key(1_700_000_000),
"v1/folo/objects/callgrind/x86_64-unknown-linux-gnu/synthetic/abc123/bless-1700000000.json"
);
}
#[test]
fn parse_key_rejects_malformed_keys() {
assert!(parse_key("v2/folo/objects/callgrind/t/m/c/f.json").is_none());
assert!(parse_key("v1/folo/callgrind/t/m/c/f.json").is_none());
assert!(parse_key("v1/folo/data/callgrind/t/m/c/f.json").is_none());
assert!(parse_key("v1/folo/objects/callgrind/t/m/f.json").is_none());
assert!(parse_key("v1/folo/objects/callgrind/t/m/c/sub/f.json").is_none());
assert!(parse_key("v1/folo/objects/callgrind/t//c/f.json").is_none());
assert!(parse_key("v1/folo/objects/callgrind/t/m/c/").is_none());
}
}