mod compare;
mod fingerprint;
pub use compare::BaselineComparison;
pub use fingerprint::{Fingerprint, hash_bytes, hash_str};
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::confidence::ConfidenceLevel;
use crate::metadata::InstrumentationMode;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BaselineRecord {
pub scenario: String,
pub actual_units: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub budget: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub git_commit: Option<String>,
pub fingerprint: Fingerprint,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub solana_versions: Vec<String>,
pub profiler_version: String,
pub instrumentation: InstrumentationMode,
pub confidence: ConfidenceLevel,
#[serde(default)]
pub approved: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct BaselineStore {
#[serde(default = "default_version")]
pub version: u32,
#[serde(default)]
pub records: BTreeMap<String, BaselineRecord>,
}
fn default_version() -> u32 {
1
}
impl BaselineStore {
#[must_use]
pub fn new() -> Self {
Self {
version: default_version(),
records: BTreeMap::new(),
}
}
pub fn insert(&mut self, record: BaselineRecord) {
self.records.insert(record.scenario.clone(), record);
}
#[must_use]
pub fn get(&self, scenario: &str) -> Option<&BaselineRecord> {
self.records.get(scenario)
}
pub fn approve(&mut self, scenario: &str) -> bool {
match self.records.get_mut(scenario) {
Some(r) => {
r.approved = true;
true
}
None => false,
}
}
#[cfg(feature = "json")]
pub fn to_json(&self) -> crate::Result<String> {
Ok(serde_json::to_string_pretty(self)?)
}
#[cfg(feature = "json")]
pub fn from_json(s: &str) -> crate::Result<Self> {
Ok(serde_json::from_str(s)?)
}
#[cfg(feature = "json")]
pub fn load(path: &std::path::Path) -> crate::Result<Self> {
match std::fs::read_to_string(path) {
Ok(s) => Self::from_json(&s),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self::new()),
Err(e) => Err(e.into()),
}
}
#[cfg(feature = "json")]
pub fn save(&self, path: &std::path::Path) -> crate::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, self.to_json()?)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn record(name: &str, cu: u64) -> BaselineRecord {
BaselineRecord {
scenario: name.into(),
actual_units: cu,
budget: Some(100_000),
timestamp: None,
git_commit: None,
fingerprint: Fingerprint::new(name, "fix", "cfg", None),
solana_versions: Vec::new(),
profiler_version: "0.1.0".into(),
instrumentation: InstrumentationMode::Off,
confidence: ConfidenceLevel::High,
approved: false,
}
}
#[test]
fn insert_get_approve() {
let mut store = BaselineStore::new();
store.insert(record("swap", 95_000));
assert_eq!(store.get("swap").map(|r| r.actual_units), Some(95_000));
assert!(store.approve("swap"));
assert!(store.get("swap").unwrap().approved);
assert!(!store.approve("missing"));
}
#[cfg(feature = "json")]
#[test]
fn json_round_trip_is_stable() {
let mut store = BaselineStore::new();
store.insert(record("b", 2));
store.insert(record("a", 1));
let json = store.to_json().unwrap();
assert!(json.find("\"a\"").unwrap() < json.find("\"b\"").unwrap());
let back = BaselineStore::from_json(&json).unwrap();
assert_eq!(store, back);
}
}