1use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12use std::time::SystemTime;
13use std::fmt;
14
15use futures::future::{ready, BoxFuture};
16use futures::stream::BoxStream;
17
18use crate::dev::{AttrConfig, AttrInfo, AttrRead, AttrValueEvent, CommandInfo, DevInfo, DevSource, DevState, Named, NamedResult};
19use crate::error::{Error, ErrorKind, Result, reason};
20use crate::value::{Scalar, Spectrum, Value};
21use crate::backend::BackendDev;
22
23
24pub const NAME: &str = "sys/database/2";
26
27const ADMIN_CLASS: &str = "DServer";
29
30#[derive(Default)]
41pub struct TestDatabase {
42 state: Mutex<State>,
43 log: Mutex<Vec<(String, Value)>>,
45 failing: Mutex<HashMap<String, FailWith>>,
47}
48
49type FailWith = Box<dyn Fn() -> Error + Send + Sync>;
51
52impl fmt::Debug for TestDatabase {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.debug_struct("TestDatabase")
55 .field("state", &self.state)
56 .field("log", &self.log)
57 .field("failing", &self.failing.lock().unwrap().keys())
58 .finish()
59 }
60}
61
62impl TestDatabase {
63
64 pub fn log(&self) -> Vec<(String, Value)> {
67 self.log.lock().unwrap().clone()
68 }
69
70 pub fn clear_log(&self) {
72 self.log.lock().unwrap().clear();
73 }
74
75 pub fn add_fail(&self, name: &str, err: impl Fn() -> Error + Send + Sync + 'static) {
78 self.failing.lock().unwrap().insert(name.to_string(), Box::new(err));
79 }
80
81 pub fn remove_fail(&self, name: &str) {
83 self.failing.lock().unwrap().remove(name);
84 }
85
86 pub fn command(&self, name: &str, input: Value) -> Result<Value> {
88
89 self.log.lock().unwrap().push((name.to_string(), input.clone()));
90
91 if let Some(err) = self.failing.lock().unwrap().get(name) {
92 return Err(err());
93 }
94
95 let mut state = self.state.lock().unwrap();
96
97 let res = match name {
98
99 "DbAddServer" => state.add_server_cmd(input),
101 "DbDeleteServer" => state.delete_server_cmd(input),
102 "DbAddDevice" => state.add_dev_cmd(input),
103 "DbDeleteDevice" => state.delete_dev_cmd(input),
104 "DbGetDeviceClassList" => state.list_server_dev(input),
105
106 "DbExportDevice" | "DbExportServer" => state.export(input),
108 "DbUnExportDevice" => state.unexport_dev(input),
109 "DbUnExportServer" => state.unexport_server(input),
110 "DbImportDevice" => state.import_dev(input),
111
112 "DbGetDeviceProperty" | "DbGetClassProperty" => state.get_prop(Kind::of(name), input),
114 "DbGetDevicePropertyList" | "DbGetClassPropertyList" => state.list_prop(Kind::of(name), input),
115 "DbPutDeviceProperty" | "DbPutClassProperty" => state.put_prop(Kind::of(name), input),
116 "DbDeleteDeviceProperty" | "DbDeleteClassProperty" => state.delete_prop(Kind::of(name), input),
117
118 "DbGetDeviceAttributeProperty2" | "DbGetClassAttributeProperty2" => state.get_attr_prop(Kind::of(name), input),
120 "DbGetDeviceAttributeList" | "DbGetClassAttributeList" => state.list_attr(Kind::of(name), input),
121 "DbPutDeviceAttributeProperty2" | "DbPutClassAttributeProperty2" => state.put_attr_prop(Kind::of(name), input),
122 "DbDeleteDeviceAttributeProperty" | "DbDeleteClassAttributeProperty" => state.delete_attr_prop(Kind::of(name), input),
123
124 _ => Err(Error::not_implemented()),
125
126 };
127
128 res.map_err(|err| err.with_append_description(format_args!("The command {name} of the database of a test")))
129
130 }
131
132}
133
134#[derive(Debug, Default)]
136struct State {
137 servers: HashMap<String, Vec<String>>,
140 devs: HashMap<String, Dev>,
142 props: HashMap<Object, Props>,
144 attr_props: HashMap<Object, Entries<Props>>,
146}
147
148#[derive(Debug)]
150struct Dev {
151 name: String,
152 class: String,
153 server: String,
154 export: Option<Export>,
157 exported: bool,
158}
159
160#[derive(Debug)]
161struct Export {
162 itr: String,
163 host: String,
164 version: String,
165 pid: i32,
166}
167
168type Object = (Kind, String);
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
173enum Kind {
174 Device,
175 Class,
176}
177
178impl Kind {
179
180 fn of(command: &str) -> Self {
182 if command.contains("Class") { Self::Class } else { Self::Device }
183 }
184
185 fn object(self, name: &str) -> Object {
186 (self, name.to_lowercase())
187 }
188
189}
190
191type Props = Entries<Vec<String>>;
193
194#[derive(Debug)]
197struct Entries<T>(Vec<(String, T)>);
198
199impl<T> Default for Entries<T> {
200 fn default() -> Self {
201 Self(Vec::new())
202 }
203}
204
205impl<T> Entries<T> {
206
207 fn get(&self, name: &str) -> Option<&T> {
208 self.0.iter().find(|(other, _)| other.eq_ignore_ascii_case(name)).map(|(_, value)| value)
209 }
210
211 fn entry(&mut self, name: &str) -> &mut T where T: Default {
213 let index = match self.0.iter().position(|(other, _)| other.eq_ignore_ascii_case(name)) {
214 Some(index) => index,
215 None => {
216 self.0.push((name.to_string(), T::default()));
217 self.0.len() - 1
218 }
219 };
220 &mut self.0[index].1
221 }
222
223 fn remove(&mut self, names: &[String]) {
225 self.0.retain(|(name, _)| !names.iter().any(|removed| removed.eq_ignore_ascii_case(name)));
226 }
227
228 fn names(&self, filter: &str) -> Vec<&str> {
230 self.0.iter().map(|(name, _)| name.as_str()).filter(|name| wildcard(filter, name)).collect()
231 }
232
233}
234
235impl State {
238
239 fn add_server_cmd(&mut self, input: Value) -> Result<Value> {
241 let args = strings(input)?;
242 let [server, pairs @ ..] = &args[..] else { return Err(malformed_err()) };
243 if pairs.len() % 2 != 0 {
244 return Err(malformed_err());
245 }
246 self.add_server(server);
247 for pair in pairs.chunks_exact(2) {
248 self.add_dev(server, &pair[0], &pair[1]);
249 }
250 Ok(Value::Void)
251 }
252
253 fn delete_server_cmd(&mut self, input: Value) -> Result<Value> {
254 let server = String::try_from(input)?;
255 for dev in self.servers.remove(&server.to_lowercase()).unwrap_or_default() {
256 self.devs.remove(&dev);
257 }
258 Ok(Value::Void)
259 }
260
261 fn add_dev_cmd(&mut self, input: Value) -> Result<Value> {
263 let args = strings(input)?;
264 let [server, dev, class] = &args[..] else { return Err(malformed_err()) };
265 self.add_server(server);
266 self.add_dev(server, dev, class);
267 Ok(Value::Void)
268 }
269
270 fn delete_dev_cmd(&mut self, input: Value) -> Result<Value> {
271 let dev = String::try_from(input)?.to_lowercase();
272 if let Some(removed) = self.devs.remove(&dev) {
273 self.server_devs(&removed.server).retain(|other| *other != dev);
274 }
275 Ok(Value::Void)
276 }
277
278 fn list_server_dev(&mut self, input: Value) -> Result<Value> {
280 let server = String::try_from(input)?;
281 let listing = self.servers.get(&server.to_lowercase())
282 .into_iter()
283 .flatten()
284 .filter_map(|dev| self.devs.get(dev))
285 .flat_map(|dev| [dev.name.as_str(), dev.class.as_str()])
286 .collect::<Vec<_>>();
287 Ok(Value::from(Spectrum::<str>::from(&listing[..])))
288 }
289
290 fn add_server(&mut self, server: &str) {
293 if !self.servers.contains_key(&server.to_lowercase()) {
294 self.add_dev(server, &format!("dserver/{server}"), ADMIN_CLASS);
295 }
296 }
297
298 fn add_dev(&mut self, server: &str, name: &str, class: &str) {
301
302 let key = name.to_lowercase();
303 let previous = self.devs.remove(&key);
304
305 if let Some(previous) = &previous {
306 self.server_devs(&previous.server).retain(|other| *other != key);
307 }
308 self.server_devs(server).push(key.clone());
309
310 let (export, exported) = previous.map_or((None, false), |dev| (dev.export, dev.exported));
311 self.devs.insert(key, Dev {
312 name: name.to_string(),
313 class: class.to_string(),
314 server: server.to_string(),
315 export,
316 exported,
317 });
318
319 }
320
321 fn server_devs(&mut self, server: &str) -> &mut Vec<String> {
323 self.servers.entry(server.to_lowercase()).or_default()
324 }
325
326}
327
328impl State {
331
332 fn export(&mut self, input: Value) -> Result<Value> {
335 let args = strings(input)?;
336 if args.len() % 5 != 0 {
337 return Err(malformed_err());
338 }
339 for info in args.chunks_exact(5) {
340 let [name, itr, host, pid, version] = info else { unreachable!() };
341 let dev = self.devs.get_mut(&name.to_lowercase())
342 .ok_or_else(|| not_defined_err(name))?;
343 dev.export = Some(Export {
344 itr: itr.clone(),
345 host: host.clone(),
346 pid: pid.parse().map_err(|_| malformed_err())?,
347 version: version.clone(),
348 });
349 dev.exported = true;
350 }
351 Ok(Value::Void)
352 }
353
354 fn unexport_dev(&mut self, input: Value) -> Result<Value> {
355 let dev = String::try_from(input)?;
356 if let Some(dev) = self.devs.get_mut(&dev.to_lowercase()) {
357 dev.exported = false;
358 }
359 Ok(Value::Void)
360 }
361
362 fn unexport_server(&mut self, input: Value) -> Result<Value> {
363 let server = String::try_from(input)?;
364 for dev in self.servers.get(&server.to_lowercase()).into_iter().flatten() {
365 if let Some(dev) = self.devs.get_mut(dev) {
366 dev.exported = false;
367 }
368 }
369 Ok(Value::Void)
370 }
371
372 fn import_dev(&mut self, input: Value) -> Result<Value> {
375 let name = String::try_from(input)?;
376 let dev = self.devs.get(&name.to_lowercase())
377 .ok_or_else(|| not_defined_err(&name))?;
378 let (itr, version, host, pid) = match &dev.export {
379 Some(export) => (&*export.itr, &*export.version, &*export.host, export.pid),
380 None => ("nada", "0", "nada", 0),
381 };
382 let strings = [&*dev.name, itr, version, &*dev.server, host, &*dev.class];
383 Ok(Value::LongString(
384 Spectrum::from(vec![i32::from(dev.exported), pid]),
385 Spectrum::<str>::from(&strings[..]),
386 ))
387 }
388
389}
390
391impl State {
394
395 fn get_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
399 let args = strings(input)?;
400 let [obj_name, prop_names @ ..] = &args[..] else { return Err(malformed_err()) };
401 let props = self.props.get(&kind.object(obj_name));
402 let mut listing = vec![obj_name.clone(), prop_names.len().to_string()];
403 for prop_name in prop_names {
404 let values = props.and_then(|props| props.get(prop_name)).map_or(&[][..], Vec::as_slice);
405 listing.push(prop_name.clone());
406 listing.push(values.len().to_string());
407 listing.extend(values.iter().cloned());
408 }
409 Ok(Value::from(Spectrum::<str>::from(&listing[..])))
410 }
411
412 fn list_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
414 let args = strings(input)?;
415 let [obj_name, filter] = &args[..] else { return Err(malformed_err()) };
416 let names = self.props.get(&kind.object(obj_name)).map_or_else(Vec::new, |props| props.names(filter));
417 Ok(Value::from(Spectrum::<str>::from(&names[..])))
418 }
419
420 fn put_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
423 let args = strings(input)?;
424 let mut cursor = Cursor::new(&args);
425 let props = self.props.entry(kind.object(cursor.next()?)).or_default();
426 for _ in 0..cursor.count()? {
427 let prop_name = cursor.next()?;
428 *props.entry(prop_name) = cursor.values()?.to_vec();
429 }
430 cursor.end()?;
431 Ok(Value::Void)
432 }
433
434 fn delete_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
436 let args = strings(input)?;
437 let [obj_name, prop_names @ ..] = &args[..] else { return Err(malformed_err()) };
438 if let Some(props) = self.props.get_mut(&kind.object(obj_name)) {
439 props.remove(prop_names);
440 }
441 Ok(Value::Void)
442 }
443
444}
445
446impl State {
449
450 fn get_attr_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
454 let args = strings(input)?;
455 let [obj_name, attr_names @ ..] = &args[..] else { return Err(malformed_err()) };
456 let attrs = self.attr_props.get(&kind.object(obj_name));
457 let mut listing = vec![obj_name.clone(), attr_names.len().to_string()];
458 for attr_name in attr_names {
459 let props = attrs.and_then(|attrs| attrs.get(attr_name)).map_or(&[][..], |props| &props.0[..]);
460 listing.push(attr_name.clone());
461 listing.push(props.len().to_string());
462 for (prop_name, values) in props {
463 listing.push(prop_name.clone());
464 listing.push(values.len().to_string());
465 if values.is_empty() {
468 listing.push(" ".to_string());
469 }
470 listing.extend(values.iter().cloned());
471 }
472 }
473 Ok(Value::from(Spectrum::<str>::from(&listing[..])))
474 }
475
476 fn list_attr(&mut self, kind: Kind, input: Value) -> Result<Value> {
478 let args = strings(input)?;
479 let [obj_name, filter] = &args[..] else { return Err(malformed_err()) };
480 let names = self.attr_props.get(&kind.object(obj_name)).map_or_else(Vec::new, |attrs| attrs.names(filter));
481 Ok(Value::from(Spectrum::<str>::from(&names[..])))
482 }
483
484 fn put_attr_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
487 let args = strings(input)?;
488 let mut cursor = Cursor::new(&args);
489 let attrs = self.attr_props.entry(kind.object(cursor.next()?)).or_default();
490 for _ in 0..cursor.count()? {
491 let props = attrs.entry(cursor.next()?);
492 for _ in 0..cursor.count()? {
493 let prop_name = cursor.next()?;
494 *props.entry(prop_name) = cursor.values()?.to_vec();
495 }
496 }
497 cursor.end()?;
498 Ok(Value::Void)
499 }
500
501 fn delete_attr_prop(&mut self, kind: Kind, input: Value) -> Result<Value> {
503 let args = strings(input)?;
504 let [obj_name, attr_name, prop_names @ ..] = &args[..] else { return Err(malformed_err()) };
505 if let Some(attrs) = self.attr_props.get_mut(&kind.object(obj_name)) {
506 attrs.entry(attr_name).remove(prop_names);
507 attrs.0.retain(|(_, props)| !props.0.is_empty());
509 }
510 Ok(Value::Void)
511 }
512
513}
514
515struct Cursor<'a> {
519 args: &'a [String],
520 pos: usize,
521}
522
523impl<'a> Cursor<'a> {
524
525 fn new(args: &'a [String]) -> Self {
526 Self { args, pos: 0 }
527 }
528
529 fn next(&mut self) -> Result<&'a str> {
530 let arg = self.args.get(self.pos).ok_or_else(malformed_err)?;
531 self.pos += 1;
532 Ok(arg)
533 }
534
535 fn count(&mut self) -> Result<usize> {
536 self.next()?.parse().map_err(|_| malformed_err())
537 }
538
539 fn values(&mut self) -> Result<&'a [String]> {
541 let len = self.count()?;
542 let values = self.args.get(self.pos..self.pos + len).ok_or_else(malformed_err)?;
543 self.pos += len;
544 Ok(values)
545 }
546
547 fn end(&self) -> Result<()> {
549 match self.pos == self.args.len() {
550 true => Ok(()),
551 false => Err(malformed_err()),
552 }
553 }
554
555}
556
557fn strings(input: Value) -> Result<Vec<String>> {
559 Vec::<String>::try_from(input)
560}
561
562fn wildcard(filter: &str, name: &str) -> bool {
565
566 let (filter, name) = (filter.to_lowercase(), name.to_lowercase());
567 let mut parts = filter.split('*');
568
569 let first = parts.next().unwrap_or_default();
571 let Some(mut rest) = name.strip_prefix(first) else { return false };
572
573 let parts = parts.collect::<Vec<_>>();
574 let Some((last, middle)) = parts.split_last() else { return rest.is_empty() };
575
576 for part in middle {
577 match rest.find(part) {
578 Some(index) => rest = &rest[index + part.len()..],
579 None => return false,
580 }
581 }
582
583 rest.ends_with(last)
584
585}
586
587fn malformed_err() -> Error {
588 Error::new(ErrorKind::InvalidData)
589 .with_description("The argument is malformed")
590}
591
592fn not_defined_err(dev: &str) -> Error {
593 Error::new(ErrorKind::Reason(reason::DB_DeviceNotDefined))
594 .with_description(format!("The device {dev} is not defined in the database"))
595}
596
597#[derive(Debug)]
603pub struct TestDatabaseDev(pub Arc<TestDatabase>);
604
605fn not_implemented<'a, T: Send + 'a>(what: &'static str) -> BoxFuture<'a, Result<T>> {
606 Box::pin(ready(Err(Error::not_implemented()
607 .with_append_description(format_args!("{what} of the database of a test")))))
608}
609
610impl BackendDev for TestDatabaseDev {
611
612 fn name(&self) -> BoxFuture<'_, Result<Arc<str>>> {
613 Box::pin(ready(Ok(Arc::from(NAME))))
614 }
615
616 fn admin_dev_name(&self) -> BoxFuture<'_, Result<Arc<str>>> {
617 not_implemented("The admin device")
618 }
619
620 fn description(&self) -> BoxFuture<'_, Result<Arc<str>>> {
621 Box::pin(ready(Ok(Arc::from("The Tango database of a test"))))
622 }
623
624 fn info(&self) -> BoxFuture<'_, Result<DevInfo>> {
625 not_implemented("The info")
626 }
627
628 fn ping(&self) -> BoxFuture<'_, Result<()>> {
629 Box::pin(ready(Ok(())))
630 }
631
632 fn state(&self) -> BoxFuture<'_, Result<DevState>> {
633 Box::pin(ready(Ok(DevState::On)))
634 }
635
636 fn status(&self) -> BoxFuture<'_, Result<String>> {
637 Box::pin(ready(Ok("The database of a test".to_string())))
638 }
639
640 fn black_box(&self, _max: Option<usize>) -> BoxFuture<'_, Result<Vec<String>>> {
641 not_implemented("The black box")
642 }
643
644 fn command(&self, name: Scalar<str>, input: Value, _source: DevSource) -> BoxFuture<'_, Result<Value>> {
645 Box::pin(ready(self.0.command(name.value(), input)))
646 }
647
648 fn read_attr(&self, _names: Option<Spectrum<str>>, _source: DevSource) -> BoxFuture<'_, Result<Vec<NamedResult<AttrRead<Value>>>>> {
649 not_implemented("Reading the attributes")
650 }
651
652 fn write_attr(&self, _values: Vec<Named<Value>>) -> BoxFuture<'_, Result<Vec<Result<()>>>> {
653 not_implemented("Writing the attributes")
654 }
655
656 fn write_read_attr(&self, _values: Vec<Named<Value>>) -> BoxFuture<'_, Result<Vec<NamedResult<AttrRead<Value>>>>> {
657 not_implemented("Writing the attributes")
658 }
659
660 fn command_info(&self, _names: Option<Spectrum<str>>) -> BoxFuture<'_, Result<Vec<NamedResult<CommandInfo>>>> {
661 not_implemented("The commands")
662 }
663
664 fn attr_info(&self, _names: Option<Spectrum<str>>) -> BoxFuture<'_, Result<Vec<NamedResult<AttrInfo>>>> {
665 not_implemented("The attributes")
666 }
667
668 fn set_attr_config(&self, _configs: Vec<Named<AttrConfig>>) -> BoxFuture<'_, Result<Vec<Result<()>>>> {
669 Box::pin(ready(Err(Error::not_implemented()
670 .with_append_description("Writing the configuration of an attribute"))))
671 }
672
673 fn command_history<'a>(&'a self, _name: &'a str, _max: usize) -> BoxFuture<'a, Result<Vec<(SystemTime, Result<Value>)>>> {
674 Box::pin(ready(Err(Error::not_implemented()
675 .with_append_description("The recorded history of a command"))))
676 }
677
678 fn attr_history<'a>(&'a self, _name: &'a str, _max: usize) -> BoxFuture<'a, Result<Vec<Result<AttrRead<Value>>>>> {
679 Box::pin(ready(Err(Error::not_implemented()
680 .with_append_description("The recorded history of an attribute"))))
681 }
682
683 fn subscribe_attr_value<'a>(&'a self, _admin: Arc<dyn BackendDev>, _name: &'a str, _event: AttrValueEvent) -> BoxFuture<'a, Result<BoxStream<'static, Result<AttrRead<Value>>>>> {
684 Box::pin(ready(Err(Error::not_implemented()
685 .with_append_description("Subscribing to the readings of an attribute"))))
686 }
687
688 fn subscribe_attr_ready<'a>(&'a self, _admin: Arc<dyn BackendDev>, _name: &'a str) -> BoxFuture<'a, Result<BoxStream<'static, Result<()>>>> {
689 Box::pin(ready(Err(Error::not_implemented()
690 .with_append_description("Subscribing to an attribute being ready"))))
691 }
692
693 fn subscribe_attr_conf<'a>(&'a self, _admin: Arc<dyn BackendDev>, _name: &'a str) -> BoxFuture<'a, Result<BoxStream<'static, Result<AttrInfo>>>> {
694 Box::pin(ready(Err(Error::not_implemented()
695 .with_append_description("Subscribing to the configuration of an attribute"))))
696 }
697
698}
699
700#[cfg(test)]
701mod tests {
702
703 use super::*;
704
705 use crate::database::ExportDevInfo;
706 use crate::testing::TestContext;
707
708 fn context() -> TestContext {
709 TestContext::new().unwrap()
710 }
711
712 #[test]
713 fn wildcards() {
714 assert!(wildcard("*", "anything"));
715 assert!(wildcard("Pos*", "position"));
716 assert!(wildcard("*tion", "Position"));
717 assert!(wildcard("p*s*n", "position"));
718 assert!(wildcard("position", "POSITION"));
719 assert!(!wildcard("pos", "position"));
720 assert!(!wildcard("p*x*n", "position"));
721 }
722
723 #[test]
724 fn servers_and_devices() {
725
726 let ctx = context();
727 let db = ctx.database();
728 db.add_server("Motor/test", &[("test/motor/1", "Motor"), ("test/motor/2", "Motor")]).unwrap();
729 db.add_dev("test/axis/1", "Axis", "Motor/test").unwrap();
730
731 let listed = db.list_server_dev("motor/TEST").unwrap();
732 let listed = listed.iter().map(|dev| (dev.name.value(), dev.value.value())).collect::<Vec<_>>();
733 assert_eq!(listed, [
734 ("dserver/Motor/test", "DServer"),
735 ("test/motor/1", "Motor"),
736 ("test/motor/2", "Motor"),
737 ("test/axis/1", "Axis"),
738 ]);
739
740 db.delete_dev("TEST/MOTOR/2").unwrap();
741 assert_eq!(db.list_server_dev("Motor/test").unwrap().len(), 3);
742
743 db.delete_server("Motor/test").unwrap();
744 assert!(db.list_server_dev("Motor/test").unwrap().is_empty());
745 assert_eq!(db.import_dev("test/motor/1").unwrap_err().kind(), ErrorKind::DevNotFound);
746
747 }
748
749 #[test]
750 fn export_and_import() {
751
752 let ctx = context();
753 let db = ctx.database();
754 db.add_dev("test/motor/1", "Motor", "Motor/test").unwrap();
755
756 let info = db.import_dev("test/motor/1").unwrap();
757 assert!(!info.exported);
758 assert_eq!(info.class.as_ref().map(|class| class.value()), Some("Motor"));
759
760 db.export_server(&[ExportDevInfo {
761 name: "test/motor/1".into(),
762 itr: "IOR:0123".into(),
763 host: "localhost".into(),
764 version: "6".into(),
765 pid: 42,
766 }]).unwrap();
767
768 let info = db.import_dev("test/motor/1").unwrap();
769 assert!(info.exported);
770 assert_eq!((info.itr.value(), info.host.value(), info.version.value(), info.server.value(), info.pid),
771 ("IOR:0123", "localhost", "6", "Motor/test", 42));
772
773 db.unexport_server("motor/test").unwrap();
775 let info = db.import_dev("test/motor/1").unwrap();
776 assert!(!info.exported);
777 assert_eq!(info.itr.value(), "IOR:0123");
778
779 }
780
781 #[test]
782 fn properties() {
783
784 let ctx = context();
785 let db = ctx.database();
786
787 db.set_dev_prop("test/motor/1", [("Limit", "90"), ("Unit", "deg")]).unwrap();
788 db.set_dev_prop("TEST/motor/1", ("limit", "45")).unwrap();
789 assert_eq!(db.get_dev_prop::<f64, _>("test/motor/1", "Limit").unwrap(), 45.0);
790 assert_eq!(db.list_dev_prop("test/motor/1", "*").unwrap().len(), 2);
791
792 db.del_dev_prop("test/motor/1", "unit").unwrap();
793 assert_eq!(db.list_dev_prop("test/motor/1", "*").unwrap().len(), 1);
794
795 db.set_class_prop("Motor", ("Limit", "10")).unwrap();
797 assert_eq!(db.get_class_prop::<f64, _>("Motor", "Limit").unwrap(), 10.0);
798 assert_eq!(db.get_dev_prop::<f64, _>("test/motor/1", "Limit").unwrap(), 45.0);
799
800 db.set_dev_attr_prop("test/motor/1", ("Position", [("unit", "mm"), ("min_value", "-5")])).unwrap();
801 assert_eq!(db.get_dev_attr_prop::<String, _, _>("test/motor/1", "position", "Unit").unwrap(), "mm");
802
803 db.del_dev_attr_prop("test/motor/1", "Position", ["unit", "min_value"]).unwrap();
804 assert_eq!(db.get_dev_attr_prop::<String, _, _>("test/motor/1", "Position", "unit").unwrap(), "");
806
807 }
808
809 #[test]
811 fn clones_share() {
812 let ctx = context();
813 let db = ctx.database();
814 let clone = db.clone();
815 clone.add_dev("test/motor/1", "Motor", "Motor/test").unwrap();
816 assert_eq!(db.list_server_dev("Motor/test").unwrap().len(), 2);
817 let other = context();
818 assert!(other.database().list_server_dev("Motor/test").unwrap().is_empty());
819 assert_ne!(db, other.database());
820 }
821
822}