blue-lang-cli 0.0.208

The blue command line: run, fmt, ast, erase, check.
use std::time::Duration;

use blue_lang_mondou::def_hash::{DefHash, TestIdentities};
use blue_lang_mondou::store::{self, Bucket, Identity, Inclusion, Store};

pub(crate) const ENV: &str = "BLUE_TEST_CACHE";

pub(crate) fn parse(value: &str) -> Option<bool> {
    match value.trim().to_ascii_lowercase().as_str() {
        "" | "on" | "1" | "true" | "yes" => Some(true),
        "off" | "0" | "false" | "no" => Some(false),
        _ => None,
    }
}

pub(crate) struct TestCache {
    store: Store,
    bucket: DefHash,
    passed: Bucket,
}

impl TestCache {
    pub(crate) fn open(no_cache: bool, reports_what_ran: bool) -> Result<Option<Self>, String> {
        let on = match std::env::var(ENV) {
            Err(_) => true,
            Ok(v) => parse(&v)
                .ok_or_else(|| format!("{ENV} is \"{v}\"; it takes on (the default) or off"))?,
        };
        if !on || no_cache || reports_what_ran {
            return Ok(None);
        }
        let Some(store) = store::open_tier("test") else {
            return Ok(None);
        };
        let bucket = DefHash(store.identity().0);
        let passed = store.load(&bucket);
        Ok(Some(Self {
            store,
            bucket,
            passed,
        }))
    }

    pub(crate) fn key(&self, ids: &TestIdentities, test: usize, bounds: &str) -> Option<String> {
        let own = ids.tests.get(&test)?;
        let id = Identity::from_parts(&[
            b"blue test cache v1",
            &self.store.identity().0,
            own.as_bytes(),
            ids.preamble.as_bytes(),
            bounds.as_bytes(),
        ]);
        Some(DefHash(id.0).hex())
    }

    pub(crate) fn passed(&self, key: &str) -> bool {
        DefHash::from_hex(key).is_some_and(|k| self.passed.entries.contains_key(&k.0))
    }

    pub(crate) fn root(&self) -> [u8; 32] {
        self.passed.root()
    }

    pub(crate) fn total(&self) -> usize {
        self.passed.entries.len()
    }

    pub(crate) fn prove(&self, key: &str) -> Option<Inclusion> {
        DefHash::from_hex(key).and_then(|k| self.passed.prove(&k.0))
    }

    pub(crate) fn record(&self, keys: &[String]) {
        let fresh: Vec<([u8; 32], Duration, Vec<u8>)> = keys
            .iter()
            .filter(|k| !self.passed(k))
            .filter_map(|k| DefHash::from_hex(k))
            .map(|k| (k.0, Duration::ZERO, Vec::new()))
            .collect();
        if fresh.is_empty() {
            return;
        }
        match self.store.append(&self.bucket, &fresh) {
            Ok(_) => {
                let _ = self.store.evict();
            }
            Err(e) => eprintln!(
                "blue: the test cache could not be written to {}: {e}",
                self.store.dir().display()
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::parse;

    #[test]
    fn the_cache_is_on_unless_named_off() {
        for off in ["off", "0", "false", "no", "OFF"] {
            assert_eq!(parse(off), Some(false), "{off}");
        }
        for on in ["", "on", "1", "true", "YES"] {
            assert_eq!(parse(on), Some(true), "{on}");
        }
        assert_eq!(parse("store"), None);
    }
}