rpytest_daemon/
fixtures.rs1use crate::models::{FixtureConfig, FixtureScope, FixtureState};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::Path;
7use std::sync::{Arc, Mutex};
8use std::time::SystemTime;
9use tracing::debug;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SessionState {
14 pub session_id: String,
15 pub repo_path: String,
16 pub python_path: String,
17 pub fixtures: HashMap<String, FixtureState>,
18 pub created_at: f64,
19 pub last_run_at: f64,
20 pub total_runs: u32,
21 pub enabled: bool,
22}
23
24impl Default for SessionState {
25 fn default() -> Self {
26 let now = SystemTime::now()
27 .duration_since(SystemTime::UNIX_EPOCH)
28 .unwrap()
29 .as_secs_f64();
30
31 SessionState {
32 session_id: String::new(),
33 repo_path: String::new(),
34 python_path: String::new(),
35 fixtures: HashMap::new(),
36 created_at: now,
37 last_run_at: now,
38 total_runs: 0,
39 enabled: false,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct FixtureManager {
47 sessions: Arc<Mutex<HashMap<String, SessionState>>>,
49 config: FixtureConfig,
51 max_age_seconds: f64,
53}
54
55impl Default for FixtureManager {
56 fn default() -> Self {
57 FixtureManager {
58 sessions: Arc::new(Mutex::new(HashMap::new())),
59 config: FixtureConfig::default(),
60 max_age_seconds: 600.0, }
62 }
63}
64
65impl FixtureManager {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn create_session(
73 &self,
74 context_id: &str,
75 repo_path: &Path,
76 python_path: &Path,
77 ) -> SessionState {
78 let now = SystemTime::now()
79 .duration_since(SystemTime::UNIX_EPOCH)
80 .unwrap()
81 .as_secs_f64();
82
83 let session = SessionState {
84 session_id: format!("{}-{}", context_id, now as u64),
85 repo_path: repo_path.to_string_lossy().to_string(),
86 python_path: python_path.to_string_lossy().to_string(),
87 fixtures: HashMap::new(),
88 created_at: now,
89 last_run_at: now,
90 total_runs: 0,
91 enabled: false,
92 };
93
94 let mut sessions = self.sessions.lock().unwrap();
95 sessions.insert(context_id.to_string(), session.clone());
96
97 debug!("Created fixture session for {}", context_id);
98 session
99 }
100
101 pub fn get_session(&self, context_id: &str) -> Option<SessionState> {
103 let sessions = self.sessions.lock().unwrap();
104 sessions.get(context_id).cloned()
105 }
106
107 pub fn enable_reuse(&self, context_id: &str) -> bool {
109 let mut sessions = self.sessions.lock().unwrap();
110 if let Some(session) = sessions.get_mut(context_id) {
111 session.enabled = true;
112 debug!("Enabled fixture reuse for {}", context_id);
113 true
114 } else {
115 false
116 }
117 }
118
119 pub fn disable_reuse(&self, context_id: &str) -> bool {
121 let mut sessions = self.sessions.lock().unwrap();
122 if let Some(session) = sessions.get_mut(context_id) {
123 session.enabled = false;
124 session.fixtures.clear();
125 debug!("Disabled fixture reuse for {}", context_id);
126 true
127 } else {
128 false
129 }
130 }
131
132 pub fn mark_fixture_used(&self, context_id: &str, name: &str, scope: FixtureScope) {
134 let now = SystemTime::now()
135 .duration_since(SystemTime::UNIX_EPOCH)
136 .unwrap()
137 .as_secs_f64();
138
139 let mut sessions = self.sessions.lock().unwrap();
140 if let Some(session) = sessions.get_mut(context_id) {
141 if !session.enabled {
142 return;
143 }
144
145 let fixture =
146 session
147 .fixtures
148 .entry(name.to_string())
149 .or_insert_with(|| FixtureState {
150 name: name.to_string(),
151 scope,
152 created_at: now,
153 last_used: now,
154 use_count: 0,
155 teardown_pending: false,
156 });
157 fixture.last_used = now;
158 fixture.use_count += 1;
159 }
160 }
161
162 pub fn mark_run_complete(&self, context_id: &str) {
164 let now = SystemTime::now()
165 .duration_since(SystemTime::UNIX_EPOCH)
166 .unwrap()
167 .as_secs_f64();
168
169 let mut sessions = self.sessions.lock().unwrap();
170 if let Some(session) = sessions.get_mut(context_id) {
171 session.last_run_at = now;
172 session.total_runs += 1;
173 }
174 }
175
176 pub fn get_stale_fixtures(&self, context_id: &str) -> Vec<String> {
178 let now = SystemTime::now()
179 .duration_since(SystemTime::UNIX_EPOCH)
180 .unwrap()
181 .as_secs_f64();
182
183 let mut stale = Vec::new();
184
185 let sessions = self.sessions.lock().unwrap();
186 if let Some(session) = sessions.get(context_id) {
187 for (name, state) in &session.fixtures {
188 if now - state.last_used > self.max_age_seconds {
189 stale.push(name.clone());
190 }
191 }
192 }
193
194 stale
195 }
196
197 pub fn clear_all(&self) {
199 let mut sessions = self.sessions.lock().unwrap();
200 sessions.clear();
201 }
202
203 pub fn remove_session(&self, context_id: &str) {
205 let mut sessions = self.sessions.lock().unwrap();
206 sessions.remove(context_id);
207 }
208
209 pub fn session_count(&self) -> usize {
211 self.sessions.lock().unwrap().len()
212 }
213
214 pub fn configure(&mut self, config: FixtureConfig) {
216 self.config = config.clone();
217 self.max_age_seconds = config.max_age_seconds;
218 }
219
220 pub fn get_config(&self) -> &FixtureConfig {
222 &self.config
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use std::path::PathBuf;
230
231 #[test]
232 fn test_create_session() {
233 let manager = FixtureManager::new();
234 let repo_path = PathBuf::from("/test/repo");
235 let python_path = PathBuf::from("/usr/bin/python");
236
237 let session = manager.create_session("ctx-1", &repo_path, &python_path);
238
239 assert!(!session.session_id.is_empty());
240 assert_eq!(session.repo_path, "/test/repo");
241 assert!(manager.session_count() == 1);
242 }
243
244 #[test]
245 fn test_enable_disable() {
246 let manager = FixtureManager::new();
247 let repo_path = PathBuf::from("/test/repo");
248 let python_path = PathBuf::from("/usr/bin/python");
249
250 manager.create_session("ctx-1", &repo_path, &python_path);
251 assert!(manager.enable_reuse("ctx-1"));
252 assert!(manager.disable_reuse("ctx-1"));
253 }
254
255 #[test]
256 fn test_mark_fixture_used() {
257 let manager = FixtureManager::new();
258 let repo_path = PathBuf::from("/test/repo");
259 let python_path = PathBuf::from("/usr/bin/python");
260
261 manager.create_session("ctx-1", &repo_path, &python_path);
262 manager.enable_reuse("ctx-1");
263 manager.mark_fixture_used("ctx-1", "db_connection", FixtureScope::Session);
264
265 let session = manager.get_session("ctx-1").unwrap();
266 assert!(session.fixtures.contains_key("db_connection"));
267 assert_eq!(session.fixtures["db_connection"].use_count, 1);
268 }
269}