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