use super::key::{entry_id_bytes, CacheKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpotCheck {
Never,
Always,
Fraction {
numerator: u32,
denominator: u32,
},
}
impl Default for SpotCheck {
fn default() -> Self {
Self::Fraction {
numerator: 1,
denominator: 32,
}
}
}
impl SpotCheck {
#[must_use]
pub fn should_sample(&self, key: &CacheKey, check_name: &str) -> bool {
match *self {
Self::Never => false,
Self::Always => true,
Self::Fraction {
numerator,
denominator,
} => {
if denominator == 0 || numerator == 0 {
return false;
}
if numerator >= denominator {
return true;
}
let bytes = entry_id_bytes(key, check_name);
let bucket = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
bucket % denominator < numerator
}
}
}
}
#[cfg(test)]
mod tests {
use crypto::{Basis, BasisKind, StateRef};
use super::*;
use crate::model::ExecutionContext;
fn key() -> CacheKey {
CacheKey::derive(
&std::collections::BTreeMap::new(),
&ExecutionContext {
repo: "test/repo".to_string(),
state: StateRef {
content_hash: "state".to_string(),
change_id: "change".to_string(),
logical_change_id: None,
},
basis: Basis {
kind: BasisKind::Branch,
evaluated_tree_digest: "tree".to_string(),
},
definition_digest: "definition".to_string(),
toolchain: None,
pick_id: None,
attempt: 1,
runner: None,
image_digest: None,
},
&ci_config::Check {
name: "build".to_string(),
class: ci_config::CheckClass::Required,
command: vec!["true".to_string()],
timeout_secs: 1,
env: std::collections::BTreeMap::new(),
services: Vec::new(),
cache_paths: Vec::new(),
retry: ci_config::Retry::default(),
triggers: Vec::new(),
supersede: false,
isolation: None,
},
)
}
#[test]
fn fraction_is_deterministic_for_a_key() {
let key = key();
let policy = SpotCheck::Fraction {
numerator: 1,
denominator: 2,
};
let first = policy.should_sample(&key, "build");
assert_eq!(first, policy.should_sample(&key, "build"));
assert!(SpotCheck::Always.should_sample(&key, "build"));
assert!(!SpotCheck::Never.should_sample(&key, "build"));
}
}