1use std::sync::{Arc, Mutex};
4
5use futures::executor::block_on;
6
7use crate::backend::{default_backend, Backend, BackendDev, BackendServer, BackendServerConfig};
8use crate::server::{Server, ServerBuilder, ServerHandle};
9use crate::database::{DatabaseProxy, ExportDevInfo};
10use crate::dev::{DevProxy, DATABASE_DEV_NAME};
11use crate::error::{Error, ErrorKind, Result};
12use crate::local::{self, LocalDevHandle};
13
14use super::database::{TestDatabase, TestDatabaseDev};
15
16
17const HOST: &str = "127.0.0.1";
19
20#[derive(Debug)]
48pub struct TestContext {
49 servers: Mutex<Vec<ServerHandle>>,
51 devs: Mutex<Vec<(Arc<str>, LocalDevHandle)>>,
53 _database_handle: LocalDevHandle,
55 own: Arc<dyn BackendServer>,
57 test_database: Arc<TestDatabase>,
58 database: DatabaseProxy,
59 host: Arc<str>,
61 backend: Arc<dyn Backend>,
62}
63
64impl TestContext {
65
66 pub fn new() -> Result<Self> {
68 Self::with_port(0)
69 }
70
71 pub fn with_port(port: u16) -> Result<Self> {
74
75 let backend = default_backend();
76 let own = block_on(backend.serve(&BackendServerConfig::new()
77 .with_host(HOST)
78 .with_port(port)))?;
79
80 let test_database = Arc::new(TestDatabase::default());
83 let dev = Arc::new(TestDatabaseDev(Arc::clone(&test_database))) as Arc<dyn BackendDev>;
84 let itr = own.add(DATABASE_DEV_NAME, Arc::clone(&dev))?;
85 let database_handle = local::register(&dev, itr, own.host(), own.port(), Arc::from(DATABASE_DEV_NAME));
86
87 let database = DatabaseProxy::new_with_backend(Arc::clone(&backend), &own.host(), own.port());
88
89 Ok(Self {
90 servers: Mutex::default(),
91 devs: Mutex::default(),
92 _database_handle: database_handle,
93 host: own.host(),
94 own,
95 test_database,
96 database,
97 backend,
98 })
99
100 }
101
102 pub fn host(&self) -> &str {
104 &self.host
105 }
106
107 pub fn port(&self) -> u16 {
109 self.own.port()
110 }
111
112 pub fn database(&self) -> &DatabaseProxy {
115 &self.database
116 }
117
118 pub fn test_database(&self) -> &Arc<TestDatabase> {
121 &self.test_database
122 }
123
124 pub fn serve(&self, builder: ServerBuilder) -> Result<Arc<Server>> {
133 let handle = builder
134 .backend(Arc::clone(&self.backend))
135 .database(self.database.clone())
136 .host(HOST)
137 .port(0)
138 .stop_on_signal(false)
139 .serve()?;
140 let server = handle.server_arc();
141 self.servers.lock().unwrap().push(handle);
142 Ok(server)
143 }
144
145 pub fn server(&self, name: &str) -> Option<Arc<Server>> {
147 self.servers.lock().unwrap().iter()
148 .find(|handle| handle.server().name().eq_ignore_ascii_case(name))
149 .map(ServerHandle::server_arc)
150 }
151
152 pub fn stop(&self, name: &str) -> Result<()> {
158 let server = self.server(name).ok_or_else(|| Error::new(ErrorKind::DevNotFound)
159 .with_description(format!("No server {name} is served by this context")))?;
160 block_on(server.stop())?;
161 let servers = self.servers.lock().unwrap();
162 let handle = servers.iter().find(|handle| handle.server().name().eq_ignore_ascii_case(name)).unwrap();
163 handle.wait()
164 }
165
166 pub fn serve_dev(&self, dev: Arc<dyn BackendDev>) -> Result<()> {
175
176 let name = block_on(dev.name())?;
177 let mut devs = self.devs.lock().unwrap();
178
179 if let Some(index) = devs.iter().position(|(other, _)| other.eq_ignore_ascii_case(&name)) {
180 devs.remove(index);
181 self.own.remove(&name);
182 }
183
184 let itr = self.own.add(&name, Arc::clone(&dev))?;
185 let handle = local::register(&dev, Arc::clone(&itr), self.own.host(), self.own.port(), Arc::clone(&name));
186
187 let exported = self.database.export_dev(ExportDevInfo {
188 name: (&*name).into(),
189 itr: (&*itr).into(),
190 host: (&*self.own.host()).into(),
191 version: self.own.version().to_string().into(),
192 pid: std::process::id() as i32,
193 });
194
195 if let Err(err) = exported {
196 drop(handle);
197 self.own.remove(&name);
198 return Err(err);
199 }
200
201 devs.push((name, handle));
202 Ok(())
203
204 }
205
206 pub fn dev(&self, name: &str) -> DevProxy {
208 DevProxy::new_from_database_with_backend(Arc::clone(&self.backend), &self.database, name)
209 }
210
211 pub fn is_local(&self, name: &str) -> Result<bool> {
216 let info = self.database.import_dev(name)?;
217 Ok(local::find(info.itr.value()).is_some())
218 }
219
220 pub fn backend_dev(&self, name: &str) -> Result<Arc<dyn BackendDev>> {
225 let info = self.database.import_dev(name)?;
226 match local::find(info.itr.value()) {
227 Some(dev) => Ok(dev),
228 None => block_on(self.backend.connect(info.itr.value())),
229 }
230 }
231
232}
233
234impl Drop for TestContext {
235
236 fn drop(&mut self) {
239 self.servers.get_mut().unwrap().clear();
240 self.devs.get_mut().unwrap().clear();
241 }
242
243}
244
245#[cfg(test)]
246mod tests {
247
248 use super::*;
249
250 use crate::server::ClassDef;
251
252 #[test]
255 fn two_servers() {
256
257 let ctx = TestContext::new().unwrap();
258 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor"), ("test/motor/2", "Motor")]).unwrap();
259 ctx.database().add_server("Slit/test", &[("test/slit/1", "Slit")]).unwrap();
260
261 let motor = ctx.serve(ServerBuilder::new("Motor", "test").class(ClassDef::<()>::new_default("Motor")).unwrap()).unwrap();
262 assert_ne!(motor.backend().unwrap().port(), 0);
263 ctx.serve(ServerBuilder::new("Slit", "test").class(ClassDef::<()>::new_default("Slit")).unwrap()).unwrap();
264
265 for name in ["test/motor/1", "test/motor/2", "test/slit/1"] {
266 let dev = ctx.dev(name);
267 dev.ping().unwrap();
268 assert_eq!(&*dev.name().unwrap(), name);
269 }
270
271 let admin = ctx.dev("test/motor/1").admin_dev().unwrap();
272 assert_eq!(admin.name().unwrap().to_lowercase(), "dserver/motor/test");
273
274 ctx.stop("Motor/test").unwrap();
275 ctx.stop("Slit/test").unwrap();
276 let unexported = ctx.test_database().log().into_iter().filter(|(name, _)| name == "DbUnExportServer").count();
277 assert_eq!(unexported, 2);
278
279 }
280
281 #[test]
284 fn not_local() {
285
286 let ctx = TestContext::new().unwrap();
287 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor")]).unwrap();
288 ctx.serve(ServerBuilder::new("Motor", "test").local(false).class(ClassDef::<()>::new_default("Motor")).unwrap()).unwrap();
289
290 let database = block_on(default_backend().connect_at(ctx.host(), ctx.port(), DATABASE_DEV_NAME)).unwrap();
291 block_on(database.ping()).unwrap();
292
293 let itr = ctx.database().import_dev("test/motor/1").unwrap().itr;
294 assert!(local::find(itr.value()).is_none());
295
296 let dev = ctx.dev("test/motor/1");
297 assert_eq!(dev.admin_dev_name().unwrap().to_lowercase(), "dserver/motor/test");
298 let remote = ctx.backend_dev("test/motor/1").unwrap();
299 assert_eq!(block_on(remote.admin_dev_name()).unwrap().to_lowercase(), "dserver/motor/test");
300
301 }
302
303 #[test]
305 fn stop() {
306
307 let ctx = TestContext::new().unwrap();
308 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor")]).unwrap();
309 ctx.serve(ServerBuilder::new("Motor", "test").class(ClassDef::<()>::new_default("Motor")).unwrap()).unwrap();
310
311 ctx.stop("motor/TEST").unwrap();
312 assert!(ctx.dev("test/motor/1").ping().is_err());
313 assert!(ctx.server("Motor/test").is_some());
314 assert!(ctx.stop("Other/test").is_err());
315
316 }
317
318 #[test]
322 fn serve_dev() {
323
324 let other = TestContext::new().unwrap();
325 other.database().add_server("Motor/other", &[("test/motor/1", "Motor")]).unwrap();
326 other.serve(ServerBuilder::new("Motor", "other").class(ClassDef::<()>::new_default("Motor")).unwrap()).unwrap();
327 let dev = other.backend_dev("test/motor/1").unwrap();
328
329 let ctx = TestContext::new().unwrap();
330
331 assert!(ctx.serve_dev(Arc::clone(&dev)).is_err());
333
334 ctx.database().add_dev("test/motor/1", "Motor", "Motor/test").unwrap();
335 ctx.serve_dev(dev).unwrap();
336
337 let proxy = ctx.dev("test/motor/1");
339 proxy.ping().unwrap();
340 assert_eq!(proxy.admin_dev_name().unwrap().to_lowercase(), "dserver/motor/other");
341
342 let itr = ctx.database().import_dev("test/motor/1").unwrap().itr;
344 let remote = block_on(default_backend().connect(itr.value())).unwrap();
345 assert_eq!(block_on(remote.admin_dev_name()).unwrap().to_lowercase(), "dserver/motor/other");
346
347 let database = ctx.database().clone();
348 drop(ctx);
349 assert!(local::find(itr.value()).is_none());
350 assert!(database.ping().is_err());
351 other.dev("test/motor/1").ping().unwrap();
352
353 }
354
355}
356