1use std::ops::RangeBounds;
4use std::sync::{Arc, Mutex};
5
6use futures::executor::block_on;
7
8use crate::backend::{default_backend, Backend, BackendDev, BackendServer, BackendServerConfig};
9use crate::server::{Server, ServerBuilder, ServerHandle};
10use crate::database::{DatabaseProxy, ExportDevInfo};
11use crate::dev::{DevProxy, DATABASE_DEV_NAME};
12use crate::error::{Error, ErrorKind, Result};
13use crate::local::{self, LocalDevHandle};
14
15use super::database::{TestDatabase, TestDatabaseDev};
16
17
18const HOST: &str = "127.0.0.1";
20
21#[derive(Debug)]
49pub struct TestContext {
50 servers: Mutex<Vec<ServerHandle>>,
52 devs: Mutex<Vec<(Arc<str>, LocalDevHandle)>>,
54 _database_handle: LocalDevHandle,
56 own: Arc<dyn BackendServer>,
58 test_database: Arc<TestDatabase>,
59 database: DatabaseProxy,
60 host: Arc<str>,
62 backend: Arc<dyn Backend>,
63}
64
65impl TestContext {
66
67 pub fn new() -> Result<Self> {
69 Self::with_port(0..=0)
70 }
71
72 pub fn with_port<R: RangeBounds<u16>>(port: R) -> Result<Self> {
75
76 let backend = default_backend();
77 let own = block_on(backend.serve(&BackendServerConfig::new()
78 .host(HOST)
79 .port(port)))?;
80
81 let test_database = Arc::new(TestDatabase::default());
84 let dev = Arc::new(TestDatabaseDev(Arc::clone(&test_database))) as Arc<dyn BackendDev>;
85 let itr = own.add(DATABASE_DEV_NAME, Arc::clone(&dev))?;
86 let database_handle = local::register(&dev, itr, own.host(), own.port(), Arc::from(DATABASE_DEV_NAME));
87
88 let database = DatabaseProxy::new_with_backend(Arc::clone(&backend), &own.host(), own.port());
89
90 Ok(Self {
91 servers: Mutex::default(),
92 devs: Mutex::default(),
93 _database_handle: database_handle,
94 host: own.host(),
95 own,
96 test_database,
97 database,
98 backend,
99 })
100
101 }
102
103 pub fn host(&self) -> &str {
105 &self.host
106 }
107
108 pub fn port(&self) -> u16 {
110 self.own.port()
111 }
112
113 pub fn database(&self) -> &DatabaseProxy {
116 &self.database
117 }
118
119 pub fn test_database(&self) -> &Arc<TestDatabase> {
122 &self.test_database
123 }
124
125 pub fn serve(&self, builder: ServerBuilder) -> Result<Arc<Server>> {
134
135 let builder = builder.stop_on_terminate(false);
136 #[cfg(feature = "cli")]
137 let builder = builder.terminate_on_signals(false);
138
139 let handle = builder
140 .backend(Arc::clone(&self.backend))
141 .database(self.database.clone())
142 .host(HOST)
143 .serve()?;
144
145 let server = handle.server_arc();
146 self.servers.lock().unwrap().push(handle);
147 Ok(server)
148
149 }
150
151 pub fn server(&self, name: &str) -> Option<Arc<Server>> {
153 self.servers.lock().unwrap().iter()
154 .find(|handle| handle.server().name().eq_ignore_ascii_case(name))
155 .map(ServerHandle::server_arc)
156 }
157
158 pub fn stop(&self, name: &str) -> Result<()> {
164 let server = self.server(name).ok_or_else(|| Error::new(ErrorKind::DevNotFound)
165 .with_description(format!("Server {name:?} is not served by this context")))?;
166 let stopped = server.stop();
167 server.wait();
168 stopped
169 }
170
171 pub fn serve_dev(&self, dev: Arc<dyn BackendDev>) -> Result<()> {
180
181 let name = block_on(dev.name())?;
182 let mut devs = self.devs.lock().unwrap();
183
184 if let Some(index) = devs.iter().position(|(other, _)| other.eq_ignore_ascii_case(&name)) {
185 devs.remove(index);
186 self.own.remove(&name);
187 }
188
189 let itr = self.own.add(&name, Arc::clone(&dev))?;
190 let handle = local::register(&dev, Arc::clone(&itr), self.own.host(), self.own.port(), Arc::clone(&name));
191
192 let exported = self.database.export_dev(ExportDevInfo {
193 name: (&*name).into(),
194 itr: (&*itr).into(),
195 host: (&*self.own.host()).into(),
196 version: self.own.version().to_string().into(),
197 pid: std::process::id() as i32,
198 });
199
200 if let Err(err) = exported {
201 drop(handle);
202 self.own.remove(&name);
203 return Err(err);
204 }
205
206 devs.push((name, handle));
207 Ok(())
208
209 }
210
211 pub fn dev(&self, name: &str) -> DevProxy {
213 DevProxy::new_from_database_with_backend(Arc::clone(&self.backend), &self.database, name)
214 }
215
216 pub fn is_local(&self, name: &str) -> Result<bool> {
221 let info = self.database.import_dev(name)?;
222 Ok(local::find(info.itr.value()).is_some())
223 }
224
225 pub fn backend_dev(&self, name: &str) -> Result<Arc<dyn BackendDev>> {
230 let info = self.database.import_dev(name)?;
231 match local::find(info.itr.value()) {
232 Some(dev) => Ok(dev),
233 None => block_on(self.backend.connect(info.itr.value())),
234 }
235 }
236
237}
238
239impl Drop for TestContext {
240
241 fn drop(&mut self) {
244 self.servers.get_mut().unwrap().clear();
245 self.devs.get_mut().unwrap().clear();
246 }
247
248}
249
250#[cfg(test)]
251mod tests {
252
253 use super::*;
254
255 use crate::server::ClassDef;
256
257 #[test]
260 fn two_servers() {
261
262 let ctx = TestContext::new().unwrap();
263 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor"), ("test/motor/2", "Motor")]).unwrap();
264 ctx.database().add_server("Slit/test", &[("test/slit/1", "Slit")]).unwrap();
265
266 let motor = ctx.serve(ServerBuilder::new("Motor", "test").class(ClassDef::<()>::new_default("Motor"))).unwrap();
267 assert_ne!(motor.backend().unwrap().port(), 0);
268 ctx.serve(ServerBuilder::new("Slit", "test").class(ClassDef::<()>::new_default("Slit"))).unwrap();
269
270 for name in ["test/motor/1", "test/motor/2", "test/slit/1"] {
271 let dev = ctx.dev(name);
272 dev.ping().unwrap();
273 assert_eq!(&*dev.name().unwrap(), name);
274 }
275
276 let admin = ctx.dev("test/motor/1").admin_dev().unwrap();
277 assert_eq!(admin.name().unwrap().to_lowercase(), "dserver/motor/test");
278
279 ctx.stop("Motor/test").unwrap();
280 ctx.stop("Slit/test").unwrap();
281 let unexported = ctx.test_database().log().into_iter().filter(|(name, _)| name == "DbUnExportServer").count();
282 assert_eq!(unexported, 2);
283
284 }
285
286 #[test]
289 fn not_local() {
290
291 let ctx = TestContext::new().unwrap();
292 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor")]).unwrap();
293 ctx.serve(ServerBuilder::new("Motor", "test").local(false).class(ClassDef::<()>::new_default("Motor"))).unwrap();
294
295 let database = block_on(default_backend().connect_at(ctx.host(), ctx.port(), DATABASE_DEV_NAME)).unwrap();
296 block_on(database.ping()).unwrap();
297
298 let itr = ctx.database().import_dev("test/motor/1").unwrap().itr;
299 assert!(local::find(itr.value()).is_none());
300
301 let dev = ctx.dev("test/motor/1");
302 assert_eq!(dev.admin_dev_name().unwrap().to_lowercase(), "dserver/motor/test");
303 let remote = ctx.backend_dev("test/motor/1").unwrap();
304 assert_eq!(block_on(remote.admin_dev_name()).unwrap().to_lowercase(), "dserver/motor/test");
305
306 }
307
308 #[test]
310 fn stop() {
311
312 let ctx = TestContext::new().unwrap();
313 ctx.database().add_server("Motor/test", &[("test/motor/1", "Motor")]).unwrap();
314 ctx.serve(ServerBuilder::new("Motor", "test").class(ClassDef::<()>::new_default("Motor"))).unwrap();
315
316 ctx.stop("motor/TEST").unwrap();
317 assert!(ctx.dev("test/motor/1").ping().is_err());
318 assert!(ctx.server("Motor/test").is_some());
319 assert!(ctx.stop("Other/test").is_err());
320
321 }
322
323 #[test]
327 fn serve_dev() {
328
329 let other = TestContext::new().unwrap();
330 other.database().add_server("Motor/other", &[("test/motor/1", "Motor")]).unwrap();
331 other.serve(ServerBuilder::new("Motor", "other").class(ClassDef::<()>::new_default("Motor"))).unwrap();
332 let dev = other.backend_dev("test/motor/1").unwrap();
333
334 let ctx = TestContext::new().unwrap();
335
336 assert!(ctx.serve_dev(Arc::clone(&dev)).is_err());
338
339 ctx.database().add_dev("test/motor/1", "Motor", "Motor/test").unwrap();
340 ctx.serve_dev(dev).unwrap();
341
342 let proxy = ctx.dev("test/motor/1");
344 proxy.ping().unwrap();
345 assert_eq!(proxy.admin_dev_name().unwrap().to_lowercase(), "dserver/motor/other");
346
347 let itr = ctx.database().import_dev("test/motor/1").unwrap().itr;
349 let remote = block_on(default_backend().connect(itr.value())).unwrap();
350 assert_eq!(block_on(remote.admin_dev_name()).unwrap().to_lowercase(), "dserver/motor/other");
351
352 let port = ctx.port();
355 drop(ctx);
356 assert!(local::find(itr.value()).is_none());
357 assert!(local::find_at("localhost", port, DATABASE_DEV_NAME).is_none());
358 other.dev("test/motor/1").ping().unwrap();
359
360 }
361
362}
363