Skip to main content

cu_profiler_core/backend/
recorded.rs

1//! Deterministic backend that replays recorded logs.
2//!
3//! This is the backbone of testing and the `inspect` command: it depends on no
4//! Solana runtime, so parser, budget, and CI logic can be developed and tested
5//! in isolation.
6
7use std::collections::HashMap;
8
9use crate::Result;
10use crate::backend::{ExecutionBackend, SimulationOutput};
11use crate::error::Error;
12use crate::metadata::BackendKind;
13use crate::scenario::Scenario;
14
15/// A backend that returns pre-recorded logs keyed by scenario name.
16#[derive(Debug, Default, Clone)]
17pub struct RecordedLogsBackend {
18    by_scenario: HashMap<String, SimulationOutput>,
19}
20
21impl RecordedLogsBackend {
22    /// An empty backend.
23    #[must_use]
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Register logs for a scenario. `success` reflects the simulated outcome.
29    pub fn insert(&mut self, scenario: impl Into<String>, logs: Vec<String>, success: bool) {
30        self.by_scenario
31            .insert(scenario.into(), SimulationOutput { logs, success });
32    }
33
34    /// Register logs from a raw multi-line blob (splitting on newlines).
35    pub fn insert_blob(&mut self, scenario: impl Into<String>, blob: &str, success: bool) {
36        let logs = blob.lines().map(str::to_string).collect();
37        self.insert(scenario, logs, success);
38    }
39}
40
41impl ExecutionBackend for RecordedLogsBackend {
42    fn kind(&self) -> BackendKind {
43        BackendKind::Recorded
44    }
45
46    /// Replaying a fixed log is deterministic — multi-sampling is a no-op here.
47    fn is_deterministic(&self) -> bool {
48        true
49    }
50
51    fn run(&self, scenario: &Scenario) -> Result<SimulationOutput> {
52        self.by_scenario
53            .get(&scenario.name)
54            .cloned()
55            .ok_or_else(|| {
56                Error::Simulation(format!(
57                    "no recorded logs registered for scenario `{}`",
58                    scenario.name
59                ))
60            })
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn replays_registered_logs() {
70        let mut backend = RecordedLogsBackend::new();
71        backend.insert_blob("swap", "Program X invoke [1]\nProgram X success", true);
72        let out = backend.run(&Scenario::new("swap")).unwrap();
73        assert_eq!(out.logs.len(), 2);
74        assert!(out.success);
75    }
76
77    #[test]
78    fn missing_scenario_errors_clearly() {
79        let backend = RecordedLogsBackend::new();
80        let err = backend.run(&Scenario::new("nope")).unwrap_err();
81        assert!(err.to_string().contains("no recorded logs"));
82    }
83}