1use std::cell::RefCell;
26use std::collections::HashMap;
27
28pub const ASSERT_CONCURRENT: u32 = 1;
30pub const ASSERT_SIMPLE_IMMEDIATE: u32 = 2;
31pub const ASSERT_OBSERVED_DEFERRED: u32 = 4;
32pub const ASSERT_FINAL_DEFERRED: u32 = 8;
33pub const ASSERT_EXPECT: u32 = 16;
34pub const ASSERT_UNIQUE: u32 = 32;
35pub const ASSERT_UNIQUE0: u32 = 64;
36pub const ASSERT_PRIORITY: u32 = 128;
37pub const ASSERT_VIOLATION: u32 = ASSERT_UNIQUE | ASSERT_UNIQUE0 | ASSERT_PRIORITY;
39pub const ASSERT_ALL: u32 = 255;
41
42pub const DIR_ASSERT: u32 = 1;
44pub const DIR_COVER: u32 = 2;
45pub const DIR_ASSUME: u32 = 4;
46pub const DIR_ALL: u32 = 7;
48
49pub const Q_CHECKING: u32 = 1;
52pub const Q_PASS_NONVACUOUS: u32 = 2;
53pub const Q_PASS_VACUOUS: u32 = 4;
54pub const Q_FAIL: u32 = 8;
55pub const Q_ALL: u32 = Q_CHECKING | Q_PASS_NONVACUOUS | Q_PASS_VACUOUS | Q_FAIL;
57
58pub const EXPECT_NO_ACTION_TERM: i64 = 2;
62
63pub type SiteId = u32;
65
66#[derive(Clone, Copy, PartialEq, Eq, Debug)]
68pub enum Control {
69 Lock,
70 Unlock,
71 On,
72 Off,
73 Kill,
74 PassOn,
75 PassOff,
76 FailOn,
77 FailOff,
78 NonvacuousOn,
79 VacuousOff,
80}
81
82impl Control {
83 pub const fn from_value(n: i64) -> Option<Control> {
86 Some(match n {
87 1 => Control::Lock,
88 2 => Control::Unlock,
89 3 => Control::On,
90 4 => Control::Off,
91 5 => Control::Kill,
92 6 => Control::PassOn,
93 7 => Control::PassOff,
94 8 => Control::FailOn,
95 9 => Control::FailOff,
96 10 => Control::NonvacuousOn,
97 11 => Control::VacuousOff,
98 _ => return None,
99 })
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct SiteDesc {
106 pub assertion_type: u32,
108 pub directive_type: u32,
110 pub scope: String,
112 pub label: String,
116}
117
118impl SiteDesc {
119 pub fn encode(&self) -> String {
121 format!(
122 "{}|{}|{}|{}",
123 self.assertion_type, self.directive_type, self.scope, self.label
124 )
125 }
126
127 pub fn parse(text: &str) -> Result<SiteDesc, String> {
129 let mut parts = text.splitn(4, '|');
130 let (Some(a), Some(d), Some(scope), Some(label)) =
131 (parts.next(), parts.next(), parts.next(), parts.next())
132 else {
133 return Err(format!("assertion site descriptor `{text}` is malformed"));
134 };
135 let num = |s: &str| {
136 s.parse::<u32>()
137 .map_err(|_| format!("assertion site descriptor `{text}` is malformed"))
138 };
139 Ok(SiteDesc {
140 assertion_type: num(a)?,
141 directive_type: num(d)?,
142 scope: scope.to_string(),
143 label: label.to_string(),
144 })
145 }
146}
147
148#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct Op {
151 pub control: Control,
152 pub assertion_type: u32,
154 pub directive_type: u32,
156 pub levels: i64,
158 pub targets: Vec<String>,
161}
162
163#[derive(Clone, Copy, PartialEq, Eq, Debug)]
165pub struct SiteState {
166 pub locked: bool,
168 pub checking: bool,
171 pub pass_nonvacuous: bool,
173 pub pass_vacuous: bool,
175 pub fail: bool,
177 pub kills: u64,
179}
180
181impl SiteState {
182 pub const DEFAULT: SiteState = SiteState {
183 locked: false,
184 checking: true,
185 pass_nonvacuous: true,
186 pass_vacuous: true,
187 fail: true,
188 kills: 0,
189 };
190
191 pub fn query_bits(&self) -> u32 {
193 let mut bits = 0;
194 if self.checking {
195 bits |= Q_CHECKING;
196 }
197 if self.pass_nonvacuous {
198 bits |= Q_PASS_NONVACUOUS;
199 }
200 if self.pass_vacuous {
201 bits |= Q_PASS_VACUOUS;
202 }
203 if self.fail {
204 bits |= Q_FAIL;
205 }
206 bits
207 }
208}
209
210pub fn anonymous_target(scope: &str, offset: u64) -> String {
213 format!("#{offset}@{scope}")
214}
215
216fn depth(path: &str) -> usize {
218 if path.is_empty() {
219 0
220 } else {
221 path.split('.').count()
222 }
223}
224
225fn target_reaches(target: &str, levels: i64, desc: &SiteDesc) -> bool {
230 if let Some(anon) = target.strip_prefix('#') {
231 let Some((offset, scope)) = anon.split_once('@') else {
233 return false;
234 };
235 return desc.label.strip_prefix('#') == Some(offset)
236 && (desc.scope == scope
237 || (desc.scope.starts_with(scope) && desc.scope[scope.len()..].starts_with('.')));
238 }
239 if !desc.label.is_empty() && desc.label == target {
240 return true;
241 }
242 let inside = desc.scope == target
243 || (desc.scope.starts_with(target) && desc.scope[target.len()..].starts_with('.'));
244 if !inside {
245 return false;
246 }
247 let below = (depth(&desc.scope) - depth(target) + 1) as i64;
248 levels <= 0 || below <= levels
249}
250
251fn op_reaches(op: &Op, desc: &SiteDesc) -> bool {
253 if op.assertion_type & desc.assertion_type == 0 {
254 return false;
255 }
256 if desc.directive_type != 0 && op.directive_type & desc.directive_type == 0 {
257 return false;
258 }
259 if op.targets.is_empty() {
260 let below = depth(&desc.scope) as i64;
262 return op.levels <= 0 || below <= op.levels;
263 }
264 op.targets
265 .iter()
266 .any(|t| target_reaches(t, op.levels, desc))
267}
268
269fn fold(mut state: SiteState, desc: &SiteDesc, op: &Op) -> SiteState {
271 if !op_reaches(op, desc) {
272 return state;
273 }
274 match op.control {
275 Control::Lock => state.locked = true,
276 Control::Unlock => state.locked = false,
277 _ if state.locked => {}
278 Control::On | Control::Off | Control::Kill => {
279 if desc.assertion_type == ASSERT_EXPECT {
281 return state;
282 }
283 state.checking = op.control == Control::On;
284 if op.control == Control::Kill {
285 state.kills += 1;
286 }
287 }
288 Control::PassOn
289 | Control::PassOff
290 | Control::FailOn
291 | Control::FailOff
292 | Control::NonvacuousOn
293 | Control::VacuousOff => {
294 if desc.assertion_type & ASSERT_VIOLATION != 0 {
297 return state;
298 }
299 match op.control {
300 Control::PassOn => {
301 state.pass_nonvacuous = true;
302 state.pass_vacuous = true;
303 }
304 Control::PassOff => {
305 state.pass_nonvacuous = false;
306 state.pass_vacuous = false;
307 }
308 Control::FailOn => state.fail = true,
309 Control::FailOff => state.fail = false,
310 Control::NonvacuousOn => state.pass_nonvacuous = true,
311 Control::VacuousOff => state.pass_vacuous = false,
312 Control::Lock | Control::Unlock | Control::On | Control::Off | Control::Kill => {}
313 }
314 }
315 }
316 state
317}
318
319struct Site {
320 desc: SiteDesc,
321 state: SiteState,
322 applied: usize,
324}
325
326#[derive(Default)]
327struct Registry {
328 log: Vec<Op>,
329 sites: Vec<Site>,
330 by_text: HashMap<String, SiteId>,
331}
332
333impl Registry {
334 fn sync(&mut self, id: SiteId) -> SiteState {
335 let Registry { log, sites, .. } = self;
336 let site = &mut sites[id as usize];
337 while site.applied < log.len() {
338 site.state = fold(site.state, &site.desc, &log[site.applied]);
339 site.applied += 1;
340 }
341 site.state
342 }
343}
344
345thread_local! {
346 static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
347}
348
349pub fn reset() {
351 REGISTRY.with(|r| *r.borrow_mut() = Registry::default());
352}
353
354pub fn intern(desc: SiteDesc) -> SiteId {
357 let text = desc.encode();
358 REGISTRY.with(|r| {
359 let mut r = r.borrow_mut();
360 if let Some(&id) = r.by_text.get(&text) {
361 return id;
362 }
363 let id = r.sites.len() as SiteId;
364 r.sites.push(Site {
365 desc,
366 state: SiteState::DEFAULT,
367 applied: 0,
368 });
369 r.by_text.insert(text, id);
370 id
371 })
372}
373
374pub fn intern_text(text: &str) -> Result<SiteId, String> {
376 let known = REGISTRY.with(|r| r.borrow().by_text.get(text).copied());
377 match known {
378 Some(id) => Ok(id),
379 None => Ok(intern(SiteDesc::parse(text)?)),
380 }
381}
382
383pub fn state(id: SiteId) -> SiteState {
385 REGISTRY.with(|r| r.borrow_mut().sync(id))
386}
387
388pub fn query_text(text: &str) -> Result<u32, String> {
391 if REGISTRY.with(|r| r.borrow().log.is_empty()) {
392 return Ok(Q_ALL);
393 }
394 Ok(state(intern_text(text)?).query_bits())
395}
396
397#[derive(Clone, Copy, Debug, PartialEq, Eq)]
400pub enum ControlEvent {
401 Lock,
402 Unlock,
403 Disable,
404 Enable,
405 Kill,
406 PassOn,
407 PassOff,
408 FailOn,
409 FailOff,
410 NonvacuousOn,
411 VacuousOff,
412}
413
414pub type ControlObserver = fn(SiteId, ControlEvent);
416
417thread_local! {
418 static OBSERVER: std::cell::Cell<Option<ControlObserver>> =
419 const { std::cell::Cell::new(None) };
420}
421
422pub fn set_control_observer(f: Option<ControlObserver>) {
425 OBSERVER.with(|o| o.set(f));
426}
427
428fn changes(a: SiteState, b: SiteState) -> Vec<ControlEvent> {
430 let mut out = Vec::new();
431 if a.locked != b.locked {
432 out.push(if b.locked {
433 ControlEvent::Lock
434 } else {
435 ControlEvent::Unlock
436 });
437 }
438 if b.kills != a.kills {
439 out.push(ControlEvent::Kill);
440 } else if a.checking != b.checking {
441 out.push(if b.checking {
442 ControlEvent::Enable
443 } else {
444 ControlEvent::Disable
445 });
446 }
447 match (
448 a.pass_nonvacuous != b.pass_nonvacuous,
449 a.pass_vacuous != b.pass_vacuous,
450 ) {
451 (true, true) => out.push(if b.pass_nonvacuous {
452 ControlEvent::PassOn
453 } else {
454 ControlEvent::PassOff
455 }),
456 (true, false) if b.pass_nonvacuous => out.push(ControlEvent::NonvacuousOn),
457 (false, true) if !b.pass_vacuous => out.push(ControlEvent::VacuousOff),
458 (true, false) => out.push(ControlEvent::PassOff),
460 (false, true) => out.push(ControlEvent::PassOn),
461 (false, false) => {}
462 }
463 if a.fail != b.fail {
464 out.push(if b.fail {
465 ControlEvent::FailOn
466 } else {
467 ControlEvent::FailOff
468 });
469 }
470 out
471}
472
473pub fn control(op: Op) -> Vec<SiteId> {
477 let (killed, events) = REGISTRY.with(|r| {
478 let mut r = r.borrow_mut();
479 let ids: Vec<SiteId> = (0..r.sites.len() as SiteId).collect();
480 let before: Vec<SiteState> = ids.iter().map(|&id| r.sync(id)).collect();
481 r.log.push(op);
482 let mut killed = Vec::new();
483 let mut events = Vec::new();
484 for (&id, b) in ids.iter().zip(before) {
485 let a = r.sync(id);
486 if a.kills != b.kills {
487 killed.push(id);
488 }
489 events.extend(changes(b, a).into_iter().map(|e| (id, e)));
490 }
491 (killed, events)
492 });
493 if let Some(f) = OBSERVER.with(|o| o.get()) {
494 for (id, e) in events {
495 f(id, e);
496 }
497 }
498 killed
499}
500
501pub fn execute(op: Op) {
507 let killed = control(op);
508 if !killed.is_empty() {
509 crate::sched::maturation::flush_sites(&killed);
510 crate::sched::proc_assert::clear_sites(&killed);
511 }
512}
513
514pub const CTL_OP_CONTROL: u32 = 0;
516pub const CTL_OP_QUERY: u32 = 1;
518
519#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_rt_assert_ctl"))]
532pub unsafe extern "C" fn real_sv_rt_assert_ctl(
533 op: u32,
534 a0: i64,
535 a1: i64,
536 a2: i64,
537 a3: i64,
538 text: *const std::ffi::c_char,
539) -> u64 {
540 let text = if text.is_null() {
541 std::borrow::Cow::Borrowed("")
542 } else {
543 unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy()
545 };
546 match op {
547 CTL_OP_CONTROL => {
548 let Some(control) = Control::from_value(a0) else {
549 eprintln!("vj: warning: $assertcontrol: illegal control_type {a0}; ignored");
550 return 0;
551 };
552 execute(Op {
553 control,
554 assertion_type: (a1 & 0xFF) as u32,
555 directive_type: (a2 & 0x7) as u32,
556 levels: a3,
557 targets: text
558 .split('\n')
559 .filter(|t| !t.is_empty())
560 .map(str::to_string)
561 .collect(),
562 });
563 0
564 }
565 CTL_OP_QUERY => match query_text(&text) {
566 Ok(bits) => u64::from(bits),
567 Err(why) => {
568 eprintln!("vj: internal error: {why}");
569 u64::from(Q_ALL)
570 }
571 },
572 _ => {
573 eprintln!("vj: internal error: sv_rt_assert_ctl: unknown op {op}");
574 0
575 }
576 }
577}
578
579pub fn site_desc(id: SiteId) -> SiteDesc {
581 REGISTRY.with(|r| r.borrow().sites[id as usize].desc.clone())
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 fn desc(ty: u32, dir: u32, scope: &str, label: &str) -> SiteDesc {
589 SiteDesc {
590 assertion_type: ty,
591 directive_type: dir,
592 scope: scope.into(),
593 label: label.into(),
594 }
595 }
596
597 fn op(control: Control, ty: u32, dir: u32, levels: i64, targets: &[&str]) -> Op {
598 Op {
599 control,
600 assertion_type: ty,
601 directive_type: dir,
602 levels,
603 targets: targets.iter().map(|s| s.to_string()).collect(),
604 }
605 }
606
607 fn conc(scope: &str, label: &str) -> SiteId {
608 intern(desc(ASSERT_CONCURRENT, DIR_ASSERT, scope, label))
609 }
610
611 #[test]
612 fn site_descriptor_round_trips_and_rejects_garbage() {
613 let d = desc(ASSERT_CONCURRENT, DIR_COVER, "top.u1", "top.u1.c1");
614 assert_eq!(SiteDesc::parse(&d.encode()), Ok(d));
615 let unlabeled = desc(ASSERT_UNIQUE, 0, "top.chk", "");
616 assert_eq!(SiteDesc::parse(&unlabeled.encode()), Ok(unlabeled));
617 assert!(SiteDesc::parse("1|2|top").is_err());
618 assert!(SiteDesc::parse("x|2|top|a").is_err());
619 }
620
621 #[test]
622 fn control_types_one_to_eleven_are_named_and_the_rest_are_not() {
623 let all = [
624 Control::Lock,
625 Control::Unlock,
626 Control::On,
627 Control::Off,
628 Control::Kill,
629 Control::PassOn,
630 Control::PassOff,
631 Control::FailOn,
632 Control::FailOff,
633 Control::NonvacuousOn,
634 Control::VacuousOff,
635 ];
636 for (i, c) in all.iter().enumerate() {
637 assert_eq!(Control::from_value(i as i64 + 1), Some(*c));
638 }
639 assert_eq!(Control::from_value(0), None);
640 assert_eq!(Control::from_value(12), None);
641 assert_eq!(Control::from_value(-1), None);
642 }
643
644 #[test]
645 fn an_untouched_site_has_every_action_enabled_without_registering() {
646 reset();
647 assert_eq!(
648 query_text("1|1|top|top.a").unwrap(),
649 Q_ALL,
650 "an empty log answers the default"
651 );
652 let id = conc("top", "top.a");
653 assert_eq!(state(id), SiteState::DEFAULT);
654 }
655
656 #[test]
657 fn off_then_on_toggles_new_attempts() {
658 reset();
659 let id = conc("top", "top.a");
660 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
661 assert!(!state(id).checking);
662 control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
663 assert!(state(id).checking);
664 assert_eq!(state(id).kills, 0);
665 }
666
667 #[test]
668 fn levels_count_scopes_below_the_named_one() {
669 reset();
670 let top = conc("top", "top.a");
671 let u1 = conc("top.u1", "top.u1.a");
672 let deep = conc("top.u1.v", "top.u1.v.a");
673 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &["top"]));
675 assert!(!state(top).checking);
676 assert!(state(u1).checking);
677 assert!(state(deep).checking);
678 control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
679 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 2, &["top"]));
681 assert!(!state(top).checking);
682 assert!(!state(u1).checking);
683 assert!(state(deep).checking);
684 control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
685 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top"]));
687 assert!(!state(deep).checking);
688 }
689
690 #[test]
691 fn a_scope_name_matches_whole_path_components_only() {
692 reset();
693 let a = conc("top.u1", "top.u1.a");
694 let b = conc("top.u10", "top.u10.a");
695 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.u1"]));
696 assert!(!state(a).checking);
697 assert!(state(b).checking, "`top.u1` is not a prefix of `top.u10`");
698 }
699
700 #[test]
701 fn a_label_target_names_one_assertion_and_ignores_levels() {
702 reset();
703 let a = conc("top", "top.a");
704 let b = conc("top", "top.b");
705 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &["top.a"]));
706 assert!(!state(a).checking);
707 assert!(state(b).checking);
708 }
709
710 #[test]
711 fn no_target_with_levels_counts_the_top_modules_as_level_one() {
712 reset();
713 let top = conc("top", "top.a");
714 let u1 = conc("top.u1", "top.u1.a");
715 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &[]));
716 assert!(!state(top).checking);
717 assert!(state(u1).checking);
718 }
719
720 #[test]
721 fn assertion_and_directive_types_filter_the_sites() {
722 reset();
723 let assert_c = intern(desc(ASSERT_CONCURRENT, DIR_ASSERT, "top", "top.a"));
724 let cover_c = intern(desc(ASSERT_CONCURRENT, DIR_COVER, "top", "top.c"));
725 let assume_c = intern(desc(ASSERT_CONCURRENT, DIR_ASSUME, "top", "top.m"));
726 let assert_i = intern(desc(ASSERT_SIMPLE_IMMEDIATE, DIR_ASSERT, "top", "top.i"));
727 control(op(Control::Kill, ASSERT_CONCURRENT, DIR_ASSERT, 0, &[]));
728 assert!(!state(assert_c).checking);
729 assert!(state(cover_c).checking, "a cover is not an assert");
730 assert!(state(assume_c).checking);
731 assert!(state(assert_i).checking, "an immediate is not concurrent");
732 }
733
734 #[test]
735 fn lock_ignores_later_controls_until_unlock_and_unlock_always_applies() {
736 reset();
737 let a = conc("top", "top.a");
738 let b = conc("top", "top.b");
739 control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
740 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
741 assert!(state(a).checking, "locked: unaffected");
742 assert!(!state(b).checking);
743 control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
744 assert!(state(a).fail);
745 assert!(!state(b).fail);
746 control(op(Control::Unlock, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
747 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
748 assert!(!state(a).checking, "unlocked: affected again");
749 }
750
751 #[test]
752 fn a_lock_reaches_a_site_registered_after_it() {
753 reset();
754 control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.late"]));
755 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
756 let late = conc("top", "top.late");
757 assert!(state(late).checking);
758 }
759
760 #[test]
761 fn kill_reports_the_sites_it_reached_and_counts_per_site() {
762 reset();
763 let a = conc("top", "top.a");
764 let b = conc("top", "top.b");
765 let killed = control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
766 assert_eq!(killed, vec![a]);
767 assert_eq!(state(a).kills, 1);
768 assert_eq!(state(b).kills, 0);
769 assert!(!state(a).checking);
770 control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.b"]));
772 assert!(control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.b"])).is_empty());
773 assert!(control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.a"])).is_empty());
775 }
776
777 #[test]
778 fn expect_ignores_on_off_kill_but_takes_the_action_controls() {
779 reset();
780 let e = intern(desc(ASSERT_EXPECT, 0, "top", ""));
781 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
782 assert!(control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &[])).is_empty());
783 assert!(state(e).checking);
784 control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
785 assert!(!state(e).fail);
786 }
787
788 #[test]
789 fn expect_and_violation_sites_ignore_the_directive_filter() {
790 reset();
791 let e = intern(desc(ASSERT_EXPECT, 0, "top", ""));
792 let u = intern(desc(ASSERT_UNIQUE, 0, "top.chk", ""));
793 control(op(Control::FailOff, ASSERT_EXPECT, DIR_COVER, 0, &[]));
794 assert!(
795 !state(e).fail,
796 "directive_type is checked only for assertions"
797 );
798 control(op(Control::Off, ASSERT_UNIQUE, DIR_COVER, 0, &[]));
799 assert!(!state(u).checking);
800 }
801
802 #[test]
803 fn violation_sites_ignore_the_action_controls_but_take_on_off_kill() {
804 reset();
805 let u = intern(desc(ASSERT_UNIQUE, 0, "top", ""));
806 let p = intern(desc(ASSERT_PRIORITY, 0, "top", ""));
807 control(op(Control::PassOff, ASSERT_ALL, DIR_ALL, 0, &[]));
808 control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
809 assert_eq!(state(u), SiteState::DEFAULT);
810 assert_eq!(state(p), SiteState::DEFAULT);
811 control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
812 assert!(!state(u).checking && !state(p).checking);
813 control(op(
815 Control::On,
816 ASSERT_CONCURRENT | ASSERT_SIMPLE_IMMEDIATE | ASSERT_OBSERVED_DEFERRED,
817 DIR_ALL,
818 0,
819 &[],
820 ));
821 assert!(!state(u).checking);
822 control(op(
823 Control::On,
824 ASSERT_UNIQUE | ASSERT_UNIQUE0 | ASSERT_PRIORITY,
825 DIR_ALL,
826 0,
827 &[],
828 ));
829 assert!(state(u).checking && state(p).checking);
830 }
831
832 #[test]
833 fn the_six_action_controls_set_their_own_flags() {
834 reset();
835 let a = conc("top", "top.a");
836 let all = |c| control(op(c, ASSERT_ALL, DIR_ALL, 0, &[]));
837 all(Control::PassOff);
838 assert_eq!(state(a).query_bits(), Q_CHECKING | Q_FAIL);
839 all(Control::NonvacuousOn);
840 assert_eq!(
841 state(a).query_bits(),
842 Q_CHECKING | Q_FAIL | Q_PASS_NONVACUOUS
843 );
844 all(Control::PassOn);
845 assert_eq!(state(a).query_bits(), Q_ALL);
846 all(Control::VacuousOff);
847 assert_eq!(state(a).query_bits(), Q_ALL & !Q_PASS_VACUOUS);
848 all(Control::FailOff);
849 assert_eq!(
850 state(a).query_bits(),
851 Q_CHECKING | Q_PASS_NONVACUOUS,
852 "vacuous pass and fail are off"
853 );
854 all(Control::FailOn);
855 assert_eq!(
856 state(a).query_bits(),
857 Q_CHECKING | Q_PASS_NONVACUOUS | Q_FAIL
858 );
859 assert_eq!(state(a).kills, 0, "action controls are not kills");
860 }
861
862 #[test]
866 fn off_stops_procedural_instances_being_queued_and_kill_flushes_the_queue() {
867 use crate::sched::proc_assert;
868 reset();
869 proc_assert::reset();
870 let a = conc("top", "top.a");
871 let b = conc("top", "top.b");
872 proc_assert::register_site(0, a);
873 proc_assert::register_site(1, b);
874 let queue =
875 |key: i64| unsafe { proc_assert::real_sv_rt_proc_assert(key, std::ptr::null(), 0) };
876 queue(0);
877 queue(1);
878 execute(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
879 queue(0);
880 assert_eq!(
881 proc_assert::drain().len(),
882 2,
883 "off: the pending ones stay, no new one"
884 );
885 queue(0);
886 queue(1);
887 execute(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
888 queue(0);
889 execute(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
890 let rest = proc_assert::drain();
891 assert_eq!(
892 rest.iter().map(|e| e.key).collect::<Vec<_>>(),
893 vec![1],
894 "kill flushed the pending instances of a, not b's"
895 );
896 queue(0);
898 assert!(proc_assert::drain().is_empty());
899 execute(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
900 queue(0);
901 assert_eq!(proc_assert::drain().len(), 1);
902 proc_assert::reset();
903 }
904
905 #[test]
908 fn the_runtime_entry_point_controls_and_queries() {
909 use std::ffi::CString;
910 reset();
911 let site = desc(ASSERT_CONCURRENT, DIR_ASSERT, "top.u1", "top.u1.a").encode();
912 let site = CString::new(site).unwrap();
913 let ctl = |op: u32, a: [i64; 4], text: &str| {
914 let text = CString::new(text).unwrap();
915 unsafe { real_sv_rt_assert_ctl(op, a[0], a[1], a[2], a[3], text.as_ptr()) }
917 };
918 let q = || unsafe { real_sv_rt_assert_ctl(CTL_OP_QUERY, 0, 0, 0, 0, site.as_ptr()) };
919 assert_eq!(q(), u64::from(Q_ALL));
920 ctl(CTL_OP_CONTROL, [4, 255, 7, 0], "top.u1\ntop.u2");
921 assert_eq!(q(), u64::from(Q_ALL & !Q_CHECKING));
922 ctl(CTL_OP_CONTROL, [0, 255, 7, 0], "");
923 ctl(CTL_OP_CONTROL, [12, 255, 7, 0], "");
924 assert_eq!(q(), u64::from(Q_ALL & !Q_CHECKING), "illegal types ignored");
925 ctl(CTL_OP_CONTROL, [3, 255, 7, 0], "");
926 assert_eq!(q(), u64::from(Q_ALL));
927 }
928
929 #[test]
930 fn query_text_reads_the_state_of_the_named_site() {
931 reset();
932 let d = desc(ASSERT_SIMPLE_IMMEDIATE, DIR_ASSERT, "top", "top.i");
933 control(op(
934 Control::Off,
935 ASSERT_SIMPLE_IMMEDIATE,
936 DIR_ASSERT,
937 0,
938 &[],
939 ));
940 assert_eq!(query_text(&d.encode()).unwrap(), Q_ALL & !Q_CHECKING);
941 let other = desc(ASSERT_CONCURRENT, DIR_ASSERT, "top", "top.c");
942 assert_eq!(query_text(&other.encode()).unwrap(), Q_ALL);
943 assert!(query_text("garbage").is_err());
944 }
945}