Skip to main content

repolith_cache/
lib.rs

1//! Cache implementations for the repolith orchestration engine.
2//!
3//! The [`Cache`] trait and [`CacheError`] type are defined in
4//! [`repolith_core::cache`] and re-exported here for convenience. This crate
5//! hosts the concrete backends (`SQLite`, in-memory, …) that satisfy the trait.
6
7pub use repolith_core::cache::{Cache, CacheError, Result};
8
9/// SQLite-backed [`Cache`] implementation. See [`sqlite::SqliteCache`].
10pub mod sqlite;
11pub use sqlite::SqliteCache;
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16    use async_trait::async_trait;
17    use repolith_core::types::{ActionId, BuildEvent, Sha256};
18
19    struct MockCache {
20        events: std::collections::HashMap<ActionId, BuildEvent>,
21    }
22
23    impl MockCache {
24        fn new() -> Self {
25            Self {
26                events: std::collections::HashMap::new(),
27            }
28        }
29    }
30
31    #[async_trait]
32    impl Cache for MockCache {
33        async fn last_build(&self, id: &ActionId) -> Option<BuildEvent> {
34            self.events.get(id).cloned()
35        }
36
37        async fn record(&mut self, event: BuildEvent) -> Result<()> {
38            let id = match &event {
39                BuildEvent::Success { id, .. } | BuildEvent::Failed { id, .. } => id.clone(),
40            };
41            self.events.insert(id, event);
42            Ok(())
43        }
44    }
45
46    #[tokio::test]
47    async fn test_mock_cache() {
48        let mut cache = MockCache::new();
49        let id = ActionId("test-action".to_string());
50
51        // Initially empty
52        assert!(cache.last_build(&id).await.is_none());
53
54        // Record an event
55        let event = BuildEvent::Success {
56            id: id.clone(),
57            input: Sha256([0; 32]),
58            output: Sha256([1; 32]),
59            ms: 100,
60        };
61
62        cache.record(event.clone()).await.unwrap();
63
64        // Retrieve the event
65        let retrieved = cache.last_build(&id).await.unwrap();
66        match retrieved {
67            BuildEvent::Success { ms, .. } => assert_eq!(ms, 100),
68            BuildEvent::Failed { .. } => panic!("Expected Success event"),
69        }
70    }
71
72    #[test]
73    fn test_cache_error_display() {
74        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
75        let err1 = CacheError::Io(io_err);
76        assert_eq!(err1.to_string(), "io: file missing");
77
78        let err2 = CacheError::Backend("db locked".to_string());
79        assert_eq!(err2.to_string(), "backend: db locked");
80    }
81
82    #[test]
83    fn test_cache_is_object_safe() {
84        // This line will fail to compile if `Cache` is not object-safe.
85        let _cache: Box<dyn Cache> = Box::new(MockCache::new());
86    }
87}