1extern crate alloc;
23
24use alloc::sync::Arc;
25use alloc::vec::Vec;
26use core::sync::atomic::{AtomicBool, Ordering};
27use core::time::Duration;
28
29#[cfg(feature = "std")]
30use std::sync::{Condvar, Mutex};
31
32use crate::error::{DdsError, Result};
33
34pub trait Condition: Send + Sync {
36 fn get_trigger_value(&self) -> bool;
39}
40
41pub use crate::entity::StatusCondition;
44
45impl Condition for StatusCondition {
46 fn get_trigger_value(&self) -> bool {
47 self.trigger_value()
48 }
49}
50
51pub struct ReadCondition {
64 sample_state_mask: u32,
65 view_state_mask: u32,
66 instance_state_mask: u32,
67 trigger: Arc<dyn Fn(u32, u32, u32) -> bool + Send + Sync>,
68}
69
70impl core::fmt::Debug for ReadCondition {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 f.debug_struct("ReadCondition")
73 .field("sample_state_mask", &self.sample_state_mask)
74 .field("view_state_mask", &self.view_state_mask)
75 .field("instance_state_mask", &self.instance_state_mask)
76 .finish_non_exhaustive()
77 }
78}
79
80impl ReadCondition {
81 #[must_use]
83 pub fn new<F>(
84 sample_state_mask: u32,
85 view_state_mask: u32,
86 instance_state_mask: u32,
87 trigger: F,
88 ) -> Arc<Self>
89 where
90 F: Fn(u32, u32, u32) -> bool + Send + Sync + 'static,
91 {
92 Arc::new(Self {
93 sample_state_mask,
94 view_state_mask,
95 instance_state_mask,
96 trigger: Arc::new(trigger),
97 })
98 }
99
100 #[must_use]
102 pub fn get_sample_state_mask(&self) -> u32 {
103 self.sample_state_mask
104 }
105
106 #[must_use]
108 pub fn get_view_state_mask(&self) -> u32 {
109 self.view_state_mask
110 }
111
112 #[must_use]
114 pub fn get_instance_state_mask(&self) -> u32 {
115 self.instance_state_mask
116 }
117}
118
119impl Condition for ReadCondition {
120 fn get_trigger_value(&self) -> bool {
121 (self.trigger)(
122 self.sample_state_mask,
123 self.view_state_mask,
124 self.instance_state_mask,
125 )
126 }
127}
128
129pub struct QueryCondition {
134 base: Arc<ReadCondition>,
135 query_expression: alloc::string::String,
136 query_parameters: Mutex<Vec<alloc::string::String>>,
137 parsed: zerodds_sql_filter::Expr,
138}
139
140impl core::fmt::Debug for QueryCondition {
141 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142 f.debug_struct("QueryCondition")
143 .field("query_expression", &self.query_expression)
144 .field("base", &self.base)
145 .finish_non_exhaustive()
146 }
147}
148
149impl QueryCondition {
150 pub fn new(
157 base: Arc<ReadCondition>,
158 query_expression: impl Into<alloc::string::String>,
159 query_parameters: Vec<alloc::string::String>,
160 ) -> Result<Arc<Self>> {
161 let expr_str = query_expression.into();
162 let parsed = zerodds_sql_filter::parse(&expr_str).map_err(|_| DdsError::BadParameter {
163 what: "QueryCondition: invalid SQL filter expression",
164 })?;
165 Ok(Arc::new(Self {
166 base,
167 query_expression: expr_str,
168 query_parameters: Mutex::new(query_parameters),
169 parsed,
170 }))
171 }
172
173 pub fn evaluate<R: zerodds_sql_filter::RowAccess>(&self, row: &R) -> Result<bool> {
185 let params = self
186 .query_parameters
187 .lock()
188 .map_err(|_| DdsError::PreconditionNotMet {
189 reason: "query parameters poisoned",
190 })?;
191 let values: Vec<zerodds_sql_filter::Value> = params
192 .iter()
193 .map(|s| zerodds_sql_filter::Value::String(s.clone()))
194 .collect();
195 self.parsed
196 .evaluate(row, &values)
197 .map_err(|_| DdsError::PreconditionNotMet {
198 reason: "QueryCondition SQL evaluation failed",
199 })
200 }
201
202 pub fn evaluate_with_values<R: zerodds_sql_filter::RowAccess>(
209 &self,
210 row: &R,
211 params: &[zerodds_sql_filter::Value],
212 ) -> Result<bool> {
213 self.parsed
214 .evaluate(row, params)
215 .map_err(|_| DdsError::PreconditionNotMet {
216 reason: "QueryCondition SQL evaluation failed",
217 })
218 }
219
220 #[must_use]
222 pub fn get_query_expression(&self) -> &str {
223 &self.query_expression
224 }
225
226 #[must_use]
228 pub fn get_query_parameters(&self) -> Vec<alloc::string::String> {
229 self.query_parameters
230 .lock()
231 .map(|p| p.clone())
232 .unwrap_or_default()
233 }
234
235 pub fn set_query_parameters(&self, params: Vec<alloc::string::String>) -> Result<()> {
240 let mut current =
241 self.query_parameters
242 .lock()
243 .map_err(|_| DdsError::PreconditionNotMet {
244 reason: "query parameters poisoned",
245 })?;
246 *current = params;
247 Ok(())
248 }
249
250 #[must_use]
253 pub fn base(&self) -> &Arc<ReadCondition> {
254 &self.base
255 }
256}
257
258impl Condition for QueryCondition {
259 fn get_trigger_value(&self) -> bool {
260 self.base.get_trigger_value()
265 }
266}
267
268#[derive(Debug, Default)]
274pub struct GuardCondition {
275 triggered: AtomicBool,
276}
277
278impl GuardCondition {
279 #[must_use]
281 pub fn new() -> Arc<Self> {
282 Arc::new(Self::default())
283 }
284
285 pub fn set_trigger_value(&self, value: bool) {
287 self.triggered.store(value, Ordering::Release);
288 }
289}
290
291impl Condition for GuardCondition {
292 fn get_trigger_value(&self) -> bool {
293 self.triggered.load(Ordering::Acquire)
294 }
295}
296
297#[cfg(feature = "std")]
303pub struct WaitSet {
304 inner: Arc<WaitSetInner>,
305}
306
307#[cfg(feature = "std")]
308struct WaitSetInner {
309 conditions: Mutex<Vec<Arc<dyn Condition>>>,
310 cvar: Condvar,
315 poll_interval: core::time::Duration,
318 locked: Mutex<()>,
320 waiting: AtomicBool,
324}
325
326pub const MAX_WAITSET_CONDITIONS: usize = 1024;
330
331#[cfg(feature = "std")]
332impl WaitSet {
333 #[must_use]
335 pub fn new() -> Self {
336 Self {
337 inner: Arc::new(WaitSetInner {
338 conditions: Mutex::new(Vec::new()),
339 cvar: Condvar::new(),
340 poll_interval: Duration::from_millis(1),
341 locked: Mutex::new(()),
342 waiting: AtomicBool::new(false),
343 }),
344 }
345 }
346
347 pub fn attach_condition(&self, cond: Arc<dyn Condition>) -> Result<()> {
352 let mut conds = self
353 .inner
354 .conditions
355 .lock()
356 .map_err(|_| DdsError::PreconditionNotMet {
357 reason: "waitset conditions poisoned",
358 })?;
359 if conds.iter().any(|c| Arc::ptr_eq(c, &cond)) {
361 return Ok(());
362 }
363 if conds.len() >= MAX_WAITSET_CONDITIONS {
365 return Err(DdsError::OutOfResources {
366 what: "waitset condition count",
367 });
368 }
369 conds.push(cond);
370 Ok(())
371 }
372
373 pub fn detach_condition(&self, cond: &Arc<dyn Condition>) -> Result<()> {
378 let mut conds = self
379 .inner
380 .conditions
381 .lock()
382 .map_err(|_| DdsError::PreconditionNotMet {
383 reason: "waitset conditions poisoned",
384 })?;
385 conds.retain(|c| !Arc::ptr_eq(c, cond));
386 Ok(())
387 }
388
389 pub fn get_conditions(&self) -> Result<Vec<Arc<dyn Condition>>> {
394 let conds = self
395 .inner
396 .conditions
397 .lock()
398 .map_err(|_| DdsError::PreconditionNotMet {
399 reason: "waitset conditions poisoned",
400 })?;
401 Ok(conds.clone())
402 }
403
404 pub fn wait(&self, timeout: Duration) -> Result<Vec<Arc<dyn Condition>>> {
417 if self
422 .inner
423 .waiting
424 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
425 .is_err()
426 {
427 return Err(DdsError::PreconditionNotMet {
428 reason: "waitset already in wait() — single-thread-wait per spec",
429 });
430 }
431 struct WaitGuard<'a>(&'a AtomicBool);
432 impl Drop for WaitGuard<'_> {
433 fn drop(&mut self) {
434 self.0.store(false, Ordering::Release);
435 }
436 }
437 let _guard = WaitGuard(&self.inner.waiting);
438
439 let deadline = std::time::Instant::now() + timeout;
440 loop {
441 let active = self.poll_active()?;
442 if !active.is_empty() {
443 return Ok(active);
444 }
445 let now = std::time::Instant::now();
446 if now >= deadline {
447 return Err(DdsError::Timeout);
448 }
449 let sleep_for = (deadline - now).min(self.inner.poll_interval);
451 let cvlock = self
452 .inner
453 .locked
454 .lock()
455 .map_err(|_| DdsError::PreconditionNotMet {
456 reason: "waitset locked poisoned",
457 })?;
458 let _ = self.inner.cvar.wait_timeout(cvlock, sleep_for);
459 }
462 }
463
464 fn poll_active(&self) -> Result<Vec<Arc<dyn Condition>>> {
466 let conds = self
467 .inner
468 .conditions
469 .lock()
470 .map_err(|_| DdsError::PreconditionNotMet {
471 reason: "waitset conditions poisoned",
472 })?;
473 Ok(conds
474 .iter()
475 .filter(|c| c.get_trigger_value())
476 .cloned()
477 .collect())
478 }
479
480 pub fn notify(&self) {
487 self.inner.cvar.notify_all();
488 }
489}
490
491#[cfg(feature = "std")]
492impl Default for WaitSet {
493 fn default() -> Self {
494 Self::new()
495 }
496}
497
498#[cfg(feature = "std")]
499impl Clone for WaitSet {
500 fn clone(&self) -> Self {
501 Self {
502 inner: Arc::clone(&self.inner),
503 }
504 }
505}
506
507#[cfg(feature = "std")]
508impl core::fmt::Debug for WaitSet {
509 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
510 let n = self.inner.conditions.lock().map(|c| c.len()).unwrap_or(0);
511 f.debug_struct("WaitSet").field("conditions", &n).finish()
512 }
513}
514
515#[cfg(test)]
516#[cfg(feature = "std")]
517#[allow(clippy::expect_used, clippy::unwrap_used)]
518mod tests {
519 use super::*;
520 use std::thread;
521 use std::time::Instant;
522
523 #[test]
524 fn guard_condition_starts_false() {
525 let g = GuardCondition::new();
526 assert!(!g.get_trigger_value());
527 }
528
529 #[test]
530 fn guard_condition_set_trigger() {
531 let g = GuardCondition::new();
532 g.set_trigger_value(true);
533 assert!(g.get_trigger_value());
534 g.set_trigger_value(false);
535 assert!(!g.get_trigger_value());
536 }
537
538 #[test]
539 fn waitset_attach_detach_idempotent() {
540 let ws = WaitSet::new();
541 let g = GuardCondition::new();
542 let cond: Arc<dyn Condition> = g.clone();
543 ws.attach_condition(cond.clone()).unwrap();
544 ws.attach_condition(cond.clone()).unwrap(); assert_eq!(ws.get_conditions().unwrap().len(), 1);
546 ws.detach_condition(&cond).unwrap();
547 assert!(ws.get_conditions().unwrap().is_empty());
548 }
549
550 #[test]
551 fn waitset_wait_returns_immediately_if_already_triggered() {
552 let ws = WaitSet::new();
553 let g = GuardCondition::new();
554 g.set_trigger_value(true);
555 let cond: Arc<dyn Condition> = g.clone();
556 ws.attach_condition(cond).unwrap();
557 let triggered = ws.wait(Duration::from_secs(1)).unwrap();
558 assert_eq!(triggered.len(), 1);
559 }
560
561 #[test]
562 fn waitset_wait_timeout_returns_err() {
563 let ws = WaitSet::new();
564 let g = GuardCondition::new();
565 let cond: Arc<dyn Condition> = g.clone();
566 ws.attach_condition(cond).unwrap();
567 let start = Instant::now();
568 let res = ws.wait(Duration::from_millis(50));
569 assert!(matches!(res, Err(DdsError::Timeout)));
570 assert!(start.elapsed() >= Duration::from_millis(40));
572 }
573
574 #[test]
575 fn waitset_wakes_when_guard_triggers() {
576 let ws = WaitSet::new();
577 let g = GuardCondition::new();
578 let cond: Arc<dyn Condition> = g.clone();
579 ws.attach_condition(cond).unwrap();
580
581 let g_clone = g.clone();
582 let handle = thread::spawn(move || {
583 thread::sleep(Duration::from_millis(20));
584 g_clone.set_trigger_value(true);
585 });
586
587 let start = Instant::now();
588 let triggered = ws.wait(Duration::from_secs(2)).unwrap();
589 let elapsed = start.elapsed();
590 handle.join().unwrap();
591
592 assert_eq!(triggered.len(), 1);
593 assert!(elapsed < Duration::from_millis(500), "elapsed={elapsed:?}");
595 }
596
597 #[test]
598 fn waitset_with_multiple_conditions_returns_all_triggered() {
599 let ws = WaitSet::new();
600 let g1 = GuardCondition::new();
601 let g2 = GuardCondition::new();
602 ws.attach_condition(g1.clone()).unwrap();
603 ws.attach_condition(g2.clone()).unwrap();
604 g1.set_trigger_value(true);
605 g2.set_trigger_value(true);
606 let triggered = ws.wait(Duration::from_millis(100)).unwrap();
607 assert_eq!(triggered.len(), 2);
608 }
609
610 #[test]
611 fn status_condition_implements_condition_trait() {
612 let state = crate::entity::EntityState::new();
613 let sc = crate::entity::StatusCondition::new(state.clone());
614 sc.set_enabled_statuses(0b0010);
615 assert!(!sc.get_trigger_value());
616 state.set_status_bits(0b0010);
617 assert!(sc.get_trigger_value());
618 }
619
620 #[test]
621 fn waitset_clone_shares_state() {
622 let ws1 = WaitSet::new();
623 let ws2 = ws1.clone();
624 let g = GuardCondition::new();
625 ws1.attach_condition(g.clone()).unwrap();
626 assert_eq!(ws2.get_conditions().unwrap().len(), 1);
628 }
629
630 #[test]
631 fn waitset_default_is_empty() {
632 let ws = WaitSet::default();
633 assert!(ws.get_conditions().unwrap().is_empty());
634 }
635
636 #[test]
639 fn waitset_concurrent_wait_returns_precondition_not_met() {
640 let ws = WaitSet::new();
643 let ws_clone = ws.clone();
645 let handle = thread::spawn(move || ws_clone.wait(Duration::from_millis(100)));
646 thread::sleep(Duration::from_millis(20));
648 let res = ws.wait(Duration::from_millis(10));
649 assert!(matches!(res, Err(DdsError::PreconditionNotMet { .. })));
650 let _ = handle.join().unwrap();
651 }
652
653 #[test]
654 fn waitset_sequential_waits_after_first_returns_succeed() {
655 let ws = WaitSet::new();
658 let r1 = ws.wait(Duration::from_millis(10));
659 assert!(matches!(r1, Err(DdsError::Timeout)));
660 let r2 = ws.wait(Duration::from_millis(10));
662 assert!(matches!(r2, Err(DdsError::Timeout)));
663 }
664
665 #[test]
666 fn waitset_attach_above_max_returns_out_of_resources() {
667 let ws = WaitSet::new();
669 for _ in 0..MAX_WAITSET_CONDITIONS {
670 let g = GuardCondition::new();
671 ws.attach_condition(g).unwrap();
672 }
673 let g = GuardCondition::new();
675 let res = ws.attach_condition(g);
676 assert!(matches!(res, Err(DdsError::OutOfResources { .. })));
677 assert_eq!(ws.get_conditions().unwrap().len(), MAX_WAITSET_CONDITIONS);
678 }
679
680 #[test]
681 fn waitset_attach_idempotent_does_not_count_against_cap() {
682 let ws = WaitSet::new();
685 let g = GuardCondition::new();
686 let cond: Arc<dyn Condition> = g.clone();
687 ws.attach_condition(cond.clone()).unwrap();
688 ws.attach_condition(cond.clone()).unwrap();
689 ws.attach_condition(cond).unwrap();
690 assert_eq!(ws.get_conditions().unwrap().len(), 1);
691 }
692
693 #[test]
696 fn read_condition_returns_trigger_from_closure() {
697 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
698 let cond = ReadCondition::new(
700 sample_state_mask::ANY,
701 view_state_mask::ANY,
702 instance_state_mask::ANY,
703 |sm, vm, im| {
704 sm == sample_state_mask::ANY
705 && vm == view_state_mask::ANY
706 && im == instance_state_mask::ANY
707 },
708 );
709 assert_eq!(cond.get_sample_state_mask(), sample_state_mask::ANY);
710 assert_eq!(cond.get_view_state_mask(), view_state_mask::ANY);
711 assert_eq!(cond.get_instance_state_mask(), instance_state_mask::ANY);
712 assert!(cond.get_trigger_value());
713 }
714
715 #[test]
716 fn read_condition_trigger_false_when_closure_says_false() {
717 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
718 let cond = ReadCondition::new(
720 sample_state_mask::NOT_READ,
721 view_state_mask::NEW,
722 instance_state_mask::ALIVE,
723 |_, _, _| false,
724 );
725 assert!(!cond.get_trigger_value());
726 }
727
728 #[test]
729 fn read_condition_implements_condition_trait_object_safe() {
730 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
731 let cond = ReadCondition::new(
732 sample_state_mask::ANY,
733 view_state_mask::ANY,
734 instance_state_mask::ANY,
735 |_, _, _| true,
736 );
737 let dyn_cond: Arc<dyn Condition> = cond;
738 assert!(dyn_cond.get_trigger_value());
739 }
740
741 #[test]
742 fn read_condition_attaches_to_waitset() {
743 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
744 let ws = WaitSet::new();
746 let cond = ReadCondition::new(
747 sample_state_mask::ANY,
748 view_state_mask::ANY,
749 instance_state_mask::ANY,
750 |_, _, _| true, );
752 ws.attach_condition(cond.clone()).unwrap();
753 let triggered = ws.wait(Duration::from_millis(50)).unwrap();
754 assert_eq!(triggered.len(), 1);
755 }
756
757 #[test]
760 fn query_condition_inherits_trigger_from_base() {
761 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
762 let base = ReadCondition::new(
763 sample_state_mask::ANY,
764 view_state_mask::ANY,
765 instance_state_mask::ANY,
766 |_, _, _| true,
767 );
768 let qc = QueryCondition::new(base, "x > 10", alloc::vec::Vec::new()).unwrap();
769 assert!(qc.get_trigger_value());
770 assert_eq!(qc.get_query_expression(), "x > 10");
771 assert!(qc.get_query_parameters().is_empty());
772 }
773
774 #[test]
775 fn query_condition_set_query_parameters_roundtrip() {
776 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
777 let base = ReadCondition::new(
778 sample_state_mask::ANY,
779 view_state_mask::ANY,
780 instance_state_mask::ANY,
781 |_, _, _| false,
782 );
783 let qc = QueryCondition::new(base, "color = %0", alloc::vec!["RED".into()]).unwrap();
784 assert_eq!(qc.get_query_parameters(), alloc::vec!["RED".to_string()]);
785 qc.set_query_parameters(alloc::vec!["BLUE".into(), "GREEN".into()])
786 .unwrap();
787 assert_eq!(
788 qc.get_query_parameters(),
789 alloc::vec!["BLUE".to_string(), "GREEN".to_string()]
790 );
791 }
792
793 #[test]
794 fn query_condition_trigger_inherits_false_from_base() {
795 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
796 let base = ReadCondition::new(
797 sample_state_mask::READ,
798 view_state_mask::NOT_NEW,
799 instance_state_mask::NOT_ALIVE,
800 |_, _, _| false, );
802 let qc = QueryCondition::new(base, "x > 0", alloc::vec::Vec::new()).unwrap();
803 assert!(!qc.get_trigger_value());
804 }
805
806 #[test]
807 fn query_condition_base_returns_correct_read_condition() {
808 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
809 let base = ReadCondition::new(
810 sample_state_mask::NOT_READ,
811 view_state_mask::NEW,
812 instance_state_mask::ALIVE,
813 |_, _, _| true,
814 );
815 let qc = QueryCondition::new(base.clone(), "x > 0", alloc::vec::Vec::new()).unwrap();
816 assert!(Arc::ptr_eq(&base, qc.base()));
818 }
819
820 #[test]
821 fn query_condition_rejects_invalid_sql() {
822 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
823 let base = ReadCondition::new(
824 sample_state_mask::ANY,
825 view_state_mask::ANY,
826 instance_state_mask::ANY,
827 |_, _, _| true,
828 );
829 let r = QueryCondition::new(base, "x > >", alloc::vec::Vec::new());
830 assert!(matches!(r, Err(DdsError::BadParameter { .. })));
831 }
832
833 #[test]
834 fn query_condition_evaluate_filters_per_sample() {
835 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
836 use zerodds_sql_filter::{RowAccess, Value};
837 struct R(i64);
838 impl RowAccess for R {
839 fn get(&self, p: &str) -> Option<Value> {
840 if p == "x" {
841 Some(Value::Int(self.0))
842 } else {
843 None
844 }
845 }
846 }
847 let base = ReadCondition::new(
848 sample_state_mask::ANY,
849 view_state_mask::ANY,
850 instance_state_mask::ANY,
851 |_, _, _| true,
852 );
853 let qc = QueryCondition::new(base, "x > 10", alloc::vec::Vec::new()).unwrap();
854 assert!(qc.evaluate(&R(42)).unwrap());
855 assert!(!qc.evaluate(&R(5)).unwrap());
856 }
857
858 #[test]
859 fn query_condition_evaluate_with_typed_values_int_param() {
860 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
861 use zerodds_sql_filter::{RowAccess, Value};
862 struct R(i64);
863 impl RowAccess for R {
864 fn get(&self, p: &str) -> Option<Value> {
865 if p == "x" {
866 Some(Value::Int(self.0))
867 } else {
868 None
869 }
870 }
871 }
872 let base = ReadCondition::new(
873 sample_state_mask::ANY,
874 view_state_mask::ANY,
875 instance_state_mask::ANY,
876 |_, _, _| true,
877 );
878 let qc = QueryCondition::new(base, "x > %0", alloc::vec::Vec::new()).unwrap();
879 assert!(qc.evaluate_with_values(&R(42), &[Value::Int(10)]).unwrap());
880 assert!(!qc.evaluate_with_values(&R(5), &[Value::Int(10)]).unwrap());
881 }
882
883 #[test]
884 fn query_condition_evaluate_uses_string_params_by_default() {
885 use crate::sample_info::{instance_state_mask, sample_state_mask, view_state_mask};
886 use zerodds_sql_filter::{RowAccess, Value};
887 struct R(alloc::string::String);
888 impl RowAccess for R {
889 fn get(&self, p: &str) -> Option<Value> {
890 if p == "color" {
891 Some(Value::String(self.0.clone()))
892 } else {
893 None
894 }
895 }
896 }
897 let base = ReadCondition::new(
898 sample_state_mask::ANY,
899 view_state_mask::ANY,
900 instance_state_mask::ANY,
901 |_, _, _| true,
902 );
903 let qc = QueryCondition::new(base, "color = %0", alloc::vec!["RED".into()]).unwrap();
904 assert!(qc.evaluate(&R("RED".into())).unwrap());
905 assert!(!qc.evaluate(&R("BLUE".into())).unwrap());
906 }
907
908 #[test]
909 fn waitset_panic_in_wait_releases_waiting_flag() {
910 let ws = WaitSet::new();
916 let _ = ws.wait(Duration::from_millis(5));
917 let r = ws.wait(Duration::from_millis(5));
920 assert!(matches!(r, Err(DdsError::Timeout)));
921 }
922}